Procházet zdrojové kódy

POR-1683 move creation of porter domain to apply endpoint (#3551)

ianedwards před 2 roky
rodič
revize
d0bae30014

+ 61 - 0
api/server/handlers/porter_app/apply.go

@@ -1,7 +1,10 @@
 package porter_app
 
 import (
+	"context"
 	"encoding/base64"
+	"errors"
+	"fmt"
 	"net/http"
 
 	"connectrpc.com/connect"
@@ -10,8 +13,10 @@ import (
 
 	"github.com/porter-dev/api-contracts/generated/go/helpers"
 
+	"github.com/porter-dev/porter/internal/porter_app"
 	"github.com/porter-dev/porter/internal/telemetry"
 
+	"github.com/porter-dev/porter/api/server/authz"
 	"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"
@@ -23,6 +28,7 @@ import (
 // ApplyPorterAppHandler is the handler for the /apps/parse endpoint
 type ApplyPorterAppHandler struct {
 	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
 }
 
 // NewApplyPorterAppHandler handles POST requests to the endpoint /apps/apply
@@ -33,6 +39,7 @@ func NewApplyPorterAppHandler(
 ) *ApplyPorterAppHandler {
 	return &ApplyPorterAppHandler{
 		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
 	}
 }
 
@@ -115,6 +122,28 @@ func (c *ApplyPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 			telemetry.AttributeKV{Key: "app-name", Value: appProto.Name},
 			telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetId},
 		)
+
+		agent, err := c.GetAgent(r, cluster, "")
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error getting kubernetes agent")
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+			return
+		}
+
+		subdomainCreateInput := porter_app.CreatePorterSubdomainInput{
+			AppName:             appProto.Name,
+			RootDomain:          c.Config().ServerConf.AppRootDomain,
+			PowerDNSClient:      c.Config().PowerDNSClient,
+			DNSRecordRepository: c.Repo().DNSRecord(),
+			KubernetesAgent:     agent,
+		}
+
+		appProto, err = addPorterSubdomainsIfNecessary(ctx, appProto, subdomainCreateInput)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error adding porter subdomains")
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
+			return
+		}
 	}
 
 	applyReq := connect.NewRequest(&porterv1.ApplyPorterAppRequest{
@@ -164,3 +193,35 @@ func (c *ApplyPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	c.WriteResult(w, r, response)
 }
+
+// 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) {
+	for serviceName, service := range app.Services {
+		if service.Type == porterv1.ServiceType_SERVICE_TYPE_WEB {
+			if service.GetWebConfig() == nil {
+				return app, fmt.Errorf("web service %s does not contain web config", serviceName)
+			}
+
+			webConfig := service.GetWebConfig()
+
+			if !webConfig.Private && len(webConfig.Domains) == 0 {
+				subdomain, err := porter_app.CreatePorterSubdomain(ctx, createSubdomainInput)
+				if err != nil {
+					return app, fmt.Errorf("error creating subdomain: %w", err)
+				}
+
+				if subdomain == "" {
+					return app, errors.New("response subdomain is empty")
+				}
+
+				webConfig.Domains = []*porterv1.Domain{
+					{Name: subdomain},
+				}
+
+				service.Config = &porterv1.Service_WebConfig{WebConfig: webConfig}
+			}
+		}
+	}
+
+	return app, nil
+}

+ 1 - 58
cli/cmd/v2/apply.go

@@ -90,12 +90,7 @@ func Apply(ctx context.Context, cliConf config.CLIConfig, client api.Client, por
 		return fmt.Errorf("error creating porter app db entry: %w", err)
 	}
 
-	base64AppProtoWithSubdomains, err := addPorterSubdomainsIfNecessary(ctx, client, cliConf.Project, cliConf.Cluster, base64AppProto)
-	if err != nil {
-		return fmt.Errorf("error creating subdomains: %w", err)
-	}
-
-	applyResp, err := client.ApplyPorterApp(ctx, cliConf.Project, cliConf.Cluster, base64AppProtoWithSubdomains, targetResp.DeploymentTargetID, "")
+	applyResp, err := client.ApplyPorterApp(ctx, cliConf.Project, cliConf.Cluster, base64AppProto, targetResp.DeploymentTargetID, "")
 	if err != nil {
 		return fmt.Errorf("error calling apply endpoint: %w", err)
 	}
@@ -273,58 +268,6 @@ func createPorterAppDbEntryInputFromProtoAndEnv(base64AppProto string) (api.Crea
 	return input, fmt.Errorf("app does not contain build or image settings")
 }
 
-func addPorterSubdomainsIfNecessary(ctx context.Context, client api.Client, project uint, cluster uint, base64AppProto string) (string, error) {
-	var editedB64AppProto string
-
-	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
-	if err != nil {
-		return editedB64AppProto, fmt.Errorf("unable to decode base64 app for revision: %w", err)
-	}
-
-	app := &porterv1.PorterApp{}
-	err = helpers.UnmarshalContractObject(decoded, app)
-	if err != nil {
-		return editedB64AppProto, fmt.Errorf("unable to unmarshal app for revision: %w", err)
-	}
-
-	for serviceName, service := range app.Services {
-		if service.Type == porterv1.ServiceType_SERVICE_TYPE_WEB {
-			if service.GetWebConfig() == nil {
-				return editedB64AppProto, fmt.Errorf("web service %s does not contain web config", serviceName)
-			}
-
-			webConfig := service.GetWebConfig()
-
-			if !webConfig.Private && len(webConfig.Domains) == 0 {
-				color.New(color.FgYellow).Printf("Service %s is public but does not contain any domains, creating Porter domain\n", serviceName) // nolint:errcheck,gosec
-				domain, err := client.CreateSubdomain(ctx, project, cluster, app.Name, serviceName)
-				if err != nil {
-					return editedB64AppProto, fmt.Errorf("error creating subdomain: %w", err)
-				}
-
-				if domain.Subdomain == "" {
-					return editedB64AppProto, errors.New("response subdomain is empty")
-				}
-
-				webConfig.Domains = []*porterv1.Domain{
-					{Name: domain.Subdomain},
-				}
-
-				service.Config = &porterv1.Service_WebConfig{WebConfig: webConfig}
-			}
-		}
-	}
-
-	marshalled, err := helpers.MarshalContractObject(ctx, app)
-	if err != nil {
-		return editedB64AppProto, fmt.Errorf("unable to marshal app back to json: %w", err)
-	}
-
-	editedB64AppProto = base64.StdEncoding.EncodeToString(marshalled)
-
-	return editedB64AppProto, nil
-}
-
 func buildSettingsFromBase64AppProto(base64AppProto string) (buildInput, error) {
 	var buildSettings buildInput
 

+ 77 - 0
internal/porter_app/create_subdomain.go

@@ -0,0 +1,77 @@
+package porter_app
+
+import (
+	"context"
+
+	"github.com/porter-dev/porter/internal/integrations/powerdns"
+	"github.com/porter-dev/porter/internal/kubernetes"
+	"github.com/porter-dev/porter/internal/kubernetes/domain"
+	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/telemetry"
+)
+
+// CreatePorterSubdomainInput is the input to the CreatePorterSubdomain function
+type CreatePorterSubdomainInput struct {
+	AppName             string
+	RootDomain          string
+	KubernetesAgent     *kubernetes.Agent
+	PowerDNSClient      *powerdns.Client
+	DNSRecordRepository repository.DNSRecordRepository
+}
+
+// CreatePorterSubdomain creates a subdomain for the porter app
+func CreatePorterSubdomain(ctx context.Context, input CreatePorterSubdomainInput) (string, error) {
+	ctx, span := telemetry.NewSpan(ctx, "create-porter-subdomain")
+	defer span.End()
+
+	var createdDomain string
+
+	if input.KubernetesAgent == nil {
+		return "", telemetry.Error(ctx, span, nil, "k8s agent is nil")
+	}
+	if input.PowerDNSClient == nil {
+		return "", telemetry.Error(ctx, span, nil, "powerdns client is nil")
+	}
+	if input.AppName == "" {
+		return "", telemetry.Error(ctx, span, nil, "app name is empty")
+	}
+	if input.RootDomain == "" {
+		return "", telemetry.Error(ctx, span, nil, "root domain is empty")
+	}
+
+	endpoint, found, err := domain.GetNGINXIngressServiceIP(input.KubernetesAgent.Clientset)
+	if err != nil {
+		return createdDomain, telemetry.Error(ctx, span, err, "error getting nginx ingress service ip")
+	}
+	if !found {
+		return createdDomain, telemetry.Error(ctx, span, nil, "target cluster does not have nginx ingress")
+	}
+
+	createDomainConf := domain.CreateDNSRecordConfig{
+		ReleaseName: input.AppName,
+		RootDomain:  input.RootDomain,
+		Endpoint:    endpoint,
+	}
+
+	record := createDomainConf.NewDNSRecordForEndpoint()
+
+	record, err = input.DNSRecordRepository.CreateDNSRecord(record)
+
+	if err != nil {
+		return createdDomain, telemetry.Error(ctx, span, nil, "error creating dns record")
+	}
+	if record == nil {
+		return createdDomain, telemetry.Error(ctx, span, nil, "dns record is nil")
+	}
+
+	_record := domain.DNSRecord(*record)
+
+	err = _record.CreateDomain(input.PowerDNSClient)
+	if err != nil {
+		return createdDomain, telemetry.Error(ctx, span, err, "error creating domain")
+	}
+
+	createdDomain = _record.Hostname
+
+	return createdDomain, nil
+}