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