Forráskód Böngészése

Merge pull request #1823 from porter-dev/belanger/api-version-migration

Add support for ingress version migrations on Kubernetes 1.22+
abelanger5 4 éve
szülő
commit
d0f6464e79

+ 16 - 2
api/server/handlers/namespace/get_ingress.go

@@ -42,7 +42,21 @@ func (c *GetIngressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	ingress, err := agent.GetIngress(namespace, name)
+	ingress1, err := agent.GetExtensionsV1Beta1Ingress(namespace, name)
+
+	if err == nil && ingress1 != nil {
+		c.WriteResult(w, r, ingress1)
+		return
+	}
+
+	ingress2, err := agent.GetNetworkingV1Beta1Ingress(namespace, name)
+
+	if err == nil && ingress2 != nil {
+		c.WriteResult(w, r, ingress2)
+		return
+	}
+
+	ingress3, err := agent.GetNetworkingV1Ingress(namespace, name)
 
 	if targetErr := kubernetes.IsNotFoundError; errors.Is(err, targetErr) {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
@@ -56,5 +70,5 @@ func (c *GetIngressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	c.WriteResult(w, r, ingress)
+	c.WriteResult(w, r, ingress3)
 }

+ 73 - 0
internal/helm/agent.go

@@ -1,6 +1,7 @@
 package helm
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"strconv"
@@ -259,6 +260,78 @@ func (a *Agent) UpgradeReleaseByValues(
 					return nil, fmt.Errorf("another operation (install/upgrade/rollback) is in progress. If this error persists, please wait for 60 seconds to force an upgrade")
 				}
 			}
+		} else if strings.Contains(err.Error(), "current release manifest contains removed kubernetes api(s)") {
+			// ref: https://helm.sh/docs/topics/kubernetes_apis/#updating-api-versions-of-a-release-manifest
+			// in this case, we manually update the secret containing the new manifests
+			secretList, err := a.K8sAgent.Clientset.CoreV1().Secrets(rel.Namespace).List(
+				context.Background(),
+				v1.ListOptions{
+					LabelSelector: fmt.Sprintf("owner=helm,name=%s", rel.Name),
+				},
+			)
+
+			if err != nil {
+				return nil, fmt.Errorf("Upgrade failed: %w", err)
+			}
+
+			if len(secretList.Items) > 0 {
+				mostRecentSecret := secretList.Items[0]
+
+				for i := 1; i < len(secretList.Items); i += 1 {
+					oldVersion, _ := strconv.Atoi(mostRecentSecret.Labels["version"])
+					newVersion, _ := strconv.Atoi(secretList.Items[i].Labels["version"])
+
+					if oldVersion < newVersion {
+						mostRecentSecret = secretList.Items[i]
+					}
+				}
+
+				// run the equivalent of `helm template` to get the manifest string for the new release
+				installCmd := action.NewInstall(a.ActionConfig)
+
+				installCmd.ReleaseName = conf.Name
+				installCmd.Namespace = rel.Namespace
+				installCmd.DryRun = true
+				installCmd.Replace = true
+
+				installCmd.ClientOnly = false
+				installCmd.IncludeCRDs = true
+
+				newRelDryRun, err := installCmd.Run(ch, conf.Values)
+
+				if err != nil {
+					return nil, err
+				}
+
+				oldManifestBuffer := bytes.NewBufferString(rel.Manifest)
+				newManifestBuffer := bytes.NewBufferString(newRelDryRun.Manifest)
+
+				versionMapper := &DeprecatedAPIVersionMapper{}
+
+				updatedManifestBuffer, err := versionMapper.Run(oldManifestBuffer, newManifestBuffer)
+
+				if err != nil {
+					return nil, err
+				}
+
+				rel.Manifest = updatedManifestBuffer.String()
+
+				helmSecrets := driver.NewSecrets(a.K8sAgent.Clientset.CoreV1().Secrets(rel.Namespace))
+
+				err = helmSecrets.Update(mostRecentSecret.GetName(), rel)
+
+				if err != nil {
+					return nil, fmt.Errorf("Upgrade failed: %w", err)
+				}
+
+				res, err := cmd.Run(conf.Name, ch, conf.Values)
+
+				if err != nil {
+					return nil, fmt.Errorf("Upgrade failed: %w", err)
+				}
+
+				return res, nil
+			}
 		}
 
 		return nil, fmt.Errorf("Upgrade failed: %w", err)

+ 152 - 0
internal/helm/postrenderer.go

@@ -823,3 +823,155 @@ func getRegNameFromImageRef(image string) (string, error) {
 
 	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
+}

+ 35 - 1
internal/kubernetes/agent.go

@@ -31,6 +31,8 @@ import (
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	v1 "k8s.io/api/core/v1"
 	v1beta1 "k8s.io/api/extensions/v1beta1"
+	netv1 "k8s.io/api/networking/v1"
+	netv1beta1 "k8s.io/api/networking/v1beta1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/fields"
@@ -829,7 +831,7 @@ func (a *Agent) GetJobPods(namespace, jobName string) ([]v1.Pod, error) {
 }
 
 // GetIngress gets ingress given the name and namespace
-func (a *Agent) GetIngress(namespace string, name string) (*v1beta1.Ingress, error) {
+func (a *Agent) GetExtensionsV1Beta1Ingress(namespace string, name string) (*v1beta1.Ingress, error) {
 	resp, err := a.Clientset.ExtensionsV1beta1().Ingresses(namespace).Get(
 		context.TODO(),
 		name,
@@ -845,6 +847,38 @@ func (a *Agent) GetIngress(namespace string, name string) (*v1beta1.Ingress, err
 	return resp, nil
 }
 
+func (a *Agent) GetNetworkingV1Ingress(namespace string, name string) (*netv1.Ingress, error) {
+	resp, err := a.Clientset.NetworkingV1().Ingresses(namespace).Get(
+		context.TODO(),
+		name,
+		metav1.GetOptions{},
+	)
+
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+func (a *Agent) GetNetworkingV1Beta1Ingress(namespace string, name string) (*netv1beta1.Ingress, error) {
+	resp, err := a.Clientset.NetworkingV1beta1().Ingresses(namespace).Get(
+		context.TODO(),
+		name,
+		metav1.GetOptions{},
+	)
+
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
 var IsNotFoundError = fmt.Errorf("not found")
 
 type BadRequestError struct {