diff --git a/api/server/handlers/porter_app/update_app_environment_group.go b/api/server/handlers/porter_app/update_app_environment_group.go index 22ed7b1655..1e695e9e61 100644 --- a/api/server/handlers/porter_app/update_app_environment_group.go +++ b/api/server/handlers/porter_app/update_app_environment_group.go @@ -69,8 +69,7 @@ type UpdateAppEnvironmentRequest struct { // UpdateAppEnvironmentResponse represents the fields on the response object from the /apps/{porter_app_name}/environment-group endpoint type UpdateAppEnvironmentResponse struct { - EnvGroupName string `json:"env_group_name"` - EnvGroupVersion int `json:"env_group_version"` + EnvGroups []environment_groups.EnvironmentGroup `json:"env_groups"` } // ServeHTTP updates or creates the environment group for an app @@ -221,21 +220,24 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R envGroups: appProto.EnvGroups, appName: appName, appEnvName: appEnvGroupName, - sameAppEnv: true, namespace: namespace, deploymentTargetID: request.DeploymentTargetID, k8sAgent: agent, } - err = syncLatestEnvGroupVersions(ctx, syncInp) + latestEnvGroups, err := syncLatestEnvGroupVersions(ctx, syncInp) if err != nil { err := telemetry.Error(ctx, span, err, "error syncing latest env group versions") c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) return } + latestEnvGroups = append(latestEnvGroups, environment_groups.EnvironmentGroup{ + Name: latestEnvironmentGroup.Name, + Version: latestEnvironmentGroup.Version, + }) + res := &UpdateAppEnvironmentResponse{ - EnvGroupName: latestEnvironmentGroup.Name, - EnvGroupVersion: latestEnvironmentGroup.Version, + EnvGroups: latestEnvGroups, } c.WriteResult(w, r, res) @@ -288,47 +290,50 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R TargetNamespace: namespace, } - syncedEnvironment, err := environment_groups.SyncLatestVersionToNamespace(ctx, agent, inp, additionalEnvGroupLabels) + syncedAppEnvironment, err := environment_groups.SyncLatestVersionToNamespace(ctx, agent, inp, additionalEnvGroupLabels) if err != nil { err := telemetry.Error(ctx, span, err, "unable to create or update synced environment group") c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) return } - telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "env-group-versioned-name", Value: syncedEnvironment.EnvironmentGroupVersionedName}) - - split := strings.Split(syncedEnvironment.EnvironmentGroupVersionedName, ".") - if len(split) != 2 { - err := telemetry.Error(ctx, span, err, "unexpected environment group versioned name") - c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) - return - } - - version, err := strconv.Atoi(split[1]) - if err != nil { - err := telemetry.Error(ctx, span, err, "error converting environment group version to int") - c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) - return - } + telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "env-group-versioned-name", Value: syncedAppEnvironment.EnvironmentGroupVersionedName}) syncInp := syncLatestEnvGroupVersionsInput{ envGroups: appProto.EnvGroups, appName: appName, appEnvName: appEnvGroupName, - sameAppEnv: false, namespace: namespace, deploymentTargetID: request.DeploymentTargetID, k8sAgent: agent, } - err = syncLatestEnvGroupVersions(ctx, syncInp) + latestEnvGroups, err := syncLatestEnvGroupVersions(ctx, syncInp) if err != nil { err := telemetry.Error(ctx, span, err, "error syncing latest env group versions") c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) return } + split := strings.Split(syncedAppEnvironment.EnvironmentGroupVersionedName, ".") + if len(split) != 2 { + err := telemetry.Error(ctx, span, err, "unexpected environment group versioned name") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) + return + } + + version, err := strconv.Atoi(split[1]) + if err != nil { + err := telemetry.Error(ctx, span, err, "error converting environment group version to int") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) + return + } + + latestEnvGroups = append(latestEnvGroups, environment_groups.EnvironmentGroup{ + Name: split[0], + Version: version, + }) + res := &UpdateAppEnvironmentResponse{ - EnvGroupName: split[0], - EnvGroupVersion: version, + EnvGroups: latestEnvGroups, } c.WriteResult(w, r, res) @@ -341,8 +346,6 @@ type syncLatestEnvGroupVersionsInput struct { 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 - // sameAppEnv is true if the app env group variables are unchanged. If true, we do not need to sync the latest version of the app env group - sameAppEnv bool // namespace is the namespace to sync the latest versions to namespace string // deploymentTargetID is the id of the deployment target @@ -352,30 +355,35 @@ type syncLatestEnvGroupVersionsInput struct { } // syncLatestEnvGroupVersions syncs the latest versions of the env groups to the namespace where an app is deployed -func syncLatestEnvGroupVersions(ctx context.Context, inp syncLatestEnvGroupVersionsInput) error { +func syncLatestEnvGroupVersions(ctx context.Context, inp syncLatestEnvGroupVersionsInput) ([]environment_groups.EnvironmentGroup, error) { ctx, span := telemetry.NewSpan(ctx, "sync-latest-env-group-versions") defer span.End() + var envGroups []environment_groups.EnvironmentGroup + if inp.deploymentTargetID == "" { - return telemetry.Error(ctx, span, nil, "deployment target id is empty") + return envGroups, telemetry.Error(ctx, span, nil, "deployment target id is empty") } if inp.appName == "" { - return telemetry.Error(ctx, span, nil, "app name is empty") + return envGroups, telemetry.Error(ctx, span, nil, "app name is empty") } if inp.appEnvName == "" { - return telemetry.Error(ctx, span, nil, "app env name is empty") + return envGroups, telemetry.Error(ctx, span, nil, "app env name is empty") } if inp.namespace == "" { - return telemetry.Error(ctx, span, nil, "namespace is empty") + return envGroups, telemetry.Error(ctx, span, nil, "namespace is empty") } if inp.k8sAgent == nil { - return telemetry.Error(ctx, span, nil, "k8s agent is nil") + return envGroups, telemetry.Error(ctx, span, nil, "k8s agent is nil") } for _, envGroup := range inp.envGroups { if envGroup == nil { continue } + if envGroup.GetName() == inp.appEnvName { + continue + } additionalEnvGroupLabels := map[string]string{ LabelKey_AppName: inp.appName, @@ -383,23 +391,30 @@ func syncLatestEnvGroupVersions(ctx context.Context, inp syncLatestEnvGroupVersi LabelKey_PorterManaged: "true", } - if envGroup.GetName() == inp.appEnvName { - if inp.sameAppEnv { - continue - } - - additionalEnvGroupLabels[environment_groups.LabelKey_DefaultAppEnvironment] = "true" - } - - _, err := environment_groups.SyncLatestVersionToNamespace(ctx, inp.k8sAgent, environment_groups.SyncLatestVersionToNamespaceInput{ + syncedEnvironment, err := environment_groups.SyncLatestVersionToNamespace(ctx, inp.k8sAgent, environment_groups.SyncLatestVersionToNamespaceInput{ TargetNamespace: inp.namespace, BaseEnvironmentGroupName: envGroup.GetName(), }, additionalEnvGroupLabels) if err != nil { telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "env-group-name", Value: envGroup.GetName()}) - return telemetry.Error(ctx, span, err, "error syncing latest version to namespace") + return envGroups, telemetry.Error(ctx, span, err, "error syncing latest version to namespace") } + + split := strings.Split(syncedEnvironment.EnvironmentGroupVersionedName, ".") + if len(split) != 2 { + return envGroups, telemetry.Error(ctx, span, err, "unexpected environment group versioned name") + } + + version, err := strconv.Atoi(split[1]) + if err != nil { + return envGroups, telemetry.Error(ctx, span, err, "error converting environment group version to int") + } + + envGroups = append(envGroups, environment_groups.EnvironmentGroup{ + Name: split[0], + Version: version, + }) } - return nil + return envGroups, nil } diff --git a/cli/cmd/v2/apply.go b/cli/cmd/v2/apply.go index 1d74a596d0..cf0a43ae1a 100644 --- a/cli/cmd/v2/apply.go +++ b/cli/cmd/v2/apply.go @@ -12,6 +12,7 @@ import ( "github.com/porter-dev/porter/api/server/handlers/porter_app" "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/cli/cli/git" @@ -78,7 +79,7 @@ func Apply(ctx context.Context, cliConf config.CLIConfig, client api.Client, por return fmt.Errorf("error calling create or update app environment group endpoint: %w", err) } - b64AppProto, err = updateAppEnvGroupInProto(ctx, b64AppProto, envGroupResp.EnvGroupName, envGroupResp.EnvGroupVersion) + b64AppProto, err = updateEnvGroupsInProto(ctx, b64AppProto, envGroupResp.EnvGroups) if err != nil { return fmt.Errorf("error updating app env group in proto: %w", err) } @@ -355,7 +356,7 @@ func imageTagFromBase64AppProto(base64AppProto string) (string, error) { return app.Image.Tag, nil } -func updateAppEnvGroupInProto(ctx context.Context, base64AppProto string, envGroupName string, envGroupVersion int) (string, error) { +func updateEnvGroupsInProto(ctx context.Context, base64AppProto string, envGroups []environment_groups.EnvironmentGroup) (string, error) { var editedB64AppProto string decoded, err := base64.StdEncoding.DecodeString(base64AppProto) @@ -369,20 +370,14 @@ func updateAppEnvGroupInProto(ctx context.Context, base64AppProto string, envGro return editedB64AppProto, fmt.Errorf("unable to unmarshal app for revision: %w", err) } - envGroupExists := false - for _, envGroup := range app.EnvGroups { - if envGroup.Name == envGroupName { - envGroup.Version = int64(envGroupVersion) - envGroupExists = true - break - } - } - if !envGroupExists { - app.EnvGroups = append(app.EnvGroups, &porterv1.EnvGroup{ - Name: envGroupName, - Version: int64(envGroupVersion), + egs := make([]*porterv1.EnvGroup, 0) + for _, envGroup := range envGroups { + egs = append(egs, &porterv1.EnvGroup{ + Name: envGroup.Name, + Version: int64(envGroup.Version), }) } + app.EnvGroups = egs marshalled, err := helpers.MarshalContractObject(ctx, app) if err != nil { diff --git a/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx b/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx index 1f7e28021a..74ea6257a3 100644 --- a/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx +++ b/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx @@ -181,25 +181,23 @@ const AppDataContainer: React.FC = ({ tabParam }) => { } ); - const updatedEnvGroup = z + const updatedEnvGroups = z .object({ - env_group_name: z.string(), - env_group_version: z.coerce.bigint(), + env_groups: z + .object({ + name: z.string(), + latest_version: z.coerce.bigint(), + }) + .array(), }) .parse(res.data); const protoWithUpdatedEnv = new PorterApp({ ...validatedAppProto, - envGroups: validatedAppProto.envGroups.map((envGroup) => { - if (envGroup.name === updatedEnvGroup.env_group_name) { - return { - ...envGroup, - version: updatedEnvGroup.env_group_version, - }; - } - - return envGroup; - }), + envGroups: updatedEnvGroups.env_groups.map((eg) => ({ + name: eg.name, + version: eg.latest_version, + })), }); await api.applyApp( diff --git a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx index 96d7bc7408..3c22e1a234 100644 --- a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx +++ b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx @@ -183,7 +183,10 @@ const CreateApp: React.FC = ({ history }) => { porterYamlFound, detectedName, loading: isLoadingPorterYaml, - } = usePorterYaml({ source: source?.type === "github" ? source : null, appName: name.value }); + } = usePorterYaml({ + source: source?.type === "github" ? source : null, + appName: name.value, + }); const deploymentTarget = useDefaultDeploymentTarget(); const { updateAppStep } = useAppAnalytics(name.value); const { validateApp } = useAppValidation({ @@ -257,7 +260,7 @@ const CreateApp: React.FC = ({ history }) => { } ); - const envGroupResponse = await api.updateEnvironmentGroupV2( + const res = await api.updateEnvironmentGroupV2( "", { deployment_target_id: deploymentTarget.deployment_target_id, @@ -272,31 +275,29 @@ const CreateApp: React.FC = ({ history }) => { } ); - const addedEnvGroup = await z + const updatedEnvGroups = z .object({ - env_group_name: z.string(), - env_group_version: z.coerce.bigint(), + env_groups: z + .object({ + name: z.string(), + latest_version: z.coerce.bigint(), + }) + .array(), }) - .parseAsync(envGroupResponse.data); + .parse(res.data); - const envGroups = [ - ...app.envGroups.filter( - (group) => group.name !== addedEnvGroup.env_group_name - ), - { - name: addedEnvGroup.env_group_name, - version: addedEnvGroup.env_group_version, - }, - ]; - const appWithSeededEnv = new PorterApp({ + const protoWithUpdatedEnv = new PorterApp({ ...app, - envGroups, + envGroups: updatedEnvGroups.env_groups.map((eg) => ({ + name: eg.name, + version: eg.latest_version, + })), }); await api.applyApp( "", { - b64_app_proto: btoa(appWithSeededEnv.toJsonString()), + b64_app_proto: btoa(protoWithUpdatedEnv.toJsonString()), deployment_target_id: deploymentTarget.deployment_target_id, }, { diff --git a/internal/kubernetes/environment_groups/list.go b/internal/kubernetes/environment_groups/list.go index 43cc222f62..cfe3eb3e54 100644 --- a/internal/kubernetes/environment_groups/list.go +++ b/internal/kubernetes/environment_groups/list.go @@ -35,7 +35,7 @@ type EnvironmentGroup struct { // SecretVariables are secret values for the EnvironmentGroup. This usually will be a Secret on the kubernetes cluster SecretVariables map[string]string `json:"secret_variables,omitempty"` // CreatedAt is only used for display purposes and is in UTC Unix time - CreatedAtUTC time.Time `json:"created_at"` + CreatedAtUTC time.Time `json:"created_at,omitempty"` } type environmentGroupOptions struct {