Przeglądaj źródła

process env groups

Mohammed Nafees 3 lat temu
rodzic
commit
e41dd77ea3

+ 8 - 8
api/server/handlers/namespace/clone_env_group.go

@@ -50,12 +50,12 @@ func (c *CloneEnvGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	cm, _, err := agent.GetLatestVersionedConfigMap(request.Name, namespace)
+	cm, _, err := agent.GetLatestVersionedConfigMap(request.SourceName, namespace)
 
 	if err != nil {
 		if errors.Is(err, kubernetes.IsNotFoundError) {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-				fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", request.Name, namespace), http.StatusNotFound,
+				fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", request.SourceName, namespace), http.StatusNotFound,
 				"no config map found for envgroup",
 			))
 			return
@@ -65,12 +65,12 @@ func (c *CloneEnvGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	secret, _, err := agent.GetLatestVersionedSecret(request.Name, namespace)
+	secret, _, err := agent.GetLatestVersionedSecret(request.SourceName, namespace)
 
 	if err != nil {
 		if errors.Is(err, kubernetes.IsNotFoundError) {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-				fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", request.Name, namespace), http.StatusNotFound,
+				fmt.Errorf("error cloning env group: envgroup %s in namespace %s not found", request.SourceName, namespace), http.StatusNotFound,
 				"no k8s secret found for envgroup",
 			))
 			return
@@ -80,8 +80,8 @@ func (c *CloneEnvGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	if request.CloneName == "" {
-		request.CloneName = request.Name
+	if request.TargetName == "" {
+		request.TargetName = request.SourceName
 	}
 
 	vars := make(map[string]string)
@@ -98,8 +98,8 @@ func (c *CloneEnvGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 
 	configMap, err := envgroup.CreateEnvGroup(agent, types.ConfigMapInput{
-		Name:            request.CloneName,
-		Namespace:       request.Namespace,
+		Name:            request.TargetName,
+		Namespace:       request.TargetNamespace,
 		Variables:       vars,
 		SecretVariables: secretVars,
 	})

+ 4 - 4
api/types/namespace.go

@@ -133,10 +133,10 @@ type GetEnvGroupRequest struct {
 }
 
 type CloneEnvGroupRequest struct {
-	Namespace string `json:"namespace" form:"required"`
-	Name      string `json:"name" form:"required,dns1123"`
-	CloneName string `json:"clone_name,dns1123"`
-	Version   uint   `json:"version"`
+	TargetNamespace string `json:"namespace" form:"required"`
+	SourceName      string `json:"name" form:"required,dns1123"`
+	TargetName      string `json:"clone_name,dns1123"`
+	Version         uint   `json:"version"`
 }
 
 type GetEnvGroupAllRequest struct {

+ 2 - 2
cli/cmd/apply.go

@@ -1199,8 +1199,8 @@ func (t *CloneEnvGroupHook) PreApply() error {
 					_, err = t.client.CloneEnvGroup(
 						context.Background(), target.Project, target.Cluster, group.Namespace,
 						&types.CloneEnvGroupRequest{
-							Name:      group.Name,
-							Namespace: target.Namespace,
+							SourceName:      group.Name,
+							TargetNamespace: target.Namespace,
 						},
 					)
 

+ 188 - 113
cli/cmd/preview/v2beta1/apply.go

@@ -9,6 +9,7 @@ import (
 	api "github.com/porter-dev/porter/api/client"
 	apiTypes "github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/cli/cmd/config"
+	"gopkg.in/yaml.v3"
 )
 
 const (
@@ -21,7 +22,7 @@ type PreviewApplier struct {
 	apiClient *api.Client
 	rawBytes  []byte
 	namespace string
-	// parsed    *types.ParsedPorterYAML
+	parsed    *PorterYAML
 
 	variablesMap map[string]string
 	osEnv        map[string]string
@@ -29,11 +30,14 @@ type PreviewApplier struct {
 }
 
 func NewApplier(client *api.Client, raw []byte, namespace string) (*PreviewApplier, error) {
-	// parsed, err := parser.ParseRawBytes(raw)
+	parsed := &PorterYAML{}
 
-	// if err != nil {
-	// 	return nil, err
-	// }
+	err := yaml.Unmarshal(raw, parsed)
+
+	if err != nil {
+		errMsg := composePreviewMessage("error parsing porter.yaml", Error)
+		return nil, fmt.Errorf("%s: %w", errMsg, err)
+	}
 
 	// err = validator.ValidatePorterYAML(parsed)
 
@@ -41,10 +45,10 @@ func NewApplier(client *api.Client, raw []byte, namespace string) (*PreviewAppli
 	// 	return nil, err
 	// }
 
-	err := validateCLIEnvironment(namespace)
+	err = validateCLIEnvironment(namespace)
 
 	if err != nil {
-		errMsg := ComposePreviewMessage(fmt.Sprintf("porter CLI is not configured correctly"), Error)
+		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return nil, fmt.Errorf("%s: %w", errMsg, err)
 	}
 
@@ -52,7 +56,7 @@ func NewApplier(client *api.Client, raw []byte, namespace string) (*PreviewAppli
 		apiClient: client,
 		rawBytes:  raw,
 		namespace: namespace,
-		// parsed:    parsed,
+		parsed:    parsed,
 	}, nil
 }
 
@@ -88,7 +92,7 @@ func (a *PreviewApplier) Apply() error {
 	)
 
 	if err != nil {
-		errMsg := ComposePreviewMessage(fmt.Sprintf("error listing namespaces for project '%d', cluster '%d'",
+		errMsg := composePreviewMessage(fmt.Sprintf("error listing namespaces for project '%d', cluster '%d'",
 			config.GetCLIConfig().Project, config.GetCLIConfig().Cluster), Error)
 		return fmt.Errorf("%s: %w", errMsg, err)
 	}
@@ -104,12 +108,12 @@ func (a *PreviewApplier) Apply() error {
 	}
 
 	if !nsFound {
-		errMsg := ComposePreviewMessage(fmt.Sprintf("namespace '%s' does not exist in project '%d', cluster '%d'",
+		errMsg := composePreviewMessage(fmt.Sprintf("namespace '%s' does not exist in project '%d', cluster '%d'",
 			a.namespace, config.GetCLIConfig().Project, config.GetCLIConfig().Cluster), Error)
 		return fmt.Errorf("%s: %w", errMsg, err)
 	}
 
-	PrintInfoMessage(fmt.Sprintf("Applying porter.yaml with the following attributes:\n"+
+	printInfoMessage(fmt.Sprintf("Applying porter.yaml with the following attributes:\n"+
 		"\tHost: %s\n\tProject ID: %d\n\tCluster ID: %d\n\tNamespace: %s",
 		config.GetCLIConfig().Host,
 		config.GetCLIConfig().Project,
@@ -120,7 +124,7 @@ func (a *PreviewApplier) Apply() error {
 	err = a.readOSEnv()
 
 	if err != nil {
-		errMsg := ComposePreviewMessage("error reading OS environment variables", Error)
+		errMsg := composePreviewMessage("error reading OS environment variables", Error)
 		return fmt.Errorf("%s: %w", errMsg, err)
 	}
 
@@ -128,7 +132,7 @@ func (a *PreviewApplier) Apply() error {
 }
 
 func (a *PreviewApplier) readOSEnv() error {
-	PrintInfoMessage("Reading OS environment variables")
+	printInfoMessage("Reading OS environment variables")
 
 	env := os.Environ()
 	osEnv := make(map[string]string)
@@ -144,7 +148,7 @@ func (a *PreviewApplier) readOSEnv() error {
 			}
 
 			if k == "" {
-				PrintWarningMessage(fmt.Sprintf("Ignoring invalid OS environment variable '%s'", kCopy))
+				printWarningMessage(fmt.Sprintf("Ignoring invalid OS environment variable '%s'", kCopy))
 			}
 
 			osEnv[k] = v
@@ -156,102 +160,173 @@ func (a *PreviewApplier) readOSEnv() error {
 	return nil
 }
 
-// func (a *PreviewApplier) processVariables() error {
-// 	PrintInfoMessage("Processing variables")
-
-// 	constantsMap := make(map[string]string)
-// 	variablesMap := make(map[string]string)
-
-// 	for _, v := range a.parsed.PorterYAML.Variables.GetValue() {
-// 		if v.Once.GetValue() {
-// 			// a constant which should be stored in the env group on first run
-// 			if exists, err := a.constantExistsInEnvGroup(v.Name.GetValue()); err == nil {
-// 				if exists == nil {
-// 					// this should not happen
-// 					return fmt.Errorf("internal error: please let the Porter team know about this and quote the following " +
-// 						"error:\n-----\nERROR: checking for constant existence in env group returned nil with no error")
-// 				}
-
-// 				val := *exists
-
-// 				if !val {
-// 					// create the constant in the env group
-// 					if v.Value.GetValue() != "" {
-// 						constantsMap[v.Name.GetValue()] = v.Value.GetValue()
-// 					} else if v.Random.GetValue() {
-// 						constantsMap[v.Name.GetValue()] = randomString(v.Length.GetValue(), defaultCharset)
-// 					} else {
-// 						// this should not happen
-// 						return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
-// 							"error:\n-----\nERROR: for variable '%s', random is false and value is empty", v.Name.GetValue())
-// 					}
-// 				}
-// 			} else {
-// 				return fmt.Errorf("error checking for existence of constant %s: %w", v.Name.GetValue(), err)
-// 			}
-// 		} else {
-// 			if v.Value.GetValue() != "" {
-// 				variablesMap[v.Name.GetValue()] = v.Value.GetValue()
-// 			} else if v.Random.GetValue() {
-// 				variablesMap[v.Name.GetValue()] = randomString(v.Length.GetValue(), defaultCharset)
-// 			} else {
-// 				// this should not happen
-// 				return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
-// 					"error:\n-----\nERROR: for variable '%s', random is false and value is empty", v.Name.GetValue())
-// 			}
-// 		}
-// 	}
-
-// 	if len(constantsMap) > 0 {
-// 		// we need to create these constants in the env group
-// 		_, err := a.apiClient.CreateEnvGroup(
-// 			context.Background(),
-// 			config.GetCLIConfig().Project,
-// 			config.GetCLIConfig().Cluster,
-// 			a.namespace,
-// 			&apiTypes.CreateEnvGroupRequest{
-// 				Name:      constantsEnvGroup,
-// 				Variables: constantsMap,
-// 			},
-// 		)
-
-// 		if err != nil {
-// 			return fmt.Errorf("error creating constants (variables with once set to true) in env group: %w", err)
-// 		}
-
-// 		for k, v := range constantsMap {
-// 			variablesMap[k] = v
-// 		}
-// 	}
-
-// 	a.variablesMap = variablesMap
-
-// 	return nil
-// }
-
-// func (a *PreviewApplier) constantExistsInEnvGroup(name string) (*bool, error) {
-// 	apiResponse, err := a.apiClient.GetEnvGroup(
-// 		context.Background(),
-// 		config.GetCLIConfig().Project,
-// 		config.GetCLIConfig().Cluster,
-// 		a.namespace,
-// 		&apiTypes.GetEnvGroupRequest{
-// 			Name: constantsEnvGroup,
-// 			// we do not care about the version because it always needs to be the latest
-// 		},
-// 	)
-
-// 	if err != nil {
-// 		if strings.Contains(err.Error(), "env group not found") {
-// 			return boolean(false), nil
-// 		}
-
-// 		return nil, err
-// 	}
-
-// 	if _, ok := apiResponse.Variables[name]; ok {
-// 		return boolean(true), nil
-// 	}
-
-// 	return boolean(false), nil
-// }
+func (a *PreviewApplier) processVariables() error {
+	printInfoMessage("Processing variables")
+
+	constantsMap := make(map[string]string)
+	variablesMap := make(map[string]string)
+
+	for _, v := range a.parsed.Variables {
+		if v == nil {
+			continue
+		}
+
+		if v.Once != nil && *v.Once {
+			// a constant which should be stored in the env group on first run
+			if exists, err := a.constantExistsInEnvGroup(*v.Name); err == nil {
+				if exists == nil {
+					// this should not happen
+					return fmt.Errorf("internal error: please let the Porter team know about this and quote the following " +
+						"error:\n-----\nERROR: checking for constant existence in env group returned nil with no error")
+				}
+
+				val := *exists
+
+				if !val {
+					// create the constant in the env group
+					if *v.Value != "" {
+						constantsMap[*v.Name] = *v.Value
+					} else if v.Random != nil && *v.Random {
+						constantsMap[*v.Name] = randomString(*v.Length, defaultCharset)
+					} else {
+						// this should not happen
+						return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
+							"error:\n-----\nERROR: for variable '%s', random is false and value is empty", *v.Name)
+					}
+				}
+			} else {
+				return fmt.Errorf("error checking for existence of constant %s: %w", *v.Name, err)
+			}
+		} else {
+			if v.Value != nil && *v.Value != "" {
+				variablesMap[*v.Name] = *v.Value
+			} else if v.Random != nil && *v.Random {
+				variablesMap[*v.Name] = randomString(*v.Length, defaultCharset)
+			} else {
+				// this should not happen
+				return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
+					"error:\n-----\nERROR: for variable '%s', random is false and value is empty", *v.Name)
+			}
+		}
+	}
+
+	if len(constantsMap) > 0 {
+		// we need to create these constants in the env group
+		_, err := a.apiClient.CreateEnvGroup(
+			context.Background(),
+			config.GetCLIConfig().Project,
+			config.GetCLIConfig().Cluster,
+			a.namespace,
+			&apiTypes.CreateEnvGroupRequest{
+				Name:      constantsEnvGroup,
+				Variables: constantsMap,
+			},
+		)
+
+		if err != nil {
+			return fmt.Errorf("error creating constants (variables with once set to true) in env group: %w", err)
+		}
+
+		for k, v := range constantsMap {
+			variablesMap[k] = v
+		}
+	}
+
+	a.variablesMap = variablesMap
+
+	return nil
+}
+
+func (a *PreviewApplier) constantExistsInEnvGroup(name string) (*bool, error) {
+	apiResponse, err := a.apiClient.GetEnvGroup(
+		context.Background(),
+		config.GetCLIConfig().Project,
+		config.GetCLIConfig().Cluster,
+		a.namespace,
+		&apiTypes.GetEnvGroupRequest{
+			Name: constantsEnvGroup,
+			// we do not care about the version because it always needs to be the latest
+		},
+	)
+
+	if err != nil {
+		if strings.Contains(err.Error(), "env group not found") {
+			return booleanptr(false), nil
+		}
+
+		return nil, err
+	}
+
+	if _, ok := apiResponse.Variables[name]; ok {
+		return booleanptr(true), nil
+	}
+
+	return booleanptr(false), nil
+}
+
+func (a *PreviewApplier) processEnvGroups() error {
+	printInfoMessage("Processing env groups")
+
+	for _, eg := range a.parsed.EnvGroups {
+		if eg == nil {
+			continue
+		}
+
+		if eg.Name == nil || *eg.Name == "" {
+
+		}
+
+		envGroup, err := a.apiClient.GetEnvGroup(
+			context.Background(),
+			config.GetCLIConfig().Project,
+			config.GetCLIConfig().Cluster,
+			a.namespace,
+			&apiTypes.GetEnvGroupRequest{
+				Name: *eg.Name,
+			},
+		)
+
+		if err != nil && strings.Contains(err.Error(), "env group not found") {
+			if eg.CloneFrom == nil {
+				return fmt.Errorf(composePreviewMessage(fmt.Sprintf("empty clone_from for env group '%s'", *eg.Name), Error))
+			}
+
+			egNS, egName, found := strings.Cut(*eg.CloneFrom, "/")
+
+			if !found {
+				return fmt.Errorf("error parsing clone_from for env group '%s': invalid format", *eg.Name)
+			}
+
+			// clone the env group
+			envGroup, err := a.apiClient.CloneEnvGroup(
+				context.Background(),
+				config.GetCLIConfig().Project,
+				config.GetCLIConfig().Cluster,
+				egNS,
+				&apiTypes.CloneEnvGroupRequest{
+					SourceName:      egName,
+					TargetNamespace: a.namespace,
+					TargetName:      *eg.Name,
+				},
+			)
+
+			if err != nil {
+				return fmt.Errorf("error cloning env group '%s' from '%s': %w", egName, egNS, err)
+			}
+
+			a.envGroups[*eg.Name] = &apiTypes.EnvGroup{
+				Name:      envGroup.Name,
+				Variables: envGroup.Variables,
+			}
+		} else if err != nil {
+			return fmt.Errorf("error checking for env group '%s': %w", *eg.Name, err)
+		} else {
+			a.envGroups[*eg.Name] = &apiTypes.EnvGroup{
+				Name:      envGroup.Name,
+				Variables: envGroup.Variables,
+			}
+		}
+	}
+
+	return nil
+}

+ 43 - 0
cli/cmd/preview/v2beta1/types.go

@@ -1,4 +1,47 @@
 package v2beta1
 
 type Variable struct {
+	Name   *string `yaml:"name" validate:"required,unique"`
+	Value  *string `yaml:"value" validate:"required_if=Random false"`
+	Once   *bool   `yaml:"once"`
+	Random *bool   `yaml:"random"`
+	Length *uint   `yaml:"length"`
+}
+
+type EnvGroup struct {
+	Name      *string `yaml:"name" validate:"required"`
+	CloneFrom *string `yaml:"clone_from" validate:"required"`
+}
+
+type Build struct {
+	Name         *string             `yaml:"name" validate:"required"`
+	Context      *string             `yaml:"context" validate:"dir"`
+	Method       *string             `yaml:"method" validate:"required,oneof=pack docker registry"`
+	Builder      *string             `yaml:"builder" validate:"required_if=Method pack"`
+	Buildpacks   []*string           `yaml:"buildpacks"`
+	Dockerfile   *string             `yaml:"dockerfile" validate:"required_if=Method docker"`
+	Image        *string             `yaml:"image" validate:"required_if=Method registry"`
+	Env          map[*string]*string `yaml:"env"`
+	EnvGroups    []*string           `yaml:"env_groups"`
+	UseCache     *bool               `yaml:"use_cache"`
+	ImageRepoURI *string             `yaml:"image_repo_uri"`
+}
+
+type Resource struct {
+	Name      *string          `yaml:"name" validate:"required,unique"`
+	DependsOn []*string        `yaml:"depends_on"`
+	Type      *string          `yaml:"type" validate:"required"`
+	ChartURL  *string          `yaml:"chart_url" validate:"url"`
+	Version   *string          `yaml:"version"`
+	Build     map[*string]*any `yaml:"build"`
+	Deploy    map[*string]*any `yaml:"deploy"`
+}
+
+type PorterYAML struct {
+	Version   *string     `yaml:"version"`
+	Variables []*Variable `yaml:"variables"`
+	EnvGroups []*EnvGroup `yaml:"env_groups"`
+	Builds    []*Build    `yaml:"builds"`
+	Apps      []*Resource `yaml:"apps"`
+	Addons    []*Resource `yaml:"addons"`
 }

+ 30 - 9
cli/cmd/preview/v2beta1/utils.go

@@ -1,6 +1,7 @@
 package v2beta1
 
 import (
+	"crypto/rand"
 	"fmt"
 
 	"github.com/fatih/color"
@@ -15,22 +16,42 @@ const (
 	Info    MessageLevel = "INFO"
 )
 
-func ComposePreviewMessage(msg string, level MessageLevel) string {
+func composePreviewMessage(msg string, level MessageLevel) string {
 	return fmt.Sprintf("[porter.yaml v2beta1][%s] -- %s", level, msg)
 }
 
-func PrintWarningMessage(msg string) {
-	color.New(color.FgYellow).Printf(fmt.Sprintf("%s\n", ComposePreviewMessage(msg, Warning)))
+func printWarningMessage(msg string) {
+	color.New(color.FgYellow).Printf(fmt.Sprintf("%s\n", composePreviewMessage(msg, Warning)))
 }
 
-func PrintErrorMessage(msg string) {
-	color.New(color.FgRed).Printf(fmt.Sprintf("%s\n", ComposePreviewMessage(msg, Error)))
+func printErrorMessage(msg string) {
+	color.New(color.FgRed).Printf(fmt.Sprintf("%s\n", composePreviewMessage(msg, Error)))
 }
 
-func PrintSuccessMessage(msg string) {
-	color.New(color.FgGreen).Printf(fmt.Sprintf("%s\n", ComposePreviewMessage(msg, Success)))
+func printSuccessMessage(msg string) {
+	color.New(color.FgGreen).Printf(fmt.Sprintf("%s\n", composePreviewMessage(msg, Success)))
 }
 
-func PrintInfoMessage(msg string) {
-	color.New(color.FgBlue).Printf(fmt.Sprintf("%s\n", ComposePreviewMessage(msg, Info)))
+func printInfoMessage(msg string) {
+	color.New(color.FgBlue).Printf(fmt.Sprintf("%s\n", composePreviewMessage(msg, Info)))
+}
+
+func booleanptr(b bool) *bool {
+	copy := b
+	return &copy
+}
+
+func stringptr(s string) *string {
+	copy := s
+	return &copy
+}
+
+func randomString(length uint, charset string) string {
+	ll := len(charset)
+	b := make([]byte, length)
+	rand.Read(b) // generates len(b) random bytes
+	for i := uint(0); i < length; i++ {
+		b[i] = charset[int(b[i])%ll]
+	}
+	return string(b)
 }