2
0

postrenderer.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. package helm
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/url"
  7. "regexp"
  8. "sort"
  9. "strings"
  10. "github.com/aws/aws-sdk-go/aws/arn"
  11. "github.com/porter-dev/porter/internal/kubernetes"
  12. "github.com/porter-dev/porter/internal/models"
  13. "github.com/porter-dev/porter/internal/repository"
  14. "github.com/stefanmcshane/helm/pkg/postrender"
  15. "golang.org/x/oauth2"
  16. "gopkg.in/yaml.v2"
  17. "github.com/docker/distribution/reference"
  18. )
  19. type PorterPostrenderer struct {
  20. DockerSecretsPostRenderer *DockerSecretsPostRenderer
  21. EnvironmentVariablePostrenderer *EnvironmentVariablePostrenderer
  22. }
  23. func NewPorterPostrenderer(
  24. cluster *models.Cluster,
  25. repo repository.Repository,
  26. agent *kubernetes.Agent,
  27. namespace string,
  28. regs []*models.Registry,
  29. doAuth *oauth2.Config,
  30. disablePullSecretsInjection bool,
  31. ) (postrender.PostRenderer, error) {
  32. var dockerSecretsPostrenderer *DockerSecretsPostRenderer
  33. var err error
  34. if !disablePullSecretsInjection && cluster != nil && agent != nil && regs != nil && len(regs) > 0 {
  35. dockerSecretsPostrenderer, err = NewDockerSecretsPostRenderer(cluster, repo, agent, namespace, regs, doAuth)
  36. if err != nil {
  37. return nil, err
  38. }
  39. }
  40. envVarPostrenderer, err := NewEnvironmentVariablePostrenderer()
  41. if err != nil {
  42. return nil, err
  43. }
  44. return &PorterPostrenderer{
  45. DockerSecretsPostRenderer: dockerSecretsPostrenderer,
  46. EnvironmentVariablePostrenderer: envVarPostrenderer,
  47. }, nil
  48. }
  49. func (p *PorterPostrenderer) Run(
  50. renderedManifests *bytes.Buffer,
  51. ) (modifiedManifests *bytes.Buffer, err error) {
  52. if p.DockerSecretsPostRenderer != nil {
  53. renderedManifests, err = p.DockerSecretsPostRenderer.Run(renderedManifests)
  54. if err != nil {
  55. return nil, err
  56. }
  57. }
  58. renderedManifests, err = p.EnvironmentVariablePostrenderer.Run(renderedManifests)
  59. return renderedManifests, err
  60. }
  61. // DockerSecretsPostRenderer is a Helm post-renderer that adds image pull secrets to
  62. // pod specs that would otherwise be unable to pull an image.
  63. //
  64. // The post-renderer currently looks for two types of registries: GCR and ECR (TODO: DOCR
  65. // and Dockerhub). It also detects if the image pull secret is necessary: if GCR image pulls
  66. // occur in a GKE cluster in the same project, or if ECR image pulls exist in an EKS cluster
  67. // in the same organization + region, an image pull is not necessary.
  68. type DockerSecretsPostRenderer struct {
  69. Cluster *models.Cluster
  70. Repo repository.Repository
  71. Agent *kubernetes.Agent
  72. Namespace string
  73. DOAuth *oauth2.Config
  74. registries map[string]*models.Registry
  75. podSpecs []resource
  76. resources []resource
  77. }
  78. // while manifests are map[string]interface{} at the top level,
  79. // nested keys will be of type map[interface{}]interface{}
  80. type resource map[interface{}]interface{}
  81. func NewDockerSecretsPostRenderer(
  82. cluster *models.Cluster,
  83. repo repository.Repository,
  84. agent *kubernetes.Agent,
  85. namespace string,
  86. regs []*models.Registry,
  87. doAuth *oauth2.Config,
  88. ) (*DockerSecretsPostRenderer, error) {
  89. // Registries is a map of registry URLs to registry ids
  90. registries := make(map[string]*models.Registry)
  91. for _, reg := range regs {
  92. regURL := reg.URL
  93. if !strings.Contains(regURL, "http") {
  94. regURL = "https://" + regURL
  95. }
  96. parsedRegURL, err := url.Parse(regURL)
  97. if err != nil {
  98. continue
  99. }
  100. addReg := parsedRegURL.Host
  101. if parsedRegURL.Path != "" {
  102. addReg += "/" + strings.Trim(parsedRegURL.Path, "/")
  103. }
  104. registries[addReg] = reg
  105. }
  106. return &DockerSecretsPostRenderer{
  107. Cluster: cluster,
  108. Repo: repo,
  109. Agent: agent,
  110. Namespace: namespace,
  111. DOAuth: doAuth,
  112. registries: registries,
  113. podSpecs: make([]resource, 0),
  114. resources: make([]resource, 0),
  115. }, nil
  116. }
  117. func (d *DockerSecretsPostRenderer) Run(
  118. renderedManifests *bytes.Buffer,
  119. ) (modifiedManifests *bytes.Buffer, err error) {
  120. bufCopy := bytes.NewBuffer(renderedManifests.Bytes())
  121. linkedRegs, err := d.getRegistriesToLink(bufCopy)
  122. // if we encountered an error here, we'll render the manifests anyway
  123. // without modification
  124. if err != nil {
  125. return renderedManifests, nil
  126. }
  127. // Check to see if the resources loaded into the postrenderer contain a configmap
  128. // with a manifest that needs secrets generation as well. If this is the case, create and
  129. // run another postrenderer for this specific manifest.
  130. for i, res := range d.resources {
  131. kindVal, hasKind := res["kind"]
  132. if !hasKind {
  133. continue
  134. }
  135. kind, ok := kindVal.(string)
  136. if !ok {
  137. continue
  138. }
  139. if kind == "ConfigMap" {
  140. labelVal := getNestedResource(res, "metadata", "labels")
  141. if labelVal == nil {
  142. continue
  143. }
  144. porterLabelVal, exists := labelVal["getporter.dev/manifest"]
  145. if !exists {
  146. continue
  147. }
  148. if labelValStr, ok := porterLabelVal.(string); ok && labelValStr == "true" {
  149. data := getNestedResource(res, "data")
  150. manifestData, exists := data["manifest"]
  151. if !exists {
  152. continue
  153. }
  154. manifestDataStr, ok := manifestData.(string)
  155. if !ok {
  156. continue
  157. }
  158. dCopy := &DockerSecretsPostRenderer{
  159. Cluster: d.Cluster,
  160. Repo: d.Repo,
  161. Agent: d.Agent,
  162. Namespace: d.Namespace,
  163. DOAuth: d.DOAuth,
  164. registries: d.registries,
  165. podSpecs: make([]resource, 0),
  166. resources: make([]resource, 0),
  167. }
  168. newData, err := dCopy.Run(bytes.NewBufferString(manifestDataStr))
  169. if err != nil {
  170. continue
  171. }
  172. data["manifest"] = string(newData.Bytes())
  173. d.resources[i] = res
  174. }
  175. }
  176. }
  177. // create the necessary secrets
  178. secrets, err := d.Agent.CreateImagePullSecrets(
  179. d.Repo,
  180. d.Namespace,
  181. linkedRegs,
  182. d.DOAuth,
  183. )
  184. if err != nil {
  185. return renderedManifests, nil
  186. }
  187. d.updatePodSpecs(secrets)
  188. modifiedManifests = bytes.NewBuffer([]byte{})
  189. encoder := yaml.NewEncoder(modifiedManifests)
  190. defer encoder.Close()
  191. for _, resource := range d.resources {
  192. // if the resource is empty, we skip encoding it to prevent errors. Helm/k8s expects empty resources to take the form "{}",
  193. // while this library writes an empty string, causing problems during installation.
  194. if len(resource) != 0 {
  195. err = encoder.Encode(resource)
  196. if err != nil {
  197. return nil, err
  198. }
  199. }
  200. }
  201. return modifiedManifests, nil
  202. }
  203. func (d *DockerSecretsPostRenderer) getRegistriesToLink(renderedManifests *bytes.Buffer) (map[string]*models.Registry, error) {
  204. // create a map of registry names to registries: these are the registries
  205. // that a secret will be generated for, if it does not exist
  206. linkedRegs := make(map[string]*models.Registry)
  207. var err error
  208. d.resources, err = decodeRenderedManifests(renderedManifests)
  209. if err != nil {
  210. return linkedRegs, err
  211. }
  212. // read the pod specs into the post-renderer object
  213. d.getPodSpecs(d.resources)
  214. for _, podSpec := range d.podSpecs {
  215. // get all images
  216. images := d.getImageList(podSpec)
  217. // read the image url
  218. for _, image := range images {
  219. regName, err := getRegNameFromImageRef(image)
  220. if err != nil {
  221. continue
  222. }
  223. // check if the integration is native to the cluster/registry combination
  224. isNative := d.isRegistryNative(regName)
  225. if isNative {
  226. continue
  227. }
  228. reg, exists := d.registries[regName]
  229. if !exists {
  230. continue
  231. }
  232. // if the registry exists, add it to the map
  233. linkedRegs[regName] = reg
  234. }
  235. }
  236. return linkedRegs, nil
  237. }
  238. func decodeRenderedManifests(
  239. renderedManifests *bytes.Buffer,
  240. ) ([]resource, error) {
  241. resArr := make([]resource, 0)
  242. // use the yaml decoder to parse the multi-document yaml.
  243. decoder := yaml.NewDecoder(renderedManifests)
  244. for {
  245. res := make(resource)
  246. err := decoder.Decode(&res)
  247. if err == io.EOF {
  248. break
  249. }
  250. if err != nil {
  251. return resArr, err
  252. }
  253. if len(res) != 0 {
  254. resArr = append(resArr, res)
  255. }
  256. }
  257. return resArr, nil
  258. }
  259. func (d *DockerSecretsPostRenderer) getPodSpecs(resources []resource) {
  260. for _, res := range resources {
  261. kindVal, hasKind := res["kind"]
  262. if !hasKind {
  263. continue
  264. }
  265. kind, ok := kindVal.(string)
  266. if !ok {
  267. continue
  268. }
  269. // manifests of list type will have an items field, items should
  270. // be recursively parsed
  271. if itemsVal, isList := res["items"]; isList {
  272. if items, ok := itemsVal.([]interface{}); ok {
  273. // convert items to resource
  274. resArr := make([]resource, 0)
  275. for _, item := range items {
  276. if arrVal, ok := item.(resource); ok {
  277. resArr = append(resArr, arrVal)
  278. }
  279. }
  280. d.getPodSpecs(resArr)
  281. }
  282. continue
  283. }
  284. // otherwise, get the pod spec based on the type of resource
  285. podSpec := getPodSpecFromResource(kind, res)
  286. if podSpec == nil {
  287. continue
  288. }
  289. d.podSpecs = append(d.podSpecs, podSpec)
  290. }
  291. return
  292. }
  293. func (d *DockerSecretsPostRenderer) updatePodSpecs(secrets map[string]string) {
  294. for _, podSpec := range d.podSpecs {
  295. containersVal, hasContainers := podSpec["containers"]
  296. if !hasContainers {
  297. continue
  298. }
  299. containers, ok := containersVal.([]interface{})
  300. if !ok {
  301. continue
  302. }
  303. imagePullSecrets := make([]map[string]interface{}, 0)
  304. if existingPullSecrets, ok := podSpec["imagePullSecrets"]; ok {
  305. if existing, ok := existingPullSecrets.([]map[string]interface{}); ok {
  306. imagePullSecrets = existing
  307. }
  308. }
  309. for _, container := range containers {
  310. _container, ok := container.(resource)
  311. if !ok {
  312. continue
  313. }
  314. image, ok := _container["image"].(string)
  315. if !ok {
  316. continue
  317. }
  318. regName, err := getRegNameFromImageRef(image)
  319. if err != nil {
  320. continue
  321. }
  322. if secretName, ok := secrets[regName]; ok && secretName != "" {
  323. imagePullSecrets = append(imagePullSecrets, map[string]interface{}{
  324. "name": secretName,
  325. })
  326. }
  327. }
  328. if len(imagePullSecrets) > 0 {
  329. podSpec["imagePullSecrets"] = imagePullSecrets
  330. }
  331. }
  332. }
  333. func (d *DockerSecretsPostRenderer) getImageList(podSpec resource) []string {
  334. images := make([]string, 0)
  335. containersVal, hasContainers := podSpec["containers"]
  336. if !hasContainers {
  337. return images
  338. }
  339. containers, ok := containersVal.([]interface{})
  340. if !ok {
  341. return images
  342. }
  343. for _, container := range containers {
  344. _container, ok := container.(resource)
  345. if !ok {
  346. continue
  347. }
  348. image, ok := _container["image"].(string)
  349. if !ok {
  350. continue
  351. }
  352. images = append(images, image)
  353. }
  354. return images
  355. }
  356. var ecrPattern = regexp.MustCompile(`(^[a-zA-Z0-9][a-zA-Z0-9-_]*)\.dkr\.ecr(\-fips)?\.([a-zA-Z0-9][a-zA-Z0-9-_]*)\.amazonaws\.com(\.cn)?`)
  357. func (d *DockerSecretsPostRenderer) isRegistryNative(regName string) bool {
  358. isNative := false
  359. if strings.Contains(regName, "gcr") && d.Cluster.AuthMechanism == models.GCP {
  360. // TODO (POR-33): fix architecture for clusters and re-add the code below
  361. // // get the project id of the cluster
  362. // gcpInt, err := d.Repo.GCPIntegration().ReadGCPIntegration(d.Cluster.ProjectID, d.Cluster.GCPIntegrationID)
  363. // if err != nil {
  364. // return false
  365. // }
  366. // gkeProjectID, err := integrations.GCPProjectIDFromJSON(gcpInt.GCPKeyData)
  367. // if err != nil {
  368. // return false
  369. // }
  370. // // parse the project id of the gcr url
  371. // if regNameArr := strings.Split(regName, "/"); len(regNameArr) >= 2 {
  372. // gcrProjectID := regNameArr[1]
  373. // isNative = gcrProjectID == gkeProjectID
  374. // }
  375. } else if strings.Contains(regName, "ecr") && d.Cluster.AuthMechanism == models.AWS {
  376. matches := ecrPattern.FindStringSubmatch(regName)
  377. if len(matches) < 3 {
  378. return false
  379. }
  380. eksAccountID := matches[1]
  381. eksRegion := matches[3]
  382. awsInt, err := d.Repo.AWSIntegration().ReadAWSIntegration(d.Cluster.ProjectID, d.Cluster.AWSIntegrationID)
  383. if err != nil {
  384. return false
  385. }
  386. err = awsInt.PopulateAWSArn()
  387. if err != nil {
  388. return false
  389. }
  390. parsedARN, err := arn.Parse(awsInt.AWSArn)
  391. if err != nil {
  392. return false
  393. }
  394. isNative = parsedARN.AccountID == eksAccountID && parsedARN.Region == eksRegion
  395. }
  396. return isNative
  397. }
  398. // EnvironmentVariablePostrenderer removes duplicated environment variables, giving preference to synced
  399. // env vars
  400. type EnvironmentVariablePostrenderer struct {
  401. podSpecs []resource
  402. resources []resource
  403. }
  404. func NewEnvironmentVariablePostrenderer() (*EnvironmentVariablePostrenderer, error) {
  405. return &EnvironmentVariablePostrenderer{
  406. podSpecs: make([]resource, 0),
  407. resources: make([]resource, 0),
  408. }, nil
  409. }
  410. func (e *EnvironmentVariablePostrenderer) Run(
  411. renderedManifests *bytes.Buffer,
  412. ) (modifiedManifests *bytes.Buffer, err error) {
  413. e.resources, err = decodeRenderedManifests(renderedManifests)
  414. if err != nil {
  415. return nil, err
  416. }
  417. // Check to see if the resources loaded into the postrenderer contain a configmap
  418. // with a manifest that needs env var cleanup as well. If this is the case, create and
  419. // run another postrenderer for this specific manifest.
  420. for i, res := range e.resources {
  421. kindVal, hasKind := res["kind"]
  422. if !hasKind {
  423. continue
  424. }
  425. kind, ok := kindVal.(string)
  426. if !ok {
  427. continue
  428. }
  429. if kind == "ConfigMap" {
  430. labelVal := getNestedResource(res, "metadata", "labels")
  431. if labelVal == nil {
  432. continue
  433. }
  434. porterLabelVal, exists := labelVal["getporter.dev/manifest"]
  435. if !exists {
  436. continue
  437. }
  438. if labelValStr, ok := porterLabelVal.(string); ok && labelValStr == "true" {
  439. data := getNestedResource(res, "data")
  440. manifestData, exists := data["manifest"]
  441. if !exists {
  442. continue
  443. }
  444. manifestDataStr, ok := manifestData.(string)
  445. if !ok {
  446. continue
  447. }
  448. dCopy := &EnvironmentVariablePostrenderer{
  449. podSpecs: make([]resource, 0),
  450. resources: make([]resource, 0),
  451. }
  452. newData, err := dCopy.Run(bytes.NewBufferString(manifestDataStr))
  453. if err != nil {
  454. continue
  455. }
  456. data["manifest"] = string(newData.Bytes())
  457. e.resources[i] = res
  458. }
  459. }
  460. }
  461. e.getPodSpecs(e.resources)
  462. e.updatePodSpecs()
  463. modifiedManifests = bytes.NewBuffer([]byte{})
  464. encoder := yaml.NewEncoder(modifiedManifests)
  465. defer encoder.Close()
  466. for _, resource := range e.resources {
  467. err = encoder.Encode(resource)
  468. if err != nil {
  469. return nil, err
  470. }
  471. }
  472. return modifiedManifests, nil
  473. }
  474. func (e *EnvironmentVariablePostrenderer) getPodSpecs(resources []resource) {
  475. for _, res := range resources {
  476. kindVal, hasKind := res["kind"]
  477. if !hasKind {
  478. continue
  479. }
  480. kind, ok := kindVal.(string)
  481. if !ok {
  482. continue
  483. }
  484. // manifests of list type will have an items field, items should
  485. // be recursively parsed
  486. if itemsVal, isList := res["items"]; isList {
  487. if items, ok := itemsVal.([]interface{}); ok {
  488. // convert items to resource
  489. resArr := make([]resource, 0)
  490. for _, item := range items {
  491. if arrVal, ok := item.(resource); ok {
  492. resArr = append(resArr, arrVal)
  493. }
  494. }
  495. e.getPodSpecs(resArr)
  496. }
  497. continue
  498. }
  499. // otherwise, get the pod spec based on the type of resource
  500. podSpec := getPodSpecFromResource(kind, res)
  501. if podSpec == nil {
  502. continue
  503. }
  504. e.podSpecs = append(e.podSpecs, podSpec)
  505. }
  506. return
  507. }
  508. func (e *EnvironmentVariablePostrenderer) updatePodSpecs() error {
  509. // for each pod spec, remove duplicate env variables
  510. for _, podSpec := range e.podSpecs {
  511. containersVal, hasContainers := podSpec["containers"]
  512. if !hasContainers {
  513. continue
  514. }
  515. containers, ok := containersVal.([]interface{})
  516. if !ok {
  517. continue
  518. }
  519. newContainers := make([]interface{}, 0)
  520. for _, container := range containers {
  521. envVars := make(map[string]interface{})
  522. _container, ok := container.(resource)
  523. if !ok {
  524. continue
  525. }
  526. // read container env variables
  527. envInter, ok := _container["env"]
  528. if !ok {
  529. newContainers = append(newContainers, _container)
  530. continue
  531. }
  532. env, ok := envInter.([]interface{})
  533. if !ok {
  534. newContainers = append(newContainers, _container)
  535. continue
  536. }
  537. for _, envVar := range env {
  538. envVarMap, ok := envVar.(resource)
  539. if !ok {
  540. continue
  541. }
  542. envVarName, ok := envVarMap["name"]
  543. if !ok {
  544. continue
  545. }
  546. envVarNameStr, ok := envVarName.(string)
  547. if !ok {
  548. continue
  549. }
  550. // check if the env var already exists, if it does perform reconciliation
  551. if currVal, exists := envVars[envVarNameStr]; exists {
  552. currValMap, ok := currVal.(resource)
  553. if !ok {
  554. continue
  555. }
  556. // if the current value has a valueFrom field, this should override the existing env var
  557. if _, currValFromFieldExists := currValMap["valueFrom"]; currValFromFieldExists {
  558. continue
  559. } else {
  560. envVars[envVarNameStr] = envVarMap
  561. }
  562. } else {
  563. envVars[envVarNameStr] = envVarMap
  564. }
  565. }
  566. // flatten env var map to array
  567. envVarArr := make([]interface{}, 0)
  568. for _, envVar := range envVars {
  569. envVarArr = append(envVarArr, envVar)
  570. }
  571. // Sort the slices according to a stable ordering. This is hacky and inefficient.
  572. sort.SliceStable(envVarArr, func(i, j int) bool {
  573. return fmt.Sprintf("%v", envVarArr[i]) > fmt.Sprintf("%v", envVarArr[j])
  574. })
  575. _container["env"] = envVarArr
  576. newContainers = append(newContainers, _container)
  577. }
  578. podSpec["containers"] = newContainers
  579. }
  580. return nil
  581. }
  582. // HELPERS
  583. func getPodSpecFromResource(kind string, res resource) resource {
  584. switch kind {
  585. case "Pod":
  586. return getNestedResource(res, "spec")
  587. case "DaemonSet", "Deployment", "Job", "ReplicaSet", "ReplicationController", "StatefulSet":
  588. return getNestedResource(res, "spec", "template", "spec")
  589. case "PodTemplate":
  590. return getNestedResource(res, "template", "spec")
  591. case "CronJob":
  592. return getNestedResource(res, "spec", "jobTemplate", "spec", "template", "spec")
  593. }
  594. return nil
  595. }
  596. func getNestedResource(res resource, keys ...string) resource {
  597. curr := res
  598. var ok bool
  599. for _, key := range keys {
  600. curr, ok = curr[key].(resource)
  601. if !ok {
  602. return nil
  603. }
  604. }
  605. return curr
  606. }
  607. func getRegNameFromImageRef(image string) (string, error) {
  608. named, err := reference.ParseNormalizedNamed(image)
  609. if err != nil {
  610. return "", err
  611. }
  612. domain := reference.Domain(named)
  613. path := reference.Path(named)
  614. var regName string
  615. // if registry is dockerhub, leave the image name as-is
  616. if strings.Contains(domain, "docker.io") {
  617. regName = "index.docker.io/" + path
  618. } else if strings.Contains(domain, "pkg.dev") {
  619. pathSlice := strings.Split(path, "/")
  620. // a GAR image path can either be PROJECT-ID/REPOSITORY/IMAGE or DOMAIN/PROJECT-ID/REPOSITORY/IMAGE
  621. //
  622. // see: https://cloud.google.com/artifact-registry/docs/docker/names#domain
  623. if len(pathSlice) == 3 {
  624. regName = fmt.Sprintf("%s/%s", domain, pathSlice[0])
  625. } else if len(pathSlice) == 4 {
  626. regName = fmt.Sprintf("%s/%s/%s", domain, pathSlice[0], pathSlice[1])
  627. } else {
  628. return "", fmt.Errorf("invalid GAR image: %s", image)
  629. }
  630. } else {
  631. regName = domain
  632. if pathArr := strings.Split(path, "/"); len(pathArr) > 1 {
  633. regName += "/" + strings.Join(pathArr[:len(pathArr)-1], "/")
  634. }
  635. }
  636. return regName, nil
  637. }
  638. type DeprecatedAPIVersionMapper struct{}
  639. type APIVersionKind struct {
  640. oldAPIVersion, newAPIVersion, oldKind, newKind string
  641. }
  642. func (d *DeprecatedAPIVersionMapper) Run(
  643. oldRenderedManifests *bytes.Buffer,
  644. newRenderedManifests *bytes.Buffer,
  645. ) (modifiedManifests *bytes.Buffer, err error) {
  646. oldResources, err := decodeRenderedManifests(oldRenderedManifests)
  647. if err != nil {
  648. return nil, err
  649. }
  650. newResources, err := decodeRenderedManifests(newRenderedManifests)
  651. if err != nil {
  652. return nil, err
  653. }
  654. newNameResourceMap := make(map[string]resource)
  655. for _, newRes := range newResources {
  656. name, ok := getResourceName(newRes)
  657. if !ok {
  658. continue
  659. }
  660. newKind, _, ok := getKindAndAPIVersion(newRes)
  661. if !ok {
  662. continue
  663. }
  664. uniqueName := fmt.Sprintf("%s-%s", strings.ToLower(newKind), name)
  665. newNameResourceMap[uniqueName] = newRes
  666. }
  667. nameMap := make(map[string]APIVersionKind)
  668. for _, oldRes := range oldResources {
  669. oldName, ok := getResourceName(oldRes)
  670. if !ok {
  671. continue
  672. }
  673. oldKind, oldAPIVersion, ok := getKindAndAPIVersion(oldRes)
  674. if !ok {
  675. continue
  676. }
  677. uniqueName := fmt.Sprintf("%s-%s", strings.ToLower(oldKind), oldName)
  678. newRes, exists := newNameResourceMap[uniqueName]
  679. if !exists {
  680. continue
  681. }
  682. newKind, newAPIVersion, ok := getKindAndAPIVersion(newRes)
  683. if !ok {
  684. continue
  685. }
  686. nameMap[oldName] = APIVersionKind{
  687. oldAPIVersion: oldAPIVersion,
  688. newAPIVersion: newAPIVersion,
  689. oldKind: oldKind,
  690. newKind: newKind,
  691. }
  692. // if the API versions don't match, update the old api version to the new api version
  693. if oldAPIVersion != newAPIVersion {
  694. oldRes["apiVersion"] = newAPIVersion
  695. }
  696. }
  697. modifiedManifests = bytes.NewBuffer([]byte{})
  698. encoder := yaml.NewEncoder(modifiedManifests)
  699. defer encoder.Close()
  700. for _, resource := range oldResources {
  701. err = encoder.Encode(resource)
  702. if err != nil {
  703. return nil, err
  704. }
  705. }
  706. return modifiedManifests, nil
  707. }
  708. func getResourceName(res resource) (string, bool) {
  709. metadataVal, hasMetadataVal := res["metadata"]
  710. if !hasMetadataVal {
  711. return "", false
  712. }
  713. metadata, ok := metadataVal.(resource)
  714. if !ok {
  715. return "", false
  716. }
  717. nameVal, ok := metadata["name"]
  718. if !ok {
  719. return "", false
  720. }
  721. name, ok := nameVal.(string)
  722. return name, ok
  723. }
  724. func getKindAndAPIVersion(res resource) (kind string, apiVersion string, ok bool) {
  725. kindVal, hasKindVal := res["kind"]
  726. if !hasKindVal {
  727. return "", "", false
  728. }
  729. kind, ok = kindVal.(string)
  730. if !ok {
  731. return "", "", false
  732. }
  733. apiVersionVal, hasAPIVersionVal := res["apiVersion"]
  734. if !hasAPIVersionVal {
  735. return "", "", false
  736. }
  737. apiVersion, ok = apiVersionVal.(string)
  738. if !ok {
  739. return "", "", false
  740. }
  741. return kind, apiVersion, true
  742. }