Sfoglia il codice sorgente

work to enable subdomains on the backend

Feroze Mohideen 3 anni fa
parent
commit
a39ed07c0b

+ 23 - 15
api/server/handlers/stacks/create.go

@@ -43,29 +43,35 @@ func (c *CreateStackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	stackName := request.StackName
 	namespace := fmt.Sprintf("porter-stack-%s", stackName)
-	porterYamlBase64 := request.PorterYAMLBase64
-	porterYaml, err := base64.StdEncoding.DecodeString(porterYamlBase64)
+
+	helmAgent, err := c.GetHelmAgent(r, cluster, namespace)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error decoding porter yaml: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting helm agent: %w", err)))
 		return
 	}
 
-	imageInfo := request.ImageInfo
-	chart, values, err := parse(porterYaml, imageInfo, c.Config(), cluster.ProjectID)
+	k8sAgent, err := c.GetAgent(r, cluster, namespace)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error with test: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting k8s agent: %w", err)))
 		return
 	}
 
-	helmAgent, err := c.GetHelmAgent(r, cluster, namespace)
+	porterYamlBase64 := request.PorterYAMLBase64
+	porterYaml, err := base64.StdEncoding.DecodeString(porterYamlBase64)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting helm agent: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error decoding porter yaml: %w", err)))
 		return
 	}
-
-	k8sAgent, err := c.GetAgent(r, cluster, namespace)
+	imageInfo := request.ImageInfo
+	chart, values, err := parse(porterYaml, imageInfo, c.Config(), cluster.ProjectID, SubdomainCreateOpts{
+		k8sAgent:       k8sAgent,
+		dnsRepo:        c.Repo().DNSRecord(),
+		powerDnsClient: c.Config().PowerDNSClient,
+		appRootDomain:  c.Config().ServerConf.AppRootDomain,
+		stackName:      stackName,
+	})
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting k8s agent: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error parsing porter yaml into chart and values: %w", err)))
 		return
 	}
 
@@ -94,10 +100,12 @@ func (c *CreateStackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	_, err = helmAgent.InstallChart(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-			fmt.Errorf("error installing a new chart: %s", err.Error()),
-			http.StatusBadRequest,
-		))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error deploying app: %s", err.Error())))
+
+		_, err = helmAgent.UninstallChart(stackName)
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error uninstalling chart: %w", err)))
+		}
 
 		return
 	}

+ 122 - 5
api/server/handlers/stacks/parse.go

@@ -7,8 +7,13 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/helm/loader"
+	"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/templater/utils"
 	"github.com/stefanmcshane/helm/pkg/chart"
+
 	"gopkg.in/yaml.v2"
 )
 
@@ -35,7 +40,15 @@ type App struct {
 	Type   *string                `yaml:"type" validate:"required, oneof=web worker job"`
 }
 
