Quellcode durchsuchen

initial commit for v2beta1

Mohammed Nafees vor 3 Jahren
Ursprung
Commit
07b55c5546
4 geänderte Dateien mit 359 neuen und 36 gelöschten Zeilen
  1. 62 36
      cli/cmd/apply.go
  2. 257 0
      cli/cmd/preview/v2beta1/apply.go
  3. 4 0
      cli/cmd/preview/v2beta1/types.go
  4. 36 0
      cli/cmd/preview/v2beta1/utils.go

+ 62 - 36
cli/cmd/apply.go

@@ -20,6 +20,7 @@ import (
 	"github.com/porter-dev/porter/cli/cmd/deploy"
 	"github.com/porter-dev/porter/cli/cmd/deploy/wait"
 	"github.com/porter-dev/porter/cli/cmd/preview"
+	previewV2Beta1 "github.com/porter-dev/porter/cli/cmd/preview/v2beta1"
 	previewInt "github.com/porter-dev/porter/internal/integrations/preview"
 	"github.com/porter-dev/porter/internal/templater/utils"
 	"github.com/porter-dev/switchboard/pkg/drivers"
@@ -29,6 +30,7 @@ import (
 	switchboardWorker "github.com/porter-dev/switchboard/pkg/worker"
 	"github.com/rs/zerolog"
 	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v2"
 )
 
 // applyCmd represents the "porter apply" base command when called
@@ -105,65 +107,89 @@ func init() {
 }
 
 func apply(_ *types.GetAuthenticatedUserResponse, client *api.Client, _ []string) error {
-	if _, ok := os.LookupEnv("PORTER_VALIDATE_YAML"); ok {
-		err := applyValidate()
-
-		if err != nil {
-			return err
-		}
-	}
-
 	fileBytes, err := ioutil.ReadFile(porterYAML)
 
 	if err != nil {
 		return fmt.Errorf("error reading porter.yaml: %w", err)
 	}
 
-	resGroup, err := parser.ParseRawBytes(fileBytes)
-
-	if err != nil {
-		return fmt.Errorf("error parsing porter.yaml: %w", err)
+	var previewVersion struct {
+		Version string `json:"version"`
 	}
 
-	basePath, err := os.Getwd()
+	err = yaml.Unmarshal(fileBytes, &previewVersion)
 
 	if err != nil {
-		return fmt.Errorf("error getting working directory: %w", err)
+		return fmt.Errorf("error unmarshaling porter.yaml: %w", err)
 	}
 
-	worker := switchboardWorker.NewWorker()
-	worker.RegisterDriver("deploy", NewDeployDriver)
-	worker.RegisterDriver("build-image", preview.NewBuildDriver)
-	worker.RegisterDriver("push-image", preview.NewPushDriver)
-	worker.RegisterDriver("update-config", preview.NewUpdateConfigDriver)
-	worker.RegisterDriver("random-string", preview.NewRandomStringDriver)
-	worker.RegisterDriver("env-group", preview.NewEnvGroupDriver)
-	worker.RegisterDriver("os-env", preview.NewOSEnvDriver)
+	if previewVersion.Version == "v2beta1" {
+		ns := os.Getenv("PORTER_NAMESPACE")
 
-	worker.SetDefaultDriver("deploy")
+		applier, err := previewV2Beta1.NewApplier(client, fileBytes, ns)
 
-	if hasDeploymentHookEnvVars() {
-		deplNamespace := os.Getenv("PORTER_NAMESPACE")
+		if err != nil {
+			return err
+		}
 
-		if deplNamespace == "" {
-			return fmt.Errorf("namespace must be set by PORTER_NAMESPACE")
+		return applier.Apply()
+	} else if previewVersion.Version == "v1" {
+		if _, ok := os.LookupEnv("PORTER_VALIDATE_YAML"); ok {
+			err := applyValidate()
+
+			if err != nil {
+				return err
+			}
 		}
 
-		deploymentHook, err := NewDeploymentHook(client, resGroup, deplNamespace)
+		resGroup, err := parser.ParseRawBytes(fileBytes)
 
 		if err != nil {
-			return fmt.Errorf("error creating deployment hook: %w", err)
+			return fmt.Errorf("error parsing porter.yaml: %w", err)
 		}
 
-		worker.RegisterHook("deployment", deploymentHook)
-	}
+		basePath, err := os.Getwd()
 
-	cloneEnvGroupHook := NewCloneEnvGroupHook(client, resGroup)
-	worker.RegisterHook("cloneenvgroup", cloneEnvGroupHook)
+		if err != nil {
+			return fmt.Errorf("error getting working directory: %w", err)
+		}
 
-	return worker.Apply(resGroup, &switchboardTypes.ApplyOpts{
-		BasePath: basePath,
-	})
+		worker := switchboardWorker.NewWorker()
+		worker.RegisterDriver("deploy", NewDeployDriver)
+		worker.RegisterDriver("build-image", preview.NewBuildDriver)
+		worker.RegisterDriver("push-image", preview.NewPushDriver)
+		worker.RegisterDriver("update-config", preview.NewUpdateConfigDriver)
+		worker.RegisterDriver("random-string", preview.NewRandomStringDriver)
+		worker.RegisterDriver("env-group", preview.NewEnvGroupDriver)
+		worker.RegisterDriver("os-env", preview.NewOSEnvDriver)
+
+		worker.SetDefaultDriver("deploy")
+
+		if hasDeploymentHookEnvVars() {
+			deplNamespace := os.Getenv("PORTER_NAMESPACE")
+
+			if deplNamespace == "" {
+				return fmt.Errorf("namespace must be set by PORTER_NAMESPACE")
+			}
+
+			deploymentHook, err := NewDeploymentHook(client, resGroup, deplNamespace)
+
+			if err != nil {
+				return fmt.Errorf("error creating deployment hook: %w", err)
+			}
+
+			worker.RegisterHook("deployment", deploymentHook)
+		}
+
+		cloneEnvGroupHook := NewCloneEnvGroupHook(client, resGroup)
+		worker.RegisterHook("cloneenvgroup", cloneEnvGroupHook)
+
+		return worker.Apply(resGroup, &switchboardTypes.ApplyOpts{
+			BasePath: basePath,
+		})
+	}
+
+	return fmt.Errorf("unknown porter.yaml version: %s", previewVersion.Version)
 }
 
 func applyValidate() error {

+ 257 - 0
cli/cmd/preview/v2beta1/apply.go

@@ -0,0 +1,257 @@
+package v2beta1
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+
+	api "github.com/porter-dev/porter/api/client"
+	apiTypes "github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/cli/cmd/config"
+)
+
+const (
+	constantsEnvGroup = "preview-env-constants"
+
+	defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~`!@#$%^&*()_+-={}[]"
+)
+
+type PreviewApplier struct {
+	apiClient *api.Client
+	rawBytes  []byte
+	namespace string
+	// parsed    *types.ParsedPorterYAML
+
+	variablesMap map[string]string
+	osEnv        map[string]string
+	envGroups    map[string]*apiTypes.EnvGroup
+}
+
+func NewApplier(client *api.Client, raw []byte, namespace string) (*PreviewApplier, error) {
+	// parsed, err := parser.ParseRawBytes(raw)
+
+	// if err != nil {
+	// 	return nil, err
+	// }
+
+	// err = validator.ValidatePorterYAML(parsed)
+
+	// if err != nil {
+	// 	return nil, err
+	// }
+
+	err := validateCLIEnvironment(namespace)
+
+	if err != nil {
+		errMsg := ComposePreviewMessage(fmt.Sprintf("porter CLI is not configured correctly"), Error)
+		return nil, fmt.Errorf("%s: %w", errMsg, err)
+	}
+
+	return &PreviewApplier{
+		apiClient: client,
+		rawBytes:  raw,
+		namespace: namespace,
+		// parsed:    parsed,
+	}, nil
+}
+
+func validateCLIEnvironment(namespace string) error {
+	if config.GetCLIConfig().Token == "" {
+		return fmt.Errorf("no auth token present, please run 'porter auth login' to authenticate")
+	}
+
+	if config.GetCLIConfig().Project == 0 {
+		return fmt.Errorf("no project selected, please run 'porter config set-project' to select a project")
+	}
+
+	if config.GetCLIConfig().Cluster == 0 {
+		return fmt.Errorf("no cluster selected, please run 'porter config set-cluster' to select a cluster")
+	}
+
+	if namespace == "" {
+		return fmt.Errorf("no namespace provided, please set the PORTER_NAMESPACE environment variable")
+	}
+
+	return nil
+}
+
+func (a *PreviewApplier) Apply() error {
+	// for v2beta1, check if the namespace exists in the current project-cluster pair
+	//
+	// this is a sanity check to ensure that the user does not see any internal
+	// errors that are caused by the namespace not existing
+	nsList, err := a.apiClient.GetK8sNamespaces(
+		context.Background(),
+		config.GetCLIConfig().Project,
+		config.GetCLIConfig().Cluster,
+	)
+
+	if err != nil {
+		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)
+	}
+
+	namespaces := *nsList
+	nsFound := false
+
+	for _, ns := range namespaces {
+		if ns.Name == a.namespace {
+			nsFound = true
+			break
+		}
+	}
+
+	if !nsFound {
+		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"+
+		"\tHost: %s\n\tProject ID: %d\n\tCluster ID: %d\n\tNamespace: %s",
+		config.GetCLIConfig().Host,
+		config.GetCLIConfig().Project,
+		config.GetCLIConfig().Cluster,
+		a.namespace),
+	)
+
+	err = a.readOSEnv()
+
+	if err != nil {
+		errMsg := ComposePreviewMessage("error reading OS environment variables", Error)
+		return fmt.Errorf("%s: %w", errMsg, err)
+	}
+
+	return nil
+}
+
+func (a *PreviewApplier) readOSEnv() error {
+	PrintInfoMessage("Reading OS environment variables")
+
+	env := os.Environ()
+	osEnv := make(map[string]string)
+
+	for _, e := range env {
+		k, v, _ := strings.Cut(e, "=")
+		kCopy := k
+
+		if k != "" && v != "" && strings.HasPrefix(k, "PORTER_APPLY_") {
+			// we only read in env variables that start with PORTER_APPLY_
+			for strings.HasPrefix(k, "PORTER_APPLY_") {
+				k = strings.TrimPrefix(k, "PORTER_APPLY_")
+			}
+
+			if k == "" {
+				PrintWarningMessage(fmt.Sprintf("Ignoring invalid OS environment variable '%s'", kCopy))
+			}
+
+			osEnv[k] = v
+		}
+	}
+
+	a.osEnv = osEnv
+
+	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
+// }

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

@@ -0,0 +1,4 @@
+package v2beta1
+
+type Variable struct {
+}

+ 36 - 0
cli/cmd/preview/v2beta1/utils.go

@@ -0,0 +1,36 @@
+package v2beta1
+
+import (
+	"fmt"
+
+	"github.com/fatih/color"
+)
+
+type MessageLevel string
+
+const (
+	Warning MessageLevel = "WARN"
+	Error   MessageLevel = "ERR"
+	Success MessageLevel = "OK"
+	Info    MessageLevel = "INFO"
+)
+
+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 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 PrintInfoMessage(msg string) {
+	color.New(color.FgBlue).Printf(fmt.Sprintf("%s\n", ComposePreviewMessage(msg, Info)))
+}