| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977 |
- package helm
- import (
- "bytes"
- "fmt"
- "io"
- "net/url"
- "regexp"
- "sort"
- "strings"
- "github.com/aws/aws-sdk-go/aws/arn"
- "github.com/porter-dev/porter/internal/kubernetes"
- "github.com/porter-dev/porter/internal/models"
- "github.com/porter-dev/porter/internal/repository"
- "github.com/stefanmcshane/helm/pkg/postrender"
- "golang.org/x/oauth2"
- "gopkg.in/yaml.v2"
- "github.com/docker/distribution/reference"
- )
- type PorterPostrenderer struct {
- DockerSecretsPostRenderer *DockerSecretsPostRenderer
- EnvironmentVariablePostrenderer *EnvironmentVariablePostrenderer
- }
- func NewPorterPostrenderer(
- cluster *models.Cluster,
- repo repository.Repository,
- agent *kubernetes.Agent,
- namespace string,
- regs []*models.Registry,
- doAuth *oauth2.Config,
- disablePullSecretsInjection bool,
- ) (postrender.PostRenderer, error) {
- var dockerSecretsPostrenderer *DockerSecretsPostRenderer
- var err error
- if !disablePullSecretsInjection && cluster != nil && agent != nil && regs != nil && len(regs) > 0 {
- dockerSecretsPostrenderer, err = NewDockerSecretsPostRenderer(cluster, repo, agent, namespace, regs, doAuth)
- if err != nil {
- return nil, err
- }
- }
- envVarPostrenderer, err := NewEnvironmentVariablePostrenderer()
- if err != nil {
- return nil, err
- }
- return &PorterPostrenderer{
- DockerSecretsPostRenderer: dockerSecretsPostrenderer,
- EnvironmentVariablePostrenderer: envVarPostrenderer,
- }, nil
- }
- func (p *PorterPostrenderer) Run(
- renderedManifests *bytes.Buffer,
- ) (modifiedManifests *bytes.Buffer, err error) {
- if p.DockerSecretsPostRenderer != nil {
- renderedManifests, err = p.DockerSecretsPostRenderer.Run(renderedManifests)
- if err != nil {
- return nil, err
- }
- }
- renderedManifests, err = p.EnvironmentVariablePostrenderer.Run(renderedManifests)
- return renderedManifests, err
- }
- // DockerSecretsPostRenderer is a Helm post-renderer that adds image pull secrets to
- // pod specs that would otherwise be unable to pull an image.
- //
- // The post-renderer currently looks for two types of registries: GCR and ECR (TODO: DOCR
- // and Dockerhub). It also detects if the image pull secret is necessary: if GCR image pulls
- // occur in a GKE cluster in the same project, or if ECR image pulls exist in an EKS cluster
- // in the same organization + region, an image pull is not necessary.
- type DockerSecretsPostRenderer struct {
- Cluster *models.Cluster
- Repo repository.Repository
- Agent *kubernetes.Agent
- Namespace string
- DOAuth *oauth2.Config
- registries map[string]*models.Registry
- podSpecs []resource
- resources []resource
- }
- // while manifests are map[string]interface{} at the top level,
- // nested keys will be of type map[interface{}]interface{}
- type resource map[interface{}]interface{}
- func NewDockerSecretsPostRenderer(
- cluster *models.Cluster,
- repo repository.Repository,
- agent *kubernetes.Agent,
- namespace string,
- regs []*models.Registry,
- doAuth *oauth2.Config,
- ) (*DockerSecretsPostRenderer, error) {
- // Registries is a map of registry URLs to registry ids
- registries := make(map[string]*models.Registry)
- for _, reg := range regs {
- regURL := reg.URL
- if !strings.Contains(regURL, "http") {
- regURL = "https://" + regURL
- }
- parsedRegURL, err := url.Parse(regURL)
- if err != nil {
- continue
- }
- addReg := parsedRegURL.Host
- if parsedRegURL.Path != "" {
- addReg += "/" + strings.Trim(parsedRegURL.Path, "/")
- }
- registries[addReg] = reg
- }
- return &DockerSecretsPostRenderer{
- Cluster: cluster,
- Repo: repo,
- Agent: agent,
- Namespace: namespace,
- DOAuth: doAuth,
- registries: registries,
- podSpecs: make([]resource, 0),
- resources: make([]resource, 0),
- }, nil
- }
- func (d *DockerSecretsPostRenderer) Run(
- renderedManifests *bytes.Buffer,
- ) (modifiedManifests *bytes.Buffer, err error) {
- bufCopy := bytes.NewBuffer(renderedManifests.Bytes())
- linkedRegs, err := d.getRegistriesToLink(bufCopy)
- // if we encountered an error here, we'll render the manifests anyway
- // without modification
- if err != nil {
- return renderedManifests, nil
- }
- // Check to see if the resources loaded into the postrenderer contain a configmap
- // with a manifest that needs secrets generation as well. If this is the case, create and
- // run another postrenderer for this specific manifest.
- for i, res := range d.resources {
- kindVal, hasKind := res["kind"]
- if !hasKind {
- continue
- }
- kind, ok := kindVal.(string)
- if !ok {
- continue
- }
- if kind == "ConfigMap" {
- labelVal := getNestedResource(res, "metadata", "labels")
- if labelVal == nil {
- continue
- }
- porterLabelVal, exists := labelVal["getporter.dev/manifest"]
- if !exists {
- continue
- }
- if labelValStr, ok := porterLabelVal.(string); ok && labelValStr == "true" {
- data := getNestedResource(res, "data")
- manifestData, exists := data["manifest"]
- if !exists {
- continue
- }
- manifestDataStr, ok := manifestData.(string)
- if !ok {
- continue
- }
- dCopy := &DockerSecretsPostRenderer{
- Cluster: d.Cluster,
- Repo: d.Repo,
- Agent: d.Agent,
- Namespace: d.Namespace,
- DOAuth: d.DOAuth,
- registries: d.registries,
- podSpecs: make([]resource, 0),
- resources: make([]resource, 0),
- }
- newData, err := dCopy.Run(bytes.NewBufferString(manifestDataStr))
- if err != nil {
- continue
- }
- data["manifest"] = string(newData.Bytes())
- d.resources[i] = res
- }
- }
- }
- // create the necessary secrets
- secrets, err := d.Agent.CreateImagePullSecrets(
- d.Repo,
- d.Namespace,
- linkedRegs,
- d.DOAuth,
- )
- if err != nil {
- return renderedManifests, nil
- }
- d.updatePodSpecs(secrets)
- modifiedManifests = bytes.NewBuffer([]byte{})
- encoder := yaml.NewEncoder(modifiedManifests)
- defer encoder.Close()
- for _, resource := range d.resources {
- // if the resource is empty, we skip encoding it to prevent errors. Helm/k8s expects empty resources to take the form "{}",
- // while this library writes an empty string, causing problems during installation.
- if len(resource) != 0 {
- err = encoder.Encode(resource)
- if err != nil {
- return nil, err
- }
- }
- }
- return modifiedManifests, nil
- }
- func (d *DockerSecretsPostRenderer) getRegistriesToLink(renderedManifests *bytes.Buffer) (map[string]*models.Registry, error) {
- // create a map of registry names to registries: these are the registries
- // that a secret will be generated for, if it does not exist
- linkedRegs := make(map[string]*models.Registry)
- var err error
- d.resources, err = decodeRenderedManifests(renderedManifests)
- if err != nil {
- return linkedRegs, err
- }
- // read the pod specs into the post-renderer object
- d.getPodSpecs(d.resources)
- for _, podSpec := range d.podSpecs {
- // get all images
- images := d.getImageList(podSpec)
- // read the image url
- for _, image := range images {
- regName, err := getRegNameFromImageRef(image)
- if err != nil {
- continue
- }
- // check if the integration is native to the cluster/registry combination
- isNative := d.isRegistryNative(regName)
- if isNative {
- continue
- }
- reg, exists := d.registries[regName]
- if !exists {
- continue
- }
- // if the registry exists, add it to the map
- linkedRegs[regName] = reg
- }
- }
- return linkedRegs, nil
- }
- func decodeRenderedManifests(
- renderedManifests *bytes.Buffer,
- ) ([]resource, error) {
- resArr := make([]resource, 0)
- // use the yaml decoder to parse the multi-document yaml.
- decoder := yaml.NewDecoder(renderedManifests)
- for {
- res := make(resource)
- err := decoder.Decode(&res)
- if err == io.EOF {
- break
- }
- if err != nil {
- return resArr, err
- }
- if len(res) != 0 {
- resArr = append(resArr, res)
- }
- }
- return resArr, nil
- }
- func (d *DockerSecretsPostRenderer) getPodSpecs(resources []resource) {
- for _, res := range resources {
- kindVal, hasKind := res["kind"]
- if !hasKind {
- continue
- }
- kind, ok := kindVal.(string)
- if !ok {
- continue
- }
- // manifests of list type will have an items field, items should
- // be recursively parsed
- if itemsVal, isList := res["items"]; isList {
- if items, ok := itemsVal.([]interface{}); ok {
- // convert items to resource
- resArr := make([]resource, 0)
- for _, item := range items {
- if arrVal, ok := item.(resource); ok {
- resArr = append(resArr, arrVal)
- }
- }
- d.getPodSpecs(resArr)
- }
- continue
- }
- // otherwise, get the pod spec based on the type of resource
- podSpec := getPodSpecFromResource(kind, res)
- if podSpec == nil {
- continue
- }
- d.podSpecs = append(d.podSpecs, podSpec)
- }
- return
- }
- func (d *DockerSecretsPostRenderer) updatePodSpecs(secrets map[string]string) {
- for _, podSpec := range d.podSpecs {
- containersVal, hasContainers := podSpec["containers"]
- if !hasContainers {
- continue
- }
- containers, ok := containersVal.([]interface{})
- if !ok {
- continue
- }
- imagePullSecrets := make([]map[string]interface{}, 0)
- if existingPullSecrets, ok := podSpec["imagePullSecrets"]; ok {
- if existing, ok := existingPullSecrets.([]map[string]interface{}); ok {
- imagePullSecrets = existing
- }
- }
- for _, container := range containers {
- _container, ok := container.(resource)
- if !ok {
- continue
- }
- image, ok := _container["image"].(string)
- if !ok {
- continue
- }
- regName, err := getRegNameFromImageRef(image)
- if err != nil {
- continue
- }
- if secretName, ok := secrets[regName]; ok && secretName != "" {
- imagePullSecrets = append(imagePullSecrets, map[string]interface{}{
- "name": secretName,
- })
- }
- }
- if len(imagePullSecrets) > 0 {
- podSpec["imagePullSecrets"] = imagePullSecrets
- }
- }
- }
- func (d *DockerSecretsPostRenderer) getImageList(podSpec resource) []string {
- images := make([]string, 0)
- containersVal, hasContainers := podSpec["containers"]
- if !hasContainers {
- return images
- }
- containers, ok := containersVal.([]interface{})
- if !ok {
- return images
- }
- for _, container := range containers {
- _container, ok := container.(resource)
- if !ok {
- continue
- }
- image, ok := _container["image"].(string)
- if !ok {
- continue
- }
- images = append(images, image)
- }
- return images
- }
- 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)?`)
- func (d *DockerSecretsPostRenderer) isRegistryNative(regName string) bool {
- isNative := false
- if strings.Contains(regName, "gcr") && d.Cluster.AuthMechanism == models.GCP {
- // TODO (POR-33): fix architecture for clusters and re-add the code below
- // // get the project id of the cluster
- // gcpInt, err := d.Repo.GCPIntegration().ReadGCPIntegration(d.Cluster.ProjectID, d.Cluster.GCPIntegrationID)
- // if err != nil {
- // return false
- // }
- // gkeProjectID, err := integrations.GCPProjectIDFromJSON(gcpInt.GCPKeyData)
- // if err != nil {
- // return false
- // }
- // // parse the project id of the gcr url
- // if regNameArr := strings.Split(regName, "/"); len(regNameArr) >= 2 {
- // gcrProjectID := regNameArr[1]
- // isNative = gcrProjectID == gkeProjectID
- // }
- } else if strings.Contains(regName, "ecr") && d.Cluster.AuthMechanism == models.AWS {
- matches := ecrPattern.FindStringSubmatch(regName)
- if len(matches) < 3 {
- return false
- }
- eksAccountID := matches[1]
- eksRegion := matches[3]
- awsInt, err := d.Repo.AWSIntegration().ReadAWSIntegration(d.Cluster.ProjectID, d.Cluster.AWSIntegrationID)
- if err != nil {
- return false
- }
- err = awsInt.PopulateAWSArn()
- if err != nil {
- return false
- }
- parsedARN, err := arn.Parse(awsInt.AWSArn)
- if err != nil {
- return false
- }
- isNative = parsedARN.AccountID == eksAccountID && parsedARN.Region == eksRegion
- }
- return isNative
- }
- // EnvironmentVariablePostrenderer removes duplicated environment variables, giving preference to synced
- // env vars
- type EnvironmentVariablePostrenderer struct {
- podSpecs []resource
- resources []resource
- }
- func NewEnvironmentVariablePostrenderer() (*EnvironmentVariablePostrenderer, error) {
- return &EnvironmentVariablePostrenderer{
- podSpecs: make([]resource, 0),
- resources: make([]resource, 0),
- }, nil
- }
- func (e *EnvironmentVariablePostrenderer) Run(
- renderedManifests *bytes.Buffer,
- ) (modifiedManifests *bytes.Buffer, err error) {
- e.resources, err = decodeRenderedManifests(renderedManifests)
- if err != nil {
- return nil, err
- }
- // Check to see if the resources loaded into the postrenderer contain a configmap
- // with a manifest that needs env var cleanup as well. If this is the case, create and
- // run another postrenderer for this specific manifest.
- for i, res := range e.resources {
- kindVal, hasKind := res["kind"]
- if !hasKind {
- continue
- }
- kind, ok := kindVal.(string)
- if !ok {
- continue
- }
- if kind == "ConfigMap" {
- labelVal := getNestedResource(res, "metadata", "labels")
- if labelVal == nil {
- continue
- }
- porterLabelVal, exists := labelVal["getporter.dev/manifest"]
- if !exists {
- continue
- }
- if labelValStr, ok := porterLabelVal.(string); ok && labelValStr == "true" {
- data := getNestedResource(res, "data")
- manifestData, exists := data["manifest"]
- if !exists {
- continue
- }
- manifestDataStr, ok := manifestData.(string)
- if !ok {
- continue
- }
- dCopy := &EnvironmentVariablePostrenderer{
- podSpecs: make([]resource, 0),
- resources: make([]resource, 0),
- }
- newData, err := dCopy.Run(bytes.NewBufferString(manifestDataStr))
- if err != nil {
- continue
- }
- data["manifest"] = string(newData.Bytes())
- e.resources[i] = res
- }
- }
- }
- e.getPodSpecs(e.resources)
- e.updatePodSpecs()
- modifiedManifests = bytes.NewBuffer([]byte{})
- encoder := yaml.NewEncoder(modifiedManifests)
- defer encoder.Close()
- for _, resource := range e.resources {
- err = encoder.Encode(resource)
- if err != nil {
- return nil, err
- }
- }
- return modifiedManifests, nil
- }
- func (e *EnvironmentVariablePostrenderer) getPodSpecs(resources []resource) {
- for _, res := range resources {
- kindVal, hasKind := res["kind"]
- if !hasKind {
- continue
- }
- kind, ok := kindVal.(string)
- if !ok {
- continue
- }
- // manifests of list type will have an items field, items should
- // be recursively parsed
- if itemsVal, isList := res["items"]; isList {
- if items, ok := itemsVal.([]interface{}); ok {
- // convert items to resource
- resArr := make([]resource, 0)
- for _, item := range items {
- if arrVal, ok := item.(resource); ok {
- resArr = append(resArr, arrVal)
- }
- }
- e.getPodSpecs(resArr)
- }
- continue
- }
- // otherwise, get the pod spec based on the type of resource
- podSpec := getPodSpecFromResource(kind, res)
- if podSpec == nil {
- continue
- }
- e.podSpecs = append(e.podSpecs, podSpec)
- }
- return
- }
- func (e *EnvironmentVariablePostrenderer) updatePodSpecs() error {
- // for each pod spec, remove duplicate env variables
- for _, podSpec := range e.podSpecs {
- containersVal, hasContainers := podSpec["containers"]
- if !hasContainers {
- continue
- }
- containers, ok := containersVal.([]interface{})
- if !ok {
- continue
- }
- newContainers := make([]interface{}, 0)
- for _, container := range containers {
- envVars := make(map[string]interface{})
- _container, ok := container.(resource)
- if !ok {
- continue
- }
- // read container env variables
- envInter, ok := _container["env"]
- if !ok {
- newContainers = append(newContainers, _container)
- continue
- }
- env, ok := envInter.([]interface{})
- if !ok {
- newContainers = append(newContainers, _container)
- continue
- }
- for _, envVar := range env {
- envVarMap, ok := envVar.(resource)
- if !ok {
- continue
- }
- envVarName, ok := envVarMap["name"]
- if !ok {
- continue
- }
- envVarNameStr, ok := envVarName.(string)
- if !ok {
- continue
- }
- // check if the env var already exists, if it does perform reconciliation
- if currVal, exists := envVars[envVarNameStr]; exists {
- currValMap, ok := currVal.(resource)
- if !ok {
- continue
- }
- // if the current value has a valueFrom field, this should override the existing env var
- if _, currValFromFieldExists := currValMap["valueFrom"]; currValFromFieldExists {
- continue
- } else {
- envVars[envVarNameStr] = envVarMap
- }
- } else {
- envVars[envVarNameStr] = envVarMap
- }
- }
- // flatten env var map to array
- envVarArr := make([]interface{}, 0)
- for _, envVar := range envVars {
- envVarArr = append(envVarArr, envVar)
- }
- // Sort the slices according to a stable ordering. This is hacky and inefficient.
- sort.SliceStable(envVarArr, func(i, j int) bool {
- return fmt.Sprintf("%v", envVarArr[i]) > fmt.Sprintf("%v", envVarArr[j])
- })
- _container["env"] = envVarArr
- newContainers = append(newContainers, _container)
- }
- podSpec["containers"] = newContainers
- }
- return nil
- }
- // HELPERS
- func getPodSpecFromResource(kind string, res resource) resource {
- switch kind {
- case "Pod":
- return getNestedResource(res, "spec")
- case "DaemonSet", "Deployment", "Job", "ReplicaSet", "ReplicationController", "StatefulSet":
- return getNestedResource(res, "spec", "template", "spec")
- case "PodTemplate":
- return getNestedResource(res, "template", "spec")
- case "CronJob":
- return getNestedResource(res, "spec", "jobTemplate", "spec", "template", "spec")
- }
- return nil
- }
- func getNestedResource(res resource, keys ...string) resource {
- curr := res
- var ok bool
- for _, key := range keys {
- curr, ok = curr[key].(resource)
- if !ok {
- return nil
- }
- }
- return curr
- }
- func getRegNameFromImageRef(image string) (string, error) {
- named, err := reference.ParseNormalizedNamed(image)
- if err != nil {
- return "", err
- }
- domain := reference.Domain(named)
- path := reference.Path(named)
- var regName string
- // if registry is dockerhub, leave the image name as-is
- if strings.Contains(domain, "docker.io") {
- regName = "index.docker.io/" + path
- } else if strings.Contains(domain, "pkg.dev") {
- pathSlice := strings.Split(path, "/")
- // a GAR image path can either be PROJECT-ID/REPOSITORY/IMAGE or DOMAIN/PROJECT-ID/REPOSITORY/IMAGE
- //
- // see: https://cloud.google.com/artifact-registry/docs/docker/names#domain
- if len(pathSlice) == 3 {
- regName = fmt.Sprintf("%s/%s", domain, pathSlice[0])
- } else if len(pathSlice) == 4 {
- regName = fmt.Sprintf("%s/%s/%s", domain, pathSlice[0], pathSlice[1])
- } else {
- return "", fmt.Errorf("invalid GAR image: %s", image)
- }
- } else {
- regName = domain
- if pathArr := strings.Split(path, "/"); len(pathArr) > 1 {
- regName += "/" + strings.Join(pathArr[:len(pathArr)-1], "/")
- }
- }
- return regName, nil
- }
- type DeprecatedAPIVersionMapper struct{}
- type APIVersionKind struct {
- oldAPIVersion, newAPIVersion, oldKind, newKind string
- }
- func (d *DeprecatedAPIVersionMapper) Run(
- oldRenderedManifests *bytes.Buffer,
- newRenderedManifests *bytes.Buffer,
- ) (modifiedManifests *bytes.Buffer, err error) {
- oldResources, err := decodeRenderedManifests(oldRenderedManifests)
- if err != nil {
- return nil, err
- }
- newResources, err := decodeRenderedManifests(newRenderedManifests)
- if err != nil {
- return nil, err
- }
- newNameResourceMap := make(map[string]resource)
- for _, newRes := range newResources {
- name, ok := getResourceName(newRes)
- if !ok {
- continue
- }
- newKind, _, ok := getKindAndAPIVersion(newRes)
- if !ok {
- continue
- }
- uniqueName := fmt.Sprintf("%s-%s", strings.ToLower(newKind), name)
- newNameResourceMap[uniqueName] = newRes
- }
- nameMap := make(map[string]APIVersionKind)
- for _, oldRes := range oldResources {
- oldName, ok := getResourceName(oldRes)
- if !ok {
- continue
- }
- oldKind, oldAPIVersion, ok := getKindAndAPIVersion(oldRes)
- if !ok {
- continue
- }
- uniqueName := fmt.Sprintf("%s-%s", strings.ToLower(oldKind), oldName)
- newRes, exists := newNameResourceMap[uniqueName]
- if !exists {
- continue
- }
- newKind, newAPIVersion, ok := getKindAndAPIVersion(newRes)
- if !ok {
- continue
- }
- nameMap[oldName] = APIVersionKind{
- oldAPIVersion: oldAPIVersion,
- newAPIVersion: newAPIVersion,
- oldKind: oldKind,
- newKind: newKind,
- }
- // if the API versions don't match, update the old api version to the new api version
- if oldAPIVersion != newAPIVersion {
- oldRes["apiVersion"] = newAPIVersion
- }
- }
- modifiedManifests = bytes.NewBuffer([]byte{})
- encoder := yaml.NewEncoder(modifiedManifests)
- defer encoder.Close()
- for _, resource := range oldResources {
- err = encoder.Encode(resource)
- if err != nil {
- return nil, err
- }
- }
- return modifiedManifests, nil
- }
- func getResourceName(res resource) (string, bool) {
- metadataVal, hasMetadataVal := res["metadata"]
- if !hasMetadataVal {
- return "", false
- }
- metadata, ok := metadataVal.(resource)
- if !ok {
- return "", false
- }
- nameVal, ok := metadata["name"]
- if !ok {
- return "", false
- }
- name, ok := nameVal.(string)
- return name, ok
- }
- func getKindAndAPIVersion(res resource) (kind string, apiVersion string, ok bool) {
- kindVal, hasKindVal := res["kind"]
- if !hasKindVal {
- return "", "", false
- }
- kind, ok = kindVal.(string)
- if !ok {
- return "", "", false
- }
- apiVersionVal, hasAPIVersionVal := res["apiVersion"]
- if !hasAPIVersionVal {
- return "", "", false
- }
- apiVersion, ok = apiVersionVal.(string)
- if !ok {
- return "", "", false
- }
- return kind, apiVersion, true
- }
|