Skip to content

Commit

Permalink
sync latest version of env groups when updating app (#3599)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Sep 20, 2023
1 parent 75753d0 commit 11eeb53
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 28 deletions.
2 changes: 2 additions & 0 deletions api/client/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ func (c *Client) CreateOrUpdateAppEnvironment(
deploymentTargetID string,
variables map[string]string,
secrets map[string]string,
Base64AppProto string,
) (*porter_app.UpdateAppEnvironmentResponse, error) {
resp := &porter_app.UpdateAppEnvironmentResponse{}

Expand All @@ -450,6 +451,7 @@ func (c *Client) CreateOrUpdateAppEnvironment(
Variables: variables,
Secrets: secrets,
HardUpdate: false,
Base64AppProto: Base64AppProto,
}

err := c.postRequest(
Expand Down
9 changes: 5 additions & 4 deletions api/server/handlers/porter_app/get_build_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ func (c *GetBuildEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

envFromProtoInp := porter_app.AppEnvironmentFromProtoInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
App: appProto,
K8SAgent: agent,
ProjectID: project.ID,
ClusterID: int(cluster.ID),
App: appProto,
K8SAgent: agent,
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
}
envGroups, err := porter_app.AppEnvironmentFromProto(ctx, envFromProtoInp)
if err != nil {
Expand Down
137 changes: 133 additions & 4 deletions api/server/handlers/porter_app/update_app_environment_group.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package porter_app

import (
"context"
"encoding/base64"
"net/http"
"strconv"
"strings"
"time"

"github.com/porter-dev/porter/internal/kubernetes"
"github.com/porter-dev/porter/internal/porter_app"

"github.com/porter-dev/porter/api/server/shared/requestutils"
"github.com/porter-dev/porter/internal/kubernetes/environment_groups"

"connectrpc.com/connect"

"github.com/porter-dev/api-contracts/generated/go/helpers"
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"

"github.com/porter-dev/porter/api/server/authz"
Expand Down Expand Up @@ -54,6 +58,7 @@ const (

// UpdateAppEnvironmentRequest represents the accepted fields on a request to the /apps/{porter_app_name}/environment-group endpoint
type UpdateAppEnvironmentRequest struct {
Base64AppProto string `json:"b64_app_proto"`
DeploymentTargetID string `json:"deployment_target_id"`
Variables map[string]string `json:"variables"`
Secrets map[string]string `json:"secrets"`
Expand Down Expand Up @@ -110,6 +115,27 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
}
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetID})

if request.Base64AppProto == "" {
err := telemetry.Error(ctx, span, nil, "b64 yaml is empty")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

decoded, err := base64.StdEncoding.DecodeString(request.Base64AppProto)
if err != nil {
err := telemetry.Error(ctx, span, err, "error decoding base yaml")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

appProto := &porterv1.PorterApp{}
err = helpers.UnmarshalContractObject(decoded, appProto)
if err != nil {
err := telemetry.Error(ctx, span, err, "error unmarshalling app proto")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

deploymentTargetDetailsReq := connect.NewRequest(&porterv1.DeploymentTargetDetailsRequest{
ProjectId: int64(project.ID),
DeploymentTargetId: request.DeploymentTargetID,
Expand Down Expand Up @@ -139,7 +165,7 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R

telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "hard-update", Value: request.HardUpdate})

envGroupName, err := porter_app.AppEnvGroupName(ctx, appName, request.DeploymentTargetID, cluster.ID, c.Repo().PorterApp())
appEnvGroupName, err := porter_app.AppEnvGroupName(ctx, appName, request.DeploymentTargetID, cluster.ID, c.Repo().PorterApp())
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting app env group name")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
Expand All @@ -153,7 +179,7 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
return
}

latestEnvironmentGroup, err := environment_groups.LatestBaseEnvironmentGroup(ctx, agent, envGroupName)
latestEnvironmentGroup, err := environment_groups.LatestBaseEnvironmentGroup(ctx, agent, appEnvGroupName)
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))
Expand Down Expand Up @@ -190,6 +216,23 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "same-env-group", Value: sameEnvGroup})

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,
appName: appName,
appEnvName: appEnvGroupName,
sameAppEnv: true,
namespace: namespace,
deploymentTargetID: request.DeploymentTargetID,
k8sAgent: agent,
}
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
}

res := &UpdateAppEnvironmentResponse{
EnvGroupName: latestEnvironmentGroup.Name,
EnvGroupVersion: latestEnvironmentGroup.Version,
Expand Down Expand Up @@ -220,7 +263,7 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
}

envGroup := environment_groups.EnvironmentGroup{
Name: envGroupName,
Name: appEnvGroupName,
Variables: variables,
SecretVariables: secrets,
CreatedAtUTC: time.Now().UTC(),
Expand All @@ -241,7 +284,7 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
}

