Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync latest version of env groups when updating app #3599

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading