postrenderer.go 21 KB

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