inp := environment_groups.SyncLatestVersionToNamespaceInput{
BaseEnvironmentGroupName: envGroupName,
BaseEnvironmentGroupName: appEnvGroupName,
TargetNamespace: namespace,
}

Expand All @@ -267,10 +310,96 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
return
}

syncInp := syncLatestEnvGroupVersionsInput{
envGroups: appProto.EnvGroups,
appName: appName,
appEnvName: appEnvGroupName,
sameAppEnv: false,
namespace: namespace,
deploymentTargetID: request.DeploymentTargetID,
k8sAgent: agent,
}
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
}

res := &UpdateAppEnvironmentResponse{
EnvGroupName: split[0],
EnvGroupVersion: version,
}

c.WriteResult(w, r, res)
}

type syncLatestEnvGroupVersionsInput struct {
// envGroups is the list of env groups to sync. We only need the names and will get the latest version of each from the porter-env-group ns
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
// 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
deploymentTargetID string
// k8sAgent is the kubernetes agent
k8sAgent *kubernetes.Agent
}

// 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 {
ctx, span := telemetry.NewSpan(ctx, "sync-latest-env-group-versions")
defer span.End()

if inp.deploymentTargetID == "" {
return telemetry.Error(ctx, span, nil, "deployment target id is empty")
}
if inp.appName == "" {
return telemetry.Error(ctx, span, nil, "app name is empty")
}
if inp.appEnvName == "" {
return telemetry.Error(ctx, span, nil, "app env name is empty")
}
if inp.namespace == "" {
return telemetry.Error(ctx, span, nil, "namespace is empty")
}
if inp.k8sAgent == nil {
return telemetry.Error(ctx, span, nil, "k8s agent is nil")
}

for _, envGroup := range inp.envGroups {
if envGroup == nil {
continue
}

additionalEnvGroupLabels := map[string]string{
LabelKey_AppName: inp.appName,
LabelKey_DeploymentTargetID: inp.deploymentTargetID,
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{
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 nil
}
2 changes: 1 addition & 1 deletion cli/cmd/v2/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func Apply(ctx context.Context, cliConf config.CLIConfig, client api.Client, por
return fmt.Errorf("error getting app name from b64 app proto: %w", err)
}

envGroupResp, err := client.CreateOrUpdateAppEnvironment(ctx, cliConf.Project, cliConf.Cluster, appName, targetResp.DeploymentTargetID, parseResp.EnvVariables, parseResp.EnvSecrets)
envGroupResp, err := client.CreateOrUpdateAppEnvironment(ctx, cliConf.Project, cliConf.Cluster, appName, targetResp.DeploymentTargetID, parseResp.EnvVariables, parseResp.EnvSecrets, parseResp.B64AppProto)
if err != nil {
return fmt.Errorf("error calling create or update app environment group endpoint: %w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,17 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
);

// updates the default env group associated with this app to store app specific env vars
const res = await api.updateAppEnvironmentGroup(
const res = await api.updateEnvironmentGroupV2(
"<token>",
{
deployment_target_id: deploymentTargetId,
variables,
secrets,
b64_app_proto: btoa(validatedAppProto.toJsonString()),
remove_missing: true,
},
{
project_id: projectId,
id: projectId,
cluster_id: clusterId,
app_name: porterApp.name,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
{
deployment_target_id: deploymentTarget.deployment_target_id,
variables: variables,
b64_app_proto: btoa(app.toJsonString()),
secrets: secrets,
},
{
Expand Down
18 changes: 1 addition & 17 deletions dashboard/src/shared/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -914,22 +914,6 @@ const createApp = baseApi<
return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/create`;
});

const updateAppEnvironmentGroup = baseApi<
{
deployment_target_id: string;
variables: Record<string, string>;
secrets: Record<string, string>;
remove_missing: boolean;
},
{
project_id: number;
cluster_id: number;
app_name: string;
}
>("POST", (pathParams) => {
return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/${pathParams.app_name}/update-environment`;
});

const applyApp = baseApi<
{
deployment_target_id: string;
Expand Down Expand Up @@ -1852,6 +1836,7 @@ const updateEnvironmentGroupV2 = baseApi<
deployment_target_id: string;
variables: Record<string, string>;
secrets: Record<string, string>;
b64_app_proto: string;
remove_missing?: boolean;
},
{
Expand Down Expand Up @@ -3108,7 +3093,6 @@ export default {
getBranchHead,
validatePorterApp,
createApp,
updateAppEnvironmentGroup,
applyApp,
getAttachedEnvGroups,
getLatestRevision,
Expand Down
1 change: 1 addition & 0 deletions internal/porter_app/v2/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type PorterYAML struct {
Env map[string]string `yaml:"env"`

Predeploy *Service `yaml:"predeploy"`
EnvGroups []string `yaml:"env_groups,omitempty"`
}

// Build represents the build settings for a Porter app
Expand Down

0 comments on commit 11eeb53

Please sign in to comment.