Skip to content

Commit 6db0a52

Browse files
authored
CLI command for pulling variables in env group or attached to app (#4100)
1 parent a1b6b61 commit 6db0a52

File tree

11 files changed

+838
-3
lines changed

11 files changed

+838
-3
lines changed

api/client/env_groups.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/porter-dev/porter/api/server/handlers/environment_groups"
8+
)
9+
10+
// GetLatestEnvGroupVariables gets the latest environment group variables for a given environment group
11+
func (c *Client) GetLatestEnvGroupVariables(
12+
ctx context.Context,
13+
projID, clusterID uint,
14+
envGroupName string,
15+
) (*environment_groups.LatestEnvGroupVariablesResponse, error) {
16+
resp := &environment_groups.LatestEnvGroupVariablesResponse{}
17+
18+
err := c.getRequest(
19+
fmt.Sprintf("/projects/%d/clusters/%d/environment-groups/%s/latest", projID, clusterID, envGroupName),
20+
nil,
21+
resp,
22+
)
23+
24+
return resp, err
25+
}

api/client/porter_app.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,28 @@ func (c *Client) GetBuildEnv(
543543
return resp, err
544544
}
545545

546+
// GetAppEnvVariables returns all env variables for a given app
547+
func (c *Client) GetAppEnvVariables(
548+
ctx context.Context,
549+
projectID uint, clusterID uint,
550+
appName string,
551+
) (*porter_app.AppEnvVariablesResponse, error) {
552+
resp := &porter_app.AppEnvVariablesResponse{}
553+
554+
req := &porter_app.AppEnvVariablesRequest{}
555+
556+
err := c.getRequest(
557+
fmt.Sprintf(
558+
"/projects/%d/clusters/%d/apps/%s/env-variables",
559+
projectID, clusterID, appName,
560+
),
561+
req,
562+
resp,
563+
)
564+
565+
return resp, err
566+
}
567+
546568
// GetBuildFromRevision returns the build environment for a given app proto
547569
func (c *Client) GetBuildFromRevision(
548570
ctx context.Context,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package environment_groups
2+
3+
import (
4+
"net/http"
5+
6+
"connectrpc.com/connect"
7+
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
8+
"github.com/porter-dev/porter/api/server/handlers"
9+
"github.com/porter-dev/porter/api/server/shared"
10+
"github.com/porter-dev/porter/api/server/shared/apierrors"
11+
"github.com/porter-dev/porter/api/server/shared/config"
12+
"github.com/porter-dev/porter/api/server/shared/requestutils"
13+
"github.com/porter-dev/porter/api/types"
14+
"github.com/porter-dev/porter/internal/models"
15+
"github.com/porter-dev/porter/internal/telemetry"
16+
)
17+
18+
// LatestEnvGroupVariablesHandler is the handler for the /projects/{project_id}/clusters/{cluster_id}/environment-groups/{env_group_name}/latest endpoint
19+
type LatestEnvGroupVariablesHandler struct {
20+
handlers.PorterHandlerReadWriter
21+
}
22+
23+
// NewLatestEnvGroupVariablesHandler handles GET requests to /projects/{project_id}/clusters/{cluster_id}/environment-groups/{env_group_name}/latest
24+
func NewLatestEnvGroupVariablesHandler(
25+
config *config.Config,
26+
decoderValidator shared.RequestDecoderValidator,
27+
writer shared.ResultWriter,
28+
) *LatestEnvGroupVariablesHandler {
29+
return &LatestEnvGroupVariablesHandler{
30+
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
31+
}
32+
}
33+
34+
// LatestEnvGroupVariablesRequest is the request object for the /projects/{project_id}/clusters/{cluster_id}/environment-groups/{env_group_name}/latest endpoint
35+
type LatestEnvGroupVariablesRequest struct{}
36+
37+
// LatestEnvGroupVariablesResponse is the response object for the /projects/{project_id}/clusters/{cluster_id}/environment-groups/{env_group_name}/latest endpoint
38+
type LatestEnvGroupVariablesResponse struct {
39+
Variables map[string]string `json:"variables"`
40+
Secrets map[string]string `json:"secrets"`
41+
}
42+
43+
// ServeHTTP retrieves the latest env group variables from CCP and writes them to the response
44+
func (c *LatestEnvGroupVariablesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
45+
ctx, span := telemetry.NewSpan(r.Context(), "serve-get-latest-env-group-variables")
46+
defer span.End()
47+
48+
project, _ := ctx.Value(types.ProjectScope).(*models.Project)
49+
cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
50+
51+
envGroupName, reqErr := requestutils.GetURLParamString(r, types.URLParamEnvGroupName)
52+
if reqErr != nil {
53+
err := telemetry.Error(ctx, span, reqErr, "error parsing env group name from url")
54+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
55+
return
56+
}
57+
58+
latestVariablesReq := connect.NewRequest(&porterv1.LatestEnvGroupWithVariablesRequest{
59+
ProjectId: int64(project.ID),
60+
ClusterId: int64(cluster.ID),
61+
EnvGroupName: envGroupName,
62+
})
63+
64+
ccpResp, err := c.Config().ClusterControlPlaneClient.LatestEnvGroupWithVariables(ctx, latestVariablesReq)
65+
if err != nil {
66+
err := telemetry.Error(ctx, span, err, "error getting env group variables from ccp")
67+
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
68+
return
69+
}
70+
71+
if ccpResp == nil || ccpResp.Msg == nil {
72+
err := telemetry.Error(ctx, span, nil, "ccp returned nil response")
73+
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
74+
return
75+
}
76+
if ccpResp.Msg.EnvGroupVariables == nil {
77+
err := telemetry.Error(ctx, span, nil, "no env variables returned")
78+
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
79+
return
80+
}
81+
82+
res := &LatestEnvGroupVariablesResponse{
83+
Variables: ccpResp.Msg.EnvGroupVariables.Normal,
84+
Secrets: ccpResp.Msg.EnvGroupVariables.Secret,
85+
}
86+
87+
c.WriteResult(w, r, res)
88+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package porter_app
2+
3+
import (
4+
"net/http"
5+
6+
"connectrpc.com/connect"
7+
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
8+
"github.com/porter-dev/porter/api/server/handlers"
9+
"github.com/porter-dev/porter/api/server/shared"
10+
"github.com/porter-dev/porter/api/server/shared/apierrors"
11+
"github.com/porter-dev/porter/api/server/shared/config"
12+
"github.com/porter-dev/porter/api/server/shared/requestutils"
13+
"github.com/porter-dev/porter/api/types"
14+
"github.com/porter-dev/porter/internal/models"
15+
"github.com/porter-dev/porter/internal/telemetry"
16+
)
17+
18+
// AppEnvVariablesHandler is the handler for the /apps/{porter_app_name}/env-variables endpoint
19+
type AppEnvVariablesHandler struct {
20+
handlers.PorterHandlerReadWriter
21+
}
22+
23+
// NewAppEnvVariablesHandler handles GET requests to /apps/{porter_app_name}/env-variables
24+
func NewAppEnvVariablesHandler(
25+
config *config.Config,
26+
decoderValidator shared.RequestDecoderValidator,
27+
writer shared.ResultWriter,
28+
) *AppEnvVariablesHandler {
29+
return &AppEnvVariablesHandler{
30+
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
31+
}
32+
}
33+
34+
// EnvVariables is a struct containing maps of the app's env variables and secrets
35+
type EnvVariables struct {
36+
Variables map[string]string `json:"variables"`
37+
Secrets map[string]string `json:"secrets"`
38+
}
39+
40+
// AppEnvVariablesRequest is the request object for the /apps/{porter_app_name}/env-variables endpoint
41+
type AppEnvVariablesRequest struct {
42+
DeploymentTargetID string `schema:"deployment_target_id"`
43+
}
44+
45+
// AppEnvVariablesResponse is the response object for the /apps/{porter_app_name}/env-variables endpoint
46+
type AppEnvVariablesResponse struct {
47+
EnvVariables EnvVariables `json:"env_variables"`
48+
}
49+
50+
// ServeHTTP retrieves the app's env variables from CCP and writes them to the response
51+
func (c *AppEnvVariablesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
52+
ctx, span := telemetry.NewSpan(r.Context(), "serve-get-app-env-variables")
53+
defer span.End()
54+
55+
project, _ := ctx.Value(types.ProjectScope).(*models.Project)
56+
cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
57+
58+
appName, reqErr := requestutils.GetURLParamString(r, types.URLParamPorterAppName)
59+
if reqErr != nil {
60+
e := telemetry.Error(ctx, span, reqErr, "error parsing stack name from url")
61+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
62+
return
63+
}
64+
65+
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-name", Value: appName})
66+
67+
request := &LatestAppRevisionRequest{}
68+
if ok := c.DecodeAndValidate(w, r, request); !ok {
69+
err := telemetry.Error(ctx, span, nil, "error decoding request")
70+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
71+
return
72+
}
73+
74+
// optional deployment target id - if not provided, use the cluster's default
75+
deploymentTargetID := request.DeploymentTargetID
76+
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deployment-target-id", Value: deploymentTargetID})
77+
78+
var deploymentTargetIdentifer *porterv1.DeploymentTargetIdentifier
79+
if deploymentTargetID != "" {
80+
deploymentTargetIdentifer = &porterv1.DeploymentTargetIdentifier{
81+
Id: deploymentTargetID,
82+
}
83+
}
84+
85+
appEnvVariablesReq := connect.NewRequest(&porterv1.AppEnvVariablesRequest{
86+
ProjectId: int64(project.ID),
87+
ClusterId: int64(cluster.ID),
88+
AppName: appName,
89+
DeploymentTargetIdentifier: deploymentTargetIdentifer,
90+
})
91+
92+
ccpResp, err := c.Config().ClusterControlPlaneClient.AppEnvVariables(ctx, appEnvVariablesReq)
93+
if err != nil {
94+
err := telemetry.Error(ctx, span, err, "error getting app env variables from ccp")
95+
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
96+
return
97+
}
98+
99+
if ccpResp == nil || ccpResp.Msg == nil {
100+
err := telemetry.Error(ctx, span, nil, "ccp returned nil response")
101+
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
102+
return
103+
}
104+
if ccpResp.Msg.EnvVariables == nil {
105+
err := telemetry.Error(ctx, span, nil, "no env variables returned")
106+
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
107+
return
108+
}
109+
110+
envVars := EnvVariables{
111+
Variables: ccpResp.Msg.EnvVariables.Normal,
112+
Secrets: ccpResp.Msg.EnvVariables.Secret,
113+
}
114+
115+
// write the app to the response
116+
c.WriteResult(w, r, &AppEnvVariablesResponse{
117+
EnvVariables: envVars,
118+
})
119+
}

api/server/router/cluster.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,35 @@ func getClusterRoutes(
17221722
Router: r,
17231723
})
17241724

1725+
// GET /api/projects/{project_id}/clusters/{cluster_id}/environment-groups/{env_group_name}/latest
1726+
getLatestEnvironmentGroupVariablesEndpoint := factory.NewAPIEndpoint(
1727+
&types.APIRequestMetadata{
1728+
Verb: types.APIVerbGet,
1729+
Method: types.HTTPVerbGet,
1730+
Path: &types.Path{
1731+
Parent: basePath,
1732+
RelativePath: fmt.Sprintf("%s/environment-groups/{%s}/latest", relPath, types.URLParamEnvGroupName),
1733+
},
1734+
Scopes: []types.PermissionScope{
1735+
types.UserScope,
1736+
types.ProjectScope,
1737+
types.ClusterScope,
1738+
},
1739+
},
1740+
)
1741+
1742+
getLatestEnvironmentGroupVariablesHandler := environment_groups.NewLatestEnvGroupVariablesHandler(
1743+
config,
1744+
factory.GetDecoderValidator(),
1745+
factory.GetResultWriter(),
1746+
)
1747+
1748+
routes = append(routes, &router.Route{
1749+
Endpoint: getLatestEnvironmentGroupVariablesEndpoint,
1750+
Handler: getLatestEnvironmentGroupVariablesHandler,
1751+
Router: r,
1752+
})
1753+
17251754
// GET /api/projects/{project_id}/clusters/{cluster_id}/environment-groups/update-linked-apps
17261755
updateLinkedAppsEndpoint := factory.NewAPIEndpoint(
17271756
&types.APIRequestMetadata{

api/server/router/porter_app.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,35 @@ func getPorterAppRoutes(
601601
Router: r,
602602
})
603603

604+
// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/env-variables -> porter_app.AppEnvVariablesHandler
605+
appEnvVariablesEndpoint := factory.NewAPIEndpoint(
606+
&types.APIRequestMetadata{
607+
Verb: types.APIVerbGet,
608+
Method: types.HTTPVerbGet,
609+
Path: &types.Path{
610+
Parent: basePath,
611+
RelativePath: fmt.Sprintf("%s/{%s}/env-variables", relPathV2, types.URLParamPorterAppName),
612+
},
613+
Scopes: []types.PermissionScope{
614+
types.UserScope,
615+
types.ProjectScope,
616+
types.ClusterScope,
617+
},
618+
},
619+
)
620+
621+
appEnvVariablesHandler := porter_app.NewAppEnvVariablesHandler(
622+
config,
623+
factory.GetDecoderValidator(),
624+
factory.GetResultWriter(),
625+
)
626+
627+
routes = append(routes, &router.Route{
628+
Endpoint: appEnvVariablesEndpoint,
629+
Handler: appEnvVariablesHandler,
630+
Router: r,
631+
})
632+
604633
// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id}/yaml -> porter_app.NewPorterYAMLFromRevisionHandler
605634
porterYAMLFromRevision := factory.NewAPIEndpoint(
606635
&types.APIRequestMetadata{

cli/cmd/commands/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func RegisterCommands() (*cobra.Command, error) {
4848
rootCmd.AddCommand(registerCommand_Stack(cliConf))
4949
rootCmd.AddCommand(registerCommand_Update(cliConf))
5050
rootCmd.AddCommand(registerCommand_Version(cliConf))
51+
rootCmd.AddCommand(registerCommand_Env(cliConf))
5152
return rootCmd, nil
5253
}
5354

0 commit comments

Comments
 (0)