-func parse(porterYaml []byte, imageInfo types.ImageInfo, config *config.Config, projectID uint) (*chart.Chart, map[string]interface{}, error) {
+type SubdomainCreateOpts struct {
+	k8sAgent       *kubernetes.Agent
+	dnsRepo        repository.DNSRecordRepository
+	powerDnsClient *powerdns.Client
+	appRootDomain  string
+	stackName      string
+}
+
+func parse(porterYaml []byte, imageInfo types.ImageInfo, config *config.Config, projectID uint, opts SubdomainCreateOpts) (*chart.Chart, map[string]interface{}, error) {
 	parsed := &PorterStackYAML{}
 
 	err := yaml.Unmarshal(porterYaml, parsed)
@@ -43,21 +56,21 @@ func parse(porterYaml []byte, imageInfo types.ImageInfo, config *config.Config,
 		return nil, nil, fmt.Errorf("%s: %w", "error parsing porter.yaml", err)
 	}
 
-	values, err := buildStackValues(parsed, imageInfo)
+	values, err := buildStackValues(parsed, imageInfo, opts)
 	if err != nil {
 		return nil, nil, fmt.Errorf("%s: %w", "error building values from porter.yaml", err)
 	}
-	convertedValues := convertMap(values)
+	convertedValues := convertMap(values).(map[string]interface{})
 
 	chart, err := buildStackChart(parsed, config, projectID)
 	if err != nil {
 		return nil, nil, fmt.Errorf("%s: %w", "error building chart from porter.yaml", err)
 	}
 
-	return chart, convertedValues.(map[string]interface{}), nil
+	return chart, convertedValues, nil
 }
 
-func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo) (map[string]interface{}, error) {
+func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo, opts SubdomainCreateOpts) (map[string]interface{}, error) {
 	values := make(map[string]interface{})
 
 	for name, app := range parsed.Apps {
@@ -65,6 +78,10 @@ func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo) (map[s
 		defaultValues := getDefaultValues(app, parsed.Env, appType)
 		convertedConfig := convertMap(app.Config).(map[string]interface{})
 		helm_values := utils.DeepCoalesceValues(defaultValues, convertedConfig)
+		err := createSubdomainIfRequired(helm_values, opts) // modifies helm_values to add subdomains if necessary
+		if err != nil {
+			return nil, err
+		}
 		values[name] = helm_values
 	}
 
@@ -233,3 +250,103 @@ func CopyEnv(env map[string]string) map[string]string {
 
 	return envCopy
 }
+
+func createSubdomainIfRequired(
+	mergedValues map[string]interface{},
+	opts SubdomainCreateOpts,
+) error {
+	// look for ingress.enabled and no custom domains set
+	ingressMap, err := getNestedMap(mergedValues, "ingress")
+	if err == nil {
+		enabledVal, enabledExists := ingressMap["enabled"]
+		customDomVal, customDomExists := ingressMap["custom_domain"]
+
+		if enabledExists && customDomExists {
+			enabled, eOK := enabledVal.(bool)
+			customDomain, cOK := customDomVal.(bool)
+
+			if eOK && cOK && enabled && !customDomain {
+				// in the case of ingress enabled but no custom domain, create subdomain
+				dnsRecord, err := createDNSRecord(opts)
+				if err != nil {
+					return fmt.Errorf("error creating subdomain: %s", err.Error())
+				}
+
+				subdomain := dnsRecord.ExternalURL
+
+				if ingressVal, ok := mergedValues["ingress"]; !ok {
+					mergedValues["ingress"] = map[string]interface{}{
+						"porter_hosts": []string{
+							subdomain,
+						},
+					}
+				} else {
+					ingressValMap := ingressVal.(map[string]interface{})
+
+					ingressValMap["porter_hosts"] = []string{
+						subdomain,
+					}
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+func createDNSRecord(opts SubdomainCreateOpts) (*types.DNSRecord, error) {
+	endpoint, found, err := domain.GetNGINXIngressServiceIP(opts.k8sAgent.Clientset)
+	if err != nil {
+		return nil, err
+	}
+	if !found {
+		return nil, fmt.Errorf("target cluster does not have nginx ingress")
+	}
+
+	createDomain := domain.CreateDNSRecordConfig{
+		ReleaseName: opts.stackName,
+		RootDomain:  opts.appRootDomain,
+		Endpoint:    endpoint,
+	}
+
+	record := createDomain.NewDNSRecordForEndpoint()
+
+	record, err = opts.dnsRepo.CreateDNSRecord(record)
+
+	if err != nil {
+		return nil, err
+	}
+
+	_record := domain.DNSRecord(*record)
+
+	err = _record.CreateDomain(opts.powerDnsClient)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return record.ToDNSRecordType(), nil
+}
+
+func getNestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, error) {
+	var res map[string]interface{}
+	curr := obj
+
+	for _, field := range fields {
+		objField, ok := curr[field]
+
+		if !ok {
+			return nil, fmt.Errorf("%s not found", field)
+		}
+
+		res, ok = objField.(map[string]interface{})
+
+		if !ok {
+			return nil, fmt.Errorf("%s is not a nested object", field)
+		}
+
+		curr = res
+	}
+
+	return res, nil
+}

+ 20 - 8
api/server/handlers/stacks/update.go

@@ -43,23 +43,35 @@ func (c *UpdateStackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	stackName := request.StackName
 	namespace := fmt.Sprintf("porter-stack-%s", stackName)
-	porterYamlBase64 := request.PorterYAMLBase64
-	porterYaml, err := base64.StdEncoding.DecodeString(porterYamlBase64)
+
+	helmAgent, err := c.GetHelmAgent(r, cluster, namespace)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error decoding porter yaml: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting helm agent: %w", err)))
 		return
 	}
 
-	imageInfo := request.ImageInfo
-	chart, values, err := parse(porterYaml, imageInfo, c.Config(), cluster.ProjectID)
+	k8sAgent, err := c.GetAgent(r, cluster, namespace)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error with test: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting k8s agent: %w", err)))
 		return
 	}
 
-	helmAgent, err := c.GetHelmAgent(r, cluster, namespace)
+	porterYamlBase64 := request.PorterYAMLBase64
+	porterYaml, err := base64.StdEncoding.DecodeString(porterYamlBase64)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting helm agent: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error decoding porter yaml: %w", err)))
+		return
+	}
+	imageInfo := request.ImageInfo
+	chart, values, err := parse(porterYaml, imageInfo, c.Config(), cluster.ProjectID, SubdomainCreateOpts{
+		k8sAgent:       k8sAgent,
+		dnsRepo:        c.Repo().DNSRecord(),
+		powerDnsClient: c.Config().PowerDNSClient,
+		appRootDomain:  c.Config().ServerConf.AppRootDomain,
+		stackName:      stackName,
+	})
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error parsing porter yaml into chart and values: %w", err)))
 		return
 	}
 

+ 15 - 13
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -200,7 +200,21 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         }
         : {};
 
-      // write to the db
+      // create the dummy chart
+      await api.createPorterStack(
+        "<token>",
+        {
+          stack_name: formState.applicationName,
+          porter_yaml: base64Encoded,
+          ...imageInfo,
+        },
+        {
+          cluster_id: currentCluster.id,
+          project_id: currentProject.id,
+        }
+      );
+
+      // if success, write to the db
       await api.createPorterApp(
         "<token>",
         {
@@ -220,18 +234,6 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         }
       );
 
-      await api.createPorterStack(
-        "<token>",
-        {
-          stack_name: formState.applicationName,
-          porter_yaml: base64Encoded,
-          ...imageInfo,
-        },
-        {
-          cluster_id: currentCluster.id,
-          project_id: currentProject.id,
-        }
-      );
       if (!actionConfig?.git_repo) {
         props.history.push(`/apps/${formState.applicationName}`);
       }

+ 1 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -328,7 +328,7 @@ export const Service = {
             //     throw new Error('Failed to create subdomain for web service');
             // }
             // ingress.porter_hosts.push(res.data.external_url)
-            throw new Error('Generating external URLs without custom subdomains not yet supported!');
+            //throw new Error('Generating external URLs without custom subdomains not yet supported!');
         }
 
         return ingress;