postrenderer.go 21 KB

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