Переглянути джерело

create default app template env (#3710)

ianedwards 2 роки тому
батько
коміт
dc18991b1a

+ 53 - 1
api/server/handlers/porter_app/create_app_template.go

@@ -2,21 +2,26 @@ package porter_app
 
 import (
 	"net/http"
+	"time"
 
 	"github.com/google/uuid"
+	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/kubernetes/environment_groups"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/porter_app"
 	"github.com/porter-dev/porter/internal/telemetry"
 )
 
 // CreateAppTemplateHandler is the handler for the /app-template endpoint
 type CreateAppTemplateHandler struct {
 	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
 }
 
 // NewCreateAppTemplateHandler handles POST requests to the endpoint /app-template
@@ -27,12 +32,15 @@ func NewCreateAppTemplateHandler(
 ) *CreateAppTemplateHandler {
 	return &CreateAppTemplateHandler{
 		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
 	}
 }
 
 // CreateAppTemplateRequest is the request object for the /app-template POST endpoint
 type CreateAppTemplateRequest struct {
-	B64AppProto string `json:"b64_app_proto"`
+	B64AppProto string            `json:"b64_app_proto"`
+	Variables   map[string]string `json:"variables"`
+	Secrets     map[string]string `json:"secrets"`
 }
 
 // CreateAppTemplateResponse is the response object for the /app-template POST endpoint
@@ -46,6 +54,7 @@ func (c *CreateAppTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	defer span.End()
 
 	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
 
 	if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
 		err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
@@ -74,6 +83,13 @@ func (c *CreateAppTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
+	agent, err := c.GetAgent(r, cluster, "")
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "error getting agent")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
 	porterApps, err := c.Repo().PorterApp().ReadPorterAppsByProjectIDAndName(project.ID, appName)
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error getting porter app from repo")
@@ -142,6 +158,42 @@ func (c *CreateAppTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
+	previewTemplateEnvName, err := porter_app.AppTemplateEnvGroupName(ctx, appName, cluster.ID, c.Repo().PorterApp())
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "unable to get app template env group name")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
+	envGroup, err := environment_groups.LatestBaseEnvironmentGroup(ctx, agent, previewTemplateEnvName)
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "unable to get latest base environment group")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
+	if envGroup.Name == "" {
+		envGroup = environment_groups.EnvironmentGroup{
+			Name:         previewTemplateEnvName,
+			CreatedAtUTC: time.Now().UTC(),
+		}
+	}
+	envGroup.Variables = request.Variables
+	envGroup.SecretVariables = request.Secrets
+
+	additionalEnvGroupLabels := map[string]string{
+		LabelKey_AppName: appName,
+		environment_groups.LabelKey_DefaultAppEnvironment: "true",
+		LabelKey_PorterManaged:                            "true",
+	}
+
+	err = environment_groups.CreateOrUpdateBaseEnvironmentGroup(ctx, agent, envGroup, additionalEnvGroupLabels)
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "unable to create or update base environment group")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
 	res := &CreateAppTemplateResponse{
 		AppTemplateID: updatedAppTemplate.ID.String(),
 	}

+ 32 - 13
api/server/handlers/porter_app/update_app_environment_group.go

@@ -160,8 +160,10 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 	}
 
 	namespace := deploymentTargetDetailsResp.Msg.Namespace
-	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "namespace", Value: namespace})
+	isPreview := deploymentTargetDetailsResp.Msg.IsPreview
 
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "is-preview", Value: isPreview})
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "namespace", Value: namespace})
 	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "hard-update", Value: request.HardUpdate})
 
 	appEnvGroupName, err := porter_app.AppEnvGroupName(ctx, appName, request.DeploymentTargetID, cluster.ID, c.Repo().PorterApp())
@@ -187,6 +189,21 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 
 	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "env-group-exists", Value: latestEnvironmentGroup.Name != ""})
 
