Przeglądaj źródła

Support a release job in stacks (#3005)

Feroze Mohideen 3 lat temu
rodzic
commit
68860d4e84

+ 13 - 6
api/server/handlers/release/create.go

@@ -115,6 +115,19 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	k8sAgent, err := c.GetAgent(r, cluster, "")
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting k8s agent: %w", err)))
+		return
+	}
+
+	// create the namespace if it does not exist already
+	_, err = k8sAgent.CreateNamespace(namespace, nil)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating namespace: %w", err)))
+		return
+	}
+
 	conf := &helm.InstallChartConfig{
 		Chart:      chart,
 		Name:       request.Name,
@@ -135,12 +148,6 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	k8sAgent, err := c.GetAgent(r, cluster, "")
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting k8s agent: %w", err)))
-		return
-	}
-
 	configMaps := make([]*v1.ConfigMap, 0)
 
 	if request.SyncedEnvGroups != nil && len(request.SyncedEnvGroups) > 0 {

+ 11 - 2
api/server/handlers/stacks/parse.go

@@ -22,7 +22,7 @@ type PorterStackYAML struct {
 	Build   *Build            `yaml:"build"`
 	Env     map[string]string `yaml:"env"`
 	Apps    map[string]*App   `yaml:"apps"`
-	Release *string           `yaml:"release"`
+	Release *App              `yaml:"release"`
 }
 
 type Build struct {
@@ -37,7 +37,7 @@ type Build struct {
 type App struct {
 	Run    *string                `yaml:"run" validate:"required"`
 	Config map[string]interface{} `yaml:"config"`
-	Type   *string                `yaml:"type" validate:"required, oneof=web worker job"`
+	Type   *string                `yaml:"type" validate:"oneof=web worker job"`
 }
 
 type SubdomainCreateOpts struct {
@@ -107,6 +107,15 @@ func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo, existi
 			return nil, err
 		}
 
+		// just in case this slips by
+		if appType == "web" {
+			if helm_values["ingress"] == nil {
+				helm_values["ingress"] = map[string]interface{}{
+					"enabled": false,
+				}
+			}
+		}
+
 		values[helmName] = helm_values
 	}
 

+ 1 - 1
api/server/shared/config/env/envconfs.go

@@ -23,7 +23,7 @@ type ServerConf struct {
 	CookieInsecure       bool          `env:"COOKIE_INSECURE,default=false"`
 	TokenGeneratorSecret string        `env:"TOKEN_GENERATOR_SECRET,default=secret"`
 	TimeoutRead          time.Duration `env:"SERVER_TIMEOUT_READ,default=5s"`
-	TimeoutWrite         time.Duration `env:"SERVER_TIMEOUT_WRITE,default=30s"`
+	TimeoutWrite         time.Duration `env:"SERVER_TIMEOUT_WRITE,default=60s"`
 	TimeoutIdle          time.Duration `env:"SERVER_TIMEOUT_IDLE,default=15s"`
 	IsLocal              bool          `env:"IS_LOCAL,default=false"`
 	IsTesting            bool          `env:"IS_TESTING,default=false"`

+ 21 - 1
cli/cmd/stack/apply.go

@@ -58,6 +58,26 @@ func CreateV1BuildResources(client *api.Client, raw []byte, stackName string, pr
 
 	v1File.Resources = append(v1File.Resources, bi, pi)
 
+	release, cmd, err := createReleaseResource(client,
+		stackConf.parsed.Release,
+		stackConf.stackName,
+		bi.Name,
+		pi.Name,
+		stackConf.projectID,
+		stackConf.clusterID,
+		stackConf.parsed.Env,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	if release != nil {
+		color.New(color.FgYellow).Printf("Found release command to run before deploying apps: %s \n", cmd)
+		v1File.Resources = append(v1File.Resources, release)
+	} else {
+		color.New(color.FgYellow).Printf("No release command found in porter.yaml or helm. \n")
+	}
+
 	return v1File, nil
 }
 
@@ -218,7 +238,7 @@ func getEnvFromRelease(client *api.Client, stackName string, projectID uint, clu
 									if normal, ok := envMap["normal"]; ok {
 										if normalMap, ok := normal.(map[string]interface{}); ok {
 											convertedMap, err := toStringMap(normalMap)
-											if err == nil {
+											if err == nil && len(convertedMap) > 0 {
 												envVarsStringMap = convertedMap
 												break
 											}

+ 109 - 0
cli/cmd/stack/release.go

@@ -0,0 +1,109 @@
+package stack
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/mitchellh/mapstructure"
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/cli/cmd/deploy"
+	"github.com/porter-dev/porter/internal/integrations/preview"
+
+	switchboardTypes "github.com/porter-dev/switchboard/pkg/types"
+)
+
+func createReleaseResource(client *api.Client, release *App, stackName, buildResourceName, pushResourceName string, projectID, clusterID uint, env map[string]string) (*switchboardTypes.Resource, string, error) {
+	var finalCmd string
+	releaseCmd := getReleaseCommandFromRelease(client, stackName, projectID, clusterID)
+	if release == nil && releaseCmd == "" {
+		return nil, "", nil
+	} else if release != nil && release.Run != nil {
+		finalCmd = *release.Run
+	} else {
+		finalCmd = releaseCmd
+	}
+
+	config := &preview.ApplicationConfig{}
+
+	config.Build.Method = "registry"
+	config.Build.Image = fmt.Sprintf("{ .%s.image }", buildResourceName)
+	config.Build.Env = CopyEnv(env)
+	config.WaitForJob = true
+
+	helm_values := make(map[string]interface{})
+	if release != nil && release.Config != nil {
+		helm_values = release.Config
+	}
+	helm_values["container"] = map[string]interface{}{
+		"command": finalCmd,
+		"env":     CopyEnv(env),
+	}
+	helm_values["paused"] = false
+	config.Values = convertMap(helm_values).(map[string]interface{})
+
+	rawConfig := make(map[string]any)
+	err := mapstructure.Decode(config, &rawConfig)
+	if err != nil {
+		return nil, "", fmt.Errorf("could not decode config for release: %w", err)
+	}
+
+	return &switchboardTypes.Resource{
+		Name:      fmt.Sprintf("%s-r", stackName),
+		DependsOn: []string{"get-env", buildResourceName, pushResourceName},
+		Source: map[string]any{
+			"name": "job",
+		},
+		Target: map[string]any{
+			"app_name":  fmt.Sprintf("%s-r", stackName),
+			"namespace": fmt.Sprintf("porter-stack-%s", stackName),
+		},
+		Config: rawConfig,
+	}, finalCmd, nil
+}
+
+func getReleaseCommandFromRelease(client *api.Client, stackName string, projectID uint, clusterID uint) string {
+	namespace := fmt.Sprintf("porter-stack-%s", stackName)
+	releaseName := fmt.Sprintf("%s-r", stackName)
+	release, err := client.GetRelease(
+		context.Background(),
+		projectID,
+		clusterID,
+		namespace,
+		releaseName,
+	)
+
+	if err != nil || release == nil || release.Config == nil {
+		return ""
+	}
+
+	containerMap, err := deploy.GetNestedMap(release.Config, "container")
+	if err == nil {
+		if command, ok := containerMap["command"]; ok {
+			if commandString, ok := command.(string); ok {
+				return commandString
+			}
+		}
+	}
+
+	return ""
+}
+
+func convertMap(m interface{}) interface{} {
+	switch m := m.(type) {
+	case map[string]interface{}:
+		for k, v := range m {
+			m[k] = convertMap(v)
+		}
+	case map[interface{}]interface{}:
+		result := map[string]interface{}{}
+		for k, v := range m {
+			result[k.(string)] = convertMap(v)
+		}
+		return result
+	case []interface{}:
+		for i, v := range m {
+			m[i] = convertMap(v)
+		}
+	}
+	return m
+}

+ 1 - 1
cli/cmd/stack/types.go

@@ -5,7 +5,7 @@ type PorterStackYAML struct {
 	Build   *Build            `yaml:"build"`
 	Env     map[string]string `yaml:"env"`
 	Apps    map[string]*App   `yaml:"apps"`
-	Release *string           `yaml:"release"`
+	Release *App              `yaml:"release"`
 }
 
 type Build struct {