Skip to content

Commit

Permalink
improve handling of missing porter.yaml in preview envs (#3843)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Oct 19, 2023
1 parent 63a6a94 commit ef8dcef
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 65 deletions.
80 changes: 63 additions & 17 deletions api/server/handlers/porter_app/update_app_environment_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ type UpdateAppEnvironmentRequest struct {

// UpdateAppEnvironmentResponse represents the fields on the response object from the /apps/{porter_app_name}/environment-group endpoint
type UpdateAppEnvironmentResponse struct {
EnvGroups []environment_groups.EnvironmentGroup `json:"env_groups"`
Base64AppProto string `json:"b64_app_proto"`
EnvGroups []environment_groups.EnvironmentGroup `json:"env_groups"`
}

// ServeHTTP updates or creates the environment group for an app
Expand Down Expand Up @@ -114,24 +115,35 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
}
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetID})

appProto := &porterv1.PorterApp{}

if request.Base64AppProto == "" {
err := telemetry.Error(ctx, span, nil, "b64 yaml is empty")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
if appName == "" {
err := telemetry.Error(ctx, span, nil, "app name is empty and no base64 proto provided")
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.Name = appName
} else {
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.StatusBadRequest))
return
}

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.StatusBadRequest))
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))
if appProto.Name == "" {
err := telemetry.Error(ctx, span, nil, "app proto name is empty")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

Expand Down Expand Up @@ -252,8 +264,25 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
Version: latestEnvironmentGroup.Version,
})

var protoEnvGroups []*porterv1.EnvGroup
for _, envGroup := range latestEnvGroups {
protoEnvGroups = append(protoEnvGroups, &porterv1.EnvGroup{
Name: envGroup.Name,
Version: int64(envGroup.Version),
})
}
appProto.EnvGroups = protoEnvGroups

encodedApp, err := encodeAppProto(ctx, appProto)
if err != nil {
err := telemetry.Error(ctx, span, err, "error encoding app proto")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

res := &UpdateAppEnvironmentResponse{
EnvGroups: latestEnvGroups,
EnvGroups: latestEnvGroups,
Base64AppProto: encodedApp,
}

c.WriteResult(w, r, res)
Expand Down Expand Up @@ -363,8 +392,25 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R
Version: version,
})

var protoEnvGroups []*porterv1.EnvGroup
for _, envGroup := range latestEnvGroups {
protoEnvGroups = append(protoEnvGroups, &porterv1.EnvGroup{
Name: envGroup.Name,
Version: int64(envGroup.Version),
})
}
appProto.EnvGroups = protoEnvGroups