+	previewTemplateEnvName, err := porter_app.AppTemplateEnvGroupName(ctx, appName, cluster.ID, c.Repo().PorterApp())
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "error getting preview template env name")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
+	// filter out preview template and app env groups
+	filteredEnvGroups := []*porterv1.EnvGroup{}
+	for _, envGroup := range appProto.EnvGroups {
+		if envGroup.GetName() != previewTemplateEnvName && envGroup.GetName() != appEnvGroupName {
+			filteredEnvGroups = append(filteredEnvGroups, envGroup)
+		}
+	}
+
 	if latestEnvironmentGroup.Name != "" {
 		sameEnvGroup := true
 		for key, newValue := range request.Variables {
@@ -217,9 +234,8 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		if sameEnvGroup {
 			// even if the env group is the same, we still need to sync the latest versions of the other env groups
 			syncInp := syncLatestEnvGroupVersionsInput{
-				envGroups:          appProto.EnvGroups,
+				envGroups:          filteredEnvGroups,
 				appName:            appName,
-				appEnvName:         appEnvGroupName,
 				namespace:          namespace,
 				deploymentTargetID: request.DeploymentTargetID,
 				k8sAgent:           agent,
@@ -245,6 +261,18 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		}
 	}
 
+	// if this app does not have a default env group for this deployment target and is a preview
+	// then use the preview template env group as the default
+	// this should only run when the app is first deployed to a given deployment target
+	if latestEnvironmentGroup.Name == "" && isPreview {
+		latestEnvironmentGroup, err = environment_groups.LatestBaseEnvironmentGroup(ctx, agent, previewTemplateEnvName)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "unable to get latest base environment group")
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+			return
+		}
+	}
+
 	variables := make(map[string]string)
 	secrets := make(map[string]string)
 
@@ -299,9 +327,8 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "env-group-versioned-name", Value: syncedAppEnvironment.EnvironmentGroupVersionedName})
 
 	syncInp := syncLatestEnvGroupVersionsInput{
-		envGroups:          appProto.EnvGroups,
+		envGroups:          filteredEnvGroups,
 		appName:            appName,
-		appEnvName:         appEnvGroupName,
 		namespace:          namespace,
 		deploymentTargetID: request.DeploymentTargetID,
 		k8sAgent:           agent,
@@ -344,8 +371,6 @@ type syncLatestEnvGroupVersionsInput struct {
 	envGroups []*porterv1.EnvGroup
 	// appName is the name of the app
 	appName string
-	// appEnvName is the name of the app env. This is the env group created when the app is created for storing app-specific variables
-	appEnvName string
 	// namespace is the namespace to sync the latest versions to
 	namespace string
 	// deploymentTargetID is the id of the deployment target
@@ -367,9 +392,6 @@ func syncLatestEnvGroupVersions(ctx context.Context, inp syncLatestEnvGroupVersi
 	if inp.appName == "" {
 		return envGroups, telemetry.Error(ctx, span, nil, "app name is empty")
 	}
-	if inp.appEnvName == "" {
-		return envGroups, telemetry.Error(ctx, span, nil, "app env name is empty")
-	}
 	if inp.namespace == "" {
 		return envGroups, telemetry.Error(ctx, span, nil, "namespace is empty")
 	}
@@ -381,9 +403,6 @@ func syncLatestEnvGroupVersions(ctx context.Context, inp syncLatestEnvGroupVersi
 		if envGroup == nil {
 			continue
 		}
-		if envGroup.GetName() == inp.appEnvName {
-			continue
-		}
 
 		additionalEnvGroupLabels := map[string]string{
 			LabelKey_AppName:            inp.appName,

+ 1 - 1
go.mod

@@ -82,7 +82,7 @@ require (
 	github.com/matryer/is v1.4.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.2.6
+	github.com/porter-dev/api-contracts v0.2.8
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

+ 2 - 2
go.sum

@@ -1516,8 +1516,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.2.6 h1:5Z/Qr1Qv6iAM4rCUfpa9+HougO8K2HFjGOeSLDZFfDw=
-github.com/porter-dev/api-contracts v0.2.6/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.8 h1:z5BZihdZ75J5Dz3jCeX6ziE/wt6h4yhFqaYoO3BqBY8=
+github.com/porter-dev/api-contracts v0.2.8/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=

+ 24 - 0
internal/porter_app/environment.go

@@ -144,3 +144,27 @@ func AppEnvGroupName(ctx context.Context, appName string, deploymentTargetId str
 
 	return fmt.Sprintf("%d-%s", porterApp.ID, deploymentTargetId[:6]), nil
 }
+
+// AppTemplateEnvGroupName returns the name of the environment group for an app template
+func AppTemplateEnvGroupName(ctx context.Context, appName string, clusterID uint, porterAppRepository repository.PorterAppRepository) (string, error) {
+	ctx, span := telemetry.NewSpan(ctx, "app-template-env-group-name")
+	defer span.End()
+
+	if appName == "" {
+		return "", telemetry.Error(ctx, span, nil, "app name is empty")
+	}
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-name", Value: appName})
+
+	if clusterID == 0 {
+		return "", telemetry.Error(ctx, span, nil, "cluster id is empty")
+	}
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "cluster-id", Value: clusterID})
+
+	porterApp, err := porterAppRepository.ReadPorterAppByName(clusterID, appName)
+	if err != nil {
+		return "", telemetry.Error(ctx, span, err, "error reading porter app by name")
+	}
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "porter-app-id", Value: porterApp.ID})
+
+	return fmt.Sprintf("%d-template-preview", porterApp.ID), nil
+}