|
|
@@ -0,0 +1,386 @@
|
|
|
+package preview
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "strings"
|
|
|
+
|
|
|
+ "github.com/docker/distribution/reference"
|
|
|
+ "github.com/mitchellh/mapstructure"
|
|
|
+ "github.com/porter-dev/switchboard/pkg/types"
|
|
|
+ "k8s.io/apimachinery/pkg/util/validation"
|
|
|
+)
|
|
|
+
|
|
|
+func commonValidator(resource *types.Resource) (*Source, *Target, error) {
|
|
|
+ source := &Source{}
|
|
|
+
|
|
|
+ err := mapstructure.Decode(resource.Source, source)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("for resource '%s': error parsing source: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ target := &Target{}
|
|
|
+
|
|
|
+ err = mapstructure.Decode(resource.Target, target)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("for resource '%s': error parsing target: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return source, target, nil
|
|
|
+}
|
|
|
+
|
|
|
+func deployDriverValidator(resource *types.Resource) error {
|
|
|
+ source, _, err := commonValidator(resource)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if source.Name == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': source name cannot be empty", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ if source.Repo == "" {
|
|
|
+ if source.Name == "web" || source.Name == "worker" || source.Name == "job" {
|
|
|
+ source.Repo = "https://charts.getporter.dev"
|
|
|
+ } else {
|
|
|
+ source.Repo = "https://chart-addons.getporter.dev"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if source.Repo == "https://charts.getporter.dev" {
|
|
|
+ appConfig := &ApplicationConfig{}
|
|
|
+
|
|
|
+ err = mapstructure.Decode(resource.Config, appConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if appConfig.Build.Method == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': build method cannot be empty", resource.Name)
|
|
|
+ } else if appConfig.Build.Method != "docker" &&
|
|
|
+ appConfig.Build.Method != "pack" &&
|
|
|
+ appConfig.Build.Method != "registry" {
|
|
|
+ return fmt.Errorf("for resource '%s': build method must be one of 'docker', 'pack', or 'registry'", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ if appConfig.Build.Method == "registry" {
|
|
|
+ if appConfig.Build.Image == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': image cannot be empty when using the 'registry' build method",
|
|
|
+ resource.Name)
|
|
|
+ } else if !strings.Contains(appConfig.Build.Image, "{") {
|
|
|
+ if len(strings.Split(appConfig.Build.Image, ":")) != 2 {
|
|
|
+ return fmt.Errorf("for resource '%s': image must be in the format 'image:tag'", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ // check for valid image
|
|
|
+ _, err := reference.ParseNamed(appConfig.Build.Image)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing image: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, eg := range appConfig.EnvGroups {
|
|
|
+ if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
|
|
|
+ resource.Name, eg.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(appConfig.Values) > 0 {
|
|
|
+ if source.Name == "web" {
|
|
|
+ err := validateWebChartValues(appConfig.Values)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for web deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ } else if source.Name == "worker" {
|
|
|
+ err := validateWorkerChartValues(appConfig.Values)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for worker deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ } else if source.Name == "job" {
|
|
|
+ err := validateJobChartValues(appConfig.Values)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for job deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if source.Repo == "https://chart-addons.getporter.dev" {
|
|
|
+ if len(resource.Config) > 0 {
|
|
|
+ if source.Name == "postgresql" {
|
|
|
+ err := validatePostgresChartValues(resource.Config)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for postgresql deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func buildImageDriverValidator(resource *types.Resource) error {
|
|
|
+ _, target, err := commonValidator(resource)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if target.AppName == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': target app_name is missing", resource.Name)
|
|
|
+ } else {
|
|
|
+ errStrs := validation.IsDNS1123Label(target.AppName)
|
|
|
+
|
|
|
+ if len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in app_name:", resource.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ driverConfig := &BuildDriverConfig{}
|
|
|
+
|
|
|
+ err = mapstructure.Decode(resource.Config, driverConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if driverConfig.Build.Method == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': build method cannot be empty", resource.Name)
|
|
|
+ } else if driverConfig.Build.Method != "docker" &&
|
|
|
+ driverConfig.Build.Method != "pack" &&
|
|
|
+ driverConfig.Build.Method != "registry" {
|
|
|
+ return fmt.Errorf("for resource '%s': build method must be one of 'docker', 'pack', or 'registry'", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ if driverConfig.Build.Method == "registry" {
|
|
|
+ if driverConfig.Build.Image == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': image cannot be empty when using the 'registry' build method",
|
|
|
+ resource.Name)
|
|
|
+ } else if !strings.Contains(driverConfig.Build.Image, "{") {
|
|
|
+ if len(strings.Split(driverConfig.Build.Image, ":")) != 2 {
|
|
|
+ return fmt.Errorf("for resource '%s': image must be in the format 'image:tag'", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ // check for valid image
|
|
|
+ _, err := reference.ParseNamed(driverConfig.Build.Image)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing image: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, eg := range driverConfig.EnvGroups {
|
|
|
+ if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
|
|
|
+ resource.Name, eg.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func pushImageDriverValidator(resource *types.Resource) error {
|
|
|
+ _, target, err := commonValidator(resource)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if target.AppName == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': target app_name is missing", resource.Name)
|
|
|
+ } else {
|
|
|
+ errStrs := validation.IsDNS1123Label(target.AppName)
|
|
|
+
|
|
|
+ if len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in app_name:", resource.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ driverConfig := &PushDriverConfig{}
|
|
|
+
|
|
|
+ err = mapstructure.Decode(resource.Config, driverConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if driverConfig.Push.Image == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': image cannot be empty", resource.Name)
|
|
|
+ } else if !strings.Contains(driverConfig.Push.Image, "{") {
|
|
|
+ if len(strings.Split(driverConfig.Push.Image, ":")) != 2 {
|
|
|
+ return fmt.Errorf("for resource '%s': image must be in the format 'image:tag'", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ // check for valid image
|
|
|
+ _, err := reference.ParseNamed(driverConfig.Push.Image)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing image: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func updateConfigDriverValidator(resource *types.Resource) error {
|
|
|
+ source, target, err := commonValidator(resource)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if target.AppName == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': target app_name is missing", resource.Name)
|
|
|
+ } else {
|
|
|
+ errStrs := validation.IsDNS1123Label(target.AppName)
|
|
|
+
|
|
|
+ if len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in app_name:", resource.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if source.Repo == "" {
|
|
|
+ if source.Name == "web" || source.Name == "worker" || source.Name == "job" {
|
|
|
+ source.Repo = "https://charts.getporter.dev"
|
|
|
+ } else {
|
|
|
+ source.Repo = "https://chart-addons.getporter.dev"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ driverConfig := &UpdateConfigDriverConfig{}
|
|
|
+
|
|
|
+ err = mapstructure.Decode(resource.Config, driverConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if driverConfig.UpdateConfig.Image == "" {
|
|
|
+ return fmt.Errorf("for resource '%s': image cannot be empty", resource.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, eg := range driverConfig.EnvGroups {
|
|
|
+ if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
|
|
|
+ resource.Name, eg.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(driverConfig.Values) > 0 && source.Repo == "https://charts.getporter.dev" {
|
|
|
+ if source.Name == "web" {
|
|
|
+ err := validateWebChartValues(driverConfig.Values)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for web deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ } else if source.Name == "worker" {
|
|
|
+ err := validateWorkerChartValues(driverConfig.Values)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for worker deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ } else if source.Name == "job" {
|
|
|
+ err := validateJobChartValues(driverConfig.Values)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error validating values for job deployment: %w",
|
|
|
+ resource.Name, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func randomStringDriverValidator(resource *types.Resource) error {
|
|
|
+ driverConfig := &RandomStringDriverConfig{}
|
|
|
+
|
|
|
+ err := mapstructure.Decode(resource.Config, driverConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func envGroupDriverValidator(resource *types.Resource) error {
|
|
|
+ target := &Target{}
|
|
|
+
|
|
|
+ err := mapstructure.Decode(resource.Target, target)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing target: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ config := &EnvGroupDriverConfig{}
|
|
|
+
|
|
|
+ err = mapstructure.Decode(resource.Config, config)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("for resource '%s': error parsing config: %w", resource.Name, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, eg := range config.EnvGroups {
|
|
|
+ if errStrs := validation.IsDNS1123Label(eg.Name); len(errStrs) > 0 {
|
|
|
+ str := fmt.Sprintf("for resource '%s': invalid characters found in env group '%s' name:",
|
|
|
+ resource.Name, eg.Name)
|
|
|
+ for _, errStr := range errStrs {
|
|
|
+ str += fmt.Sprintf("\n * %s", errStr)
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Errorf("%s", str)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func osEnvDriverValidator(resource *types.Resource) error {
|
|
|
+ return nil
|
|
|
+}
|