encodedApp, err := encodeAppProto(ctx, appProto)
if err != nil {
err := telemetry.Error(ctx, span, err, "error encoding app proto")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

res := &UpdateAppEnvironmentResponse{
EnvGroups: latestEnvGroups,
EnvGroups: latestEnvGroups,
Base64AppProto: encodedApp,
}

c.WriteResult(w, r, res)
Expand Down
37 changes: 25 additions & 12 deletions cli/cmd/v2/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func Apply(ctx context.Context, inp ApplyInput) error {
}

// overrides incorporated into the app contract baed on the deployment target
var b64AppOverrides string
var overrides *porter_app.EncodedAppWithEnv

appName := inp.AppName
if porterYamlExists {
Expand All @@ -98,6 +98,8 @@ func Apply(ctx context.Context, inp ApplyInput) error {
}
b64AppProto = parseResp.B64AppProto

overrides = parseResp.PreviewApp

// override app name if provided
appName, err = appNameFromB64AppProto(parseResp.B64AppProto)
if err != nil {
Expand Down Expand Up @@ -128,21 +130,32 @@ func Apply(ctx context.Context, inp ApplyInput) error {
return fmt.Errorf("error updating app env group in proto: %w", err)
}

if inp.PreviewApply && parseResp.PreviewApp != nil {
b64AppOverrides = parseResp.PreviewApp.B64AppProto
color.New(color.FgGreen).Printf("Successfully parsed Porter YAML: applying app \"%s\"\n", appName) // nolint:errcheck,gosec
}

envGroupResp, err := client.CreateOrUpdateAppEnvironment(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetID, parseResp.PreviewApp.EnvVariables, parseResp.PreviewApp.EnvSecrets, parseResp.PreviewApp.B64AppProto)
if err != nil {
return fmt.Errorf("error calling create or update app environment group endpoint: %w", err)
}
// b64AppOverrides is the base64-encoded app proto with preview environment specific overrides and env groups
var b64AppOverrides string

b64AppOverrides, err = updateEnvGroupsInProto(ctx, b64AppOverrides, envGroupResp.EnvGroups)
if err != nil {
return fmt.Errorf("error updating app env group in proto: %w", err)
}
if inp.PreviewApply {
var previewEnvVariables map[string]string
var previewEnvSecrets map[string]string

if overrides != nil {
b64AppOverrides = overrides.B64AppProto
previewEnvVariables = overrides.EnvVariables
previewEnvSecrets = overrides.EnvSecrets
}

color.New(color.FgGreen).Printf("Successfully parsed Porter YAML: applying app \"%s\"\n", appName) // nolint:errcheck,gosec
envGroupResp, err := client.CreateOrUpdateAppEnvironment(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetID, previewEnvVariables, previewEnvSecrets, b64AppOverrides)
if err != nil {
return fmt.Errorf("error calling create or update app environment group endpoint: %w", err)
}
b64AppOverrides = envGroupResp.Base64AppProto

b64AppOverrides, err = updateEnvGroupsInProto(ctx, b64AppOverrides, envGroupResp.EnvGroups)
if err != nil {
return fmt.Errorf("error updating app env group in proto: %w", err)
}
}

if appName == "" {
Expand Down
84 changes: 48 additions & 36 deletions dashboard/src/lib/porter-apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,10 @@ export function applyPreviewOverrides({
overrides,
}: {
app: ClientPorterApp;
overrides: DetectedServices["previews"];
overrides?: DetectedServices["previews"];
}): ClientPorterApp {
if (!overrides) {
return app;
}

const services = app.services.map((svc) => {
const override = overrides.services.find(
const override = overrides?.services.find(
(s) => s.name.value === svc.name.value
);
if (override) {
Expand All @@ -502,24 +498,41 @@ export function applyPreviewOverrides({
});

if (ds.config.type == "web") {
ds.config.domains = [];
return {
...ds,
config: {
...ds.config,
domains: [],
},
};
}
return ds;
}

if (svc.config.type == "web") {
svc.config.domains = [];
return {
...svc,
config: {
...svc.config,
domains: [],
},
};
}

return svc;
});
const additionalServices = overrides.services
.filter((s) => !app.services.find((svc) => svc.name.value === s.name.value))
.map((svc) => deserializeService({ service: serializeService(svc) }));
const additionalServices =
overrides?.services
.filter(
(s) => !app.services.find((svc) => svc.name.value === s.name.value)
)
.map((svc) => deserializeService({ service: serializeService(svc) })) ??
[];

app.services = [...services, ...additionalServices];

if (app.predeploy) {
const predeployOverride = overrides.predeploy;
const predeployOverride = overrides?.predeploy;
if (predeployOverride) {
app.predeploy = [
deserializeService({
Expand All @@ -530,33 +543,32 @@ export function applyPreviewOverrides({
}
}

const envOverrides = overrides.variables;
if (envOverrides) {
const env = app.env.map((e) => {
const override = envOverrides[e.key];
if (override) {
return {
...e,
locked: true,
value: override,
};
}

return e;
});
const envOverrides = overrides?.variables;

const additionalEnv = Object.entries(envOverrides)
.filter(([key]) => !app.env.find((e) => e.key === key))
.map(([key, value]) => ({
key,
value,
hidden: false,
const env = app.env.map((e) => {
const override = envOverrides?.[e.key];
if (override) {
return {
...e,
locked: true,
deleted: false,
}));
value: override,
};
}

app.env = [...env, ...additionalEnv];
}
return e;
});

const additionalEnv = Object.entries(envOverrides ?? {})
.filter(([key]) => !app.env.find((e) => e.key === key))
.map(([key, value]) => ({
key,
value,
hidden: false,
locked: true,
deleted: false,
}));

app.env = [...env, ...additionalEnv];

return app;
}

0 comments on commit ef8dcef

Please sign in to comment.