Skip to content

Commit

Permalink
POR-1592 support branch specific deploys from cli (#3634)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Sep 25, 2023
1 parent fd170f2 commit 7c54d20
Show file tree
Hide file tree
Showing 19 changed files with 588 additions and 141 deletions.
34 changes: 34 additions & 0 deletions api/client/deployment_target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package client

import (
"context"
"fmt"

"github.com/porter-dev/porter/api/server/handlers/deployment_target"
)

// CreateDeploymentTarget creates a new deployment target for a given project and cluster with the provided name
func (c *Client) CreateDeploymentTarget(
ctx context.Context,
projectID, clusterID uint,
selector string,
preview bool,
) (*deployment_target.CreateDeploymentTargetResponse, error) {
resp := &deployment_target.CreateDeploymentTargetResponse{}

req := &deployment_target.CreateDeploymentTargetRequest{
Selector: selector,
Preview: preview,
}

err := c.postRequest(
fmt.Sprintf(
"/projects/%d/clusters/%d/deployment-targets",
projectID, clusterID,
),
req,
resp,
)

return resp, err
}
115 changes: 115 additions & 0 deletions api/server/handlers/deployment_target/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package deployment_target

import (
"net/http"

"github.com/google/uuid"
"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
"github.com/porter-dev/porter/api/server/shared/apierrors"
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/telemetry"
)

// CreateDeploymentTargetHandler is the handler for the /deployment-targets endpoint
type CreateDeploymentTargetHandler struct {
handlers.PorterHandlerReadWriter
}

// NewCreateDeploymentTargetHandler handles POST requests to the endpoint /deployment-targets
func NewCreateDeploymentTargetHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *CreateDeploymentTargetHandler {
return &CreateDeploymentTargetHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
}
}

// CreateDeploymentTargetRequest is the request object for the /deployment-targets POST endpoint
type CreateDeploymentTargetRequest struct {
Selector string `json:"selector"`
Preview bool `json:"preview"`
}

// CreateDeploymentTargetResponse is the response object for the /deployment-targets POST endpoint
type CreateDeploymentTargetResponse struct {
DeploymentTargetID string `json:"deployment_target_id"`
}

// ServeHTTP handles POST requests to create a new deployment target
func (c *CreateDeploymentTargetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-create-deployment-target")
defer span.End()

project, _ := ctx.Value(types.ProjectScope).(*models.Project)
cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)

if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
return
}

request := &CreateDeploymentTargetRequest{}
if ok := c.DecodeAndValidate(w, r, request); !ok {
err := telemetry.Error(ctx, span, nil, "error decoding request")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
if request.Selector == "" {
err := telemetry.Error(ctx, span, nil, "name is required")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

var res *CreateDeploymentTargetResponse

existingDeploymentTarget, err := c.Repo().DeploymentTarget().DeploymentTargetBySelectorAndSelectorType(
project.ID,
cluster.ID,
request.Selector,
string(models.DeploymentTargetSelectorType_Namespace),
)
if err != nil {
err := telemetry.Error(ctx, span, err, "error checking for existing deployment target")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

if existingDeploymentTarget.ID != uuid.Nil {
res = &CreateDeploymentTargetResponse{
DeploymentTargetID: existingDeploymentTarget.ID.String(),
}
c.WriteResult(w, r, res)
return
}

deploymentTarget := &models.DeploymentTarget{
ProjectID: int(project.ID),
ClusterID: int(cluster.ID),
Selector: request.Selector,
SelectorType: models.DeploymentTargetSelectorType_Namespace,
Preview: request.Preview,
}
deploymentTarget, err = c.Repo().DeploymentTarget().CreateDeploymentTarget(deploymentTarget)
if err != nil {
err := telemetry.Error(ctx, span, err, "error creating deployment target")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}
if deploymentTarget.ID == uuid.Nil {
err := telemetry.Error(ctx, span, nil, "deployment target id is nil")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

res = &CreateDeploymentTargetResponse{
DeploymentTargetID: deploymentTarget.ID.String(),
}

c.WriteResult(w, r, res)
}
21 changes: 19 additions & 2 deletions api/server/handlers/porter_app/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

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

"github.com/porter-dev/porter/internal/deployment_target"
"github.com/porter-dev/porter/internal/porter_app"
"github.com/porter-dev/porter/internal/telemetry"

Expand Down Expand Up @@ -124,6 +125,18 @@ func (c *ApplyPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetId},
)

deploymentTargetDetails, err := deployment_target.DeploymentTargetDetails(ctx, deployment_target.DeploymentTargetDetailsInput{
ProjectID: int64(project.ID),
ClusterID: int64(cluster.ID),
DeploymentTargetID: deploymentTargetID,
CCPClient: c.Config().ClusterControlPlaneClient,
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting deployment target details")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

agent, err := c.GetAgent(r, cluster, "")
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting kubernetes agent")
Expand All @@ -139,7 +152,7 @@ func (c *ApplyPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
KubernetesAgent: agent,
}

appProto, err = addPorterSubdomainsIfNecessary(ctx, appProto, subdomainCreateInput)
appProto, err = addPorterSubdomainsIfNecessary(ctx, appProto, deploymentTargetDetails, subdomainCreateInput)
if err != nil {
err := telemetry.Error(ctx, span, err, "error adding porter subdomains")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
Expand Down Expand Up @@ -197,7 +210,7 @@ func (c *ApplyPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
}

// addPorterSubdomainsIfNecessary adds porter subdomains to the app proto if a web service is changed to private and has no domains
func addPorterSubdomainsIfNecessary(ctx context.Context, app *porterv1.PorterApp, createSubdomainInput porter_app.CreatePorterSubdomainInput) (*porterv1.PorterApp, error) {
func addPorterSubdomainsIfNecessary(ctx context.Context, app *porterv1.PorterApp, deploymentTarget deployment_target.DeploymentTarget, createSubdomainInput porter_app.CreatePorterSubdomainInput) (*porterv1.PorterApp, error) {
for serviceName, service := range app.Services {
if service.Type == porterv1.ServiceType_SERVICE_TYPE_WEB {
if service.GetWebConfig() == nil {
Expand All @@ -207,6 +220,10 @@ func addPorterSubdomainsIfNecessary(ctx context.Context, app *porterv1.PorterApp
webConfig := service.GetWebConfig()

if !webConfig.GetPrivate() && len(webConfig.Domains) == 0 {
if deploymentTarget.Namespace != DeploymentTargetSelector_Default {
createSubdomainInput.AppName = fmt.Sprintf("%s-%s", createSubdomainInput.AppName, deploymentTarget.Namespace)
}

subdomain, err := porter_app.CreatePorterSubdomain(ctx, createSubdomainInput)
if err != nil {
return app, fmt.Errorf("error creating subdomain: %w", err)
Expand Down
36 changes: 24 additions & 12 deletions api/server/handlers/porter_app/get_app_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/server/shared/requestutils"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/deployment_target"
"github.com/porter-dev/porter/internal/kubernetes/environment_groups"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/porter_app"
Expand Down Expand Up @@ -124,12 +125,24 @@ func (c *GetAppEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

deploymentTarget, err := deployment_target.DeploymentTargetDetails(ctx, deployment_target.DeploymentTargetDetailsInput{
ProjectID: int64(project.ID),
ClusterID: int64(cluster.ID),
DeploymentTargetID: revision.DeploymentTargetID,
CCPClient: c.Config().ClusterControlPlaneClient,
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting deployment target details")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

envFromProtoInp := porter_app.AppEnvironmentFromProtoInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
App: appProto,
K8SAgent: agent,
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
ProjectID: project.ID,
ClusterID: int(cluster.ID),
DeploymentTarget: deploymentTarget,
App: appProto,
K8SAgent: agent,
}

envGroups, err := porter_app.AppEnvironmentFromProto(ctx, envFromProtoInp, porter_app.WithEnvGroupFilter(request.EnvGroups), porter_app.WithSecrets(), porter_app.WithoutDefaultAppEnvGroups())
Expand All @@ -140,13 +153,12 @@ func (c *GetAppEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

revisionWithEnv, err := porter_app.AttachEnvToRevision(ctx, porter_app.AttachEnvToRevisionInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
DeploymentTargetID: revision.DeploymentTargetID,
Revision: revision,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
ProjectID: project.ID,
ClusterID: int(cluster.ID),
Revision: revision,
DeploymentTarget: deploymentTarget,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error attaching env to revision")
Expand Down
26 changes: 19 additions & 7 deletions api/server/handlers/porter_app/get_app_revision.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/server/shared/requestutils"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/deployment_target"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/porter_app"
"github.com/porter-dev/porter/internal/telemetry"
Expand Down Expand Up @@ -86,14 +87,25 @@ func (c *GetAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
return
}

deploymentTarget, err := deployment_target.DeploymentTargetDetails(ctx, deployment_target.DeploymentTargetDetailsInput{
ProjectID: int64(project.ID),
ClusterID: int64(cluster.ID),
DeploymentTargetID: ccpResp.Msg.AppRevision.DeploymentTargetId,
CCPClient: c.Config().ClusterControlPlaneClient,
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting deployment target details")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

revisionWithEnv, err := porter_app.AttachEnvToRevision(ctx, porter_app.AttachEnvToRevisionInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
Revision: encodedRevision,
DeploymentTargetID: ccpResp.Msg.AppRevision.DeploymentTargetId,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
ProjectID: project.ID,
ClusterID: int(cluster.ID),
Revision: encodedRevision,
DeploymentTarget: deploymentTarget,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error attaching env to revision")
Expand Down
23 changes: 18 additions & 5 deletions api/server/handlers/porter_app/get_build_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/server/shared/requestutils"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/deployment_target"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/porter_app"
"github.com/porter-dev/porter/internal/telemetry"
Expand Down Expand Up @@ -114,12 +115,24 @@ func (c *GetBuildEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

deploymentTarget, err := deployment_target.DeploymentTargetDetails(ctx, deployment_target.DeploymentTargetDetailsInput{
ProjectID: int64(project.ID),
ClusterID: int64(cluster.ID),
DeploymentTargetID: revision.DeploymentTargetID,
CCPClient: c.Config().ClusterControlPlaneClient,
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting deployment target details")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

envFromProtoInp := porter_app.AppEnvironmentFromProtoInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
App: appProto,
K8SAgent: agent,
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
ProjectID: project.ID,
ClusterID: int(cluster.ID),
DeploymentTarget: deploymentTarget,
App: appProto,
K8SAgent: agent,
}
envGroups, err := porter_app.AppEnvironmentFromProto(ctx, envFromProtoInp)
if err != nil {
Expand Down
27 changes: 8 additions & 19 deletions api/server/handlers/porter_app/latest_app_revisions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"connectrpc.com/connect"
"github.com/google/uuid"
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
Expand Down Expand Up @@ -52,34 +53,22 @@ func (c *LatestAppRevisionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)

deploymentTargets, err := c.Repo().DeploymentTarget().List(project.ID)
// todo(ianedwards): once we have a way to select a deployment target, we can add it to the request
defaultDeploymentTarget, err := c.Repo().DeploymentTarget().DeploymentTargetBySelectorAndSelectorType(project.ID, cluster.ID, DeploymentTargetSelector_Default, DeploymentTargetSelectorType_Default)
if err != nil {
err = telemetry.Error(ctx, span, err, "error reading deployment targets")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

if len(deploymentTargets) == 0 {
res := &LatestAppRevisionsResponse{
AppRevisions: []LatestRevisionWithSource{},
}

c.WriteResult(w, r, res)
err := telemetry.Error(ctx, span, err, "error getting default deployment target from repo")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

if len(deploymentTargets) > 1 {
err = telemetry.Error(ctx, span, err, "more than one deployment target found")
if defaultDeploymentTarget.ID == uuid.Nil {
err := telemetry.Error(ctx, span, err, "default deployment target not found")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

// todo(ianedwards): once we have a way to select a deployment target, we can add it to the request
deploymentTarget := deploymentTargets[0]

listAppRevisionsReq := connect.NewRequest(&porterv1.LatestAppRevisionsRequest{
ProjectId: int64(project.ID),
DeploymentTargetId: deploymentTarget.ID.String(),
DeploymentTargetId: defaultDeploymentTarget.ID.String(),
})

latestAppRevisionsResp, err := c.Config().ClusterControlPlaneClient.LatestAppRevisions(ctx, listAppRevisionsReq)
Expand Down
Loading

0 comments on commit 7c54d20

Please sign in to comment.