diff --git a/Dockerfile b/Dockerfile index ce17d0c7..da991c0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -102,8 +102,6 @@ COPY legacy/scripts /kubectl-build-deploy/scripts COPY legacy/helmcharts /kubectl-build-deploy/helmcharts -ENV IMAGECACHE_REGISTRY=imagecache.amazeeio.cloud - ENV DBAAS_OPERATOR_HTTP=dbaas.lagoon.svc:5000 RUN curl -sSL https://github.com/uselagoon/lagoon-linter/releases/download/v0.8.0/lagoon-linter_0.8.0_linux_amd64.tar.gz \ diff --git a/cmd/tasks_run.go b/cmd/tasks_run.go index 6c01ab0e..9f9132cf 100644 --- a/cmd/tasks_run.go +++ b/cmd/tasks_run.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" + "os" + "strings" + "github.com/spf13/cobra" "github.com/uselagoon/build-deploy-tool/internal/generator" "github.com/uselagoon/build-deploy-tool/internal/lagoon" "github.com/uselagoon/build-deploy-tool/internal/tasklib" - "io/ioutil" - "os" - "strings" ) var runPreRollout, runPostRollout, outOfClusterConfig bool @@ -31,7 +31,7 @@ var taskCmd = &cobra.Command{ // unidleThenRun is a wrapper around 'runCleanTaskInEnvironment' used for pre-rollout tasks // We actually want to unidle the namespace before running pre-rollout tasks, // so we wrap the usual task runner before calling it. -func unidleThenRun(namespace string, incoming lagoon.Task) error { +func unidleThenRun(namespace string, prePost string, incoming lagoon.Task) error { fmt.Printf("Unidling namespace with RequiresEnvironment: %v, ScaleMaxIterations:%v and ScaleWaitTime:%v\n", incoming.RequiresEnvironment, incoming.ScaleMaxIterations, incoming.ScaleWaitTime) err := lagoon.UnidleNamespace(context.TODO(), namespace, incoming.ScaleMaxIterations, incoming.ScaleWaitTime) if err != nil { @@ -47,7 +47,7 @@ func unidleThenRun(namespace string, incoming lagoon.Task) error { return fmt.Errorf("There was a problem when unidling the environment for pre-rollout tasks: %v", err.Error()) } } - return runCleanTaskInEnvironment(namespace, incoming) + return runCleanTaskInEnvironment(namespace, prePost, incoming) } var tasksPreRun = &cobra.Command{ @@ -65,7 +65,7 @@ var tasksPreRun = &cobra.Command{ } fmt.Println("Executing Pre-rollout Tasks") - taskIterator, err := iterateTaskGenerator(true, unidleThenRun, buildValues, true) + taskIterator, err := iterateTaskGenerator(true, unidleThenRun, buildValues, "Pre-Rollout", true) if err != nil { fmt.Println("Pre-rollout Tasks Failed with the following error: ", err.Error()) os.Exit(1) @@ -97,7 +97,7 @@ var tasksPostRun = &cobra.Command{ fmt.Println("Executing Post-rollout Tasks") - taskIterator, err := iterateTaskGenerator(false, runCleanTaskInEnvironment, buildValues, true) + taskIterator, err := iterateTaskGenerator(false, runCleanTaskInEnvironment, buildValues, "Post-Rollout", true) if err != nil { fmt.Println("Pre-rollout Tasks Failed with the following error: ", err.Error()) os.Exit(1) @@ -159,7 +159,7 @@ type iterateTaskFuncType func(tasklib.TaskEnvironment, []lagoon.Task) (bool, err // that lets the resulting function reference values as part of the closure, thereby cleaning up the definition a bit. // so, the variables passed into the factor (eg. allowDeployMissingErrors, etc.) determine the way the function behaves, // without needing to pass those into the call to the returned function itself. -func iterateTaskGenerator(allowDeployMissingErrors bool, taskRunner runTaskInEnvironmentFuncType, buildValues generator.BuildValues, debug bool) (iterateTaskFuncType, error) { +func iterateTaskGenerator(allowDeployMissingErrors bool, taskRunner runTaskInEnvironmentFuncType, buildValues generator.BuildValues, prePost string, debug bool) (iterateTaskFuncType, error) { var retErr error namespace := buildValues.Namespace if namespace == "" { @@ -168,7 +168,7 @@ func iterateTaskGenerator(allowDeployMissingErrors bool, taskRunner runTaskInEnv if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) { retErr = fmt.Errorf("A target namespace is required to run pre/post-rollout tasks") } - nsb, err := ioutil.ReadFile(filename) + nsb, err := os.ReadFile(filename) if err != nil { retErr = err } @@ -188,7 +188,7 @@ func iterateTaskGenerator(allowDeployMissingErrors bool, taskRunner runTaskInEnv return true, err } if runTask { - err := taskRunner(namespace, task) + err := taskRunner(namespace, prePost, task) if err != nil { switch e := err.(type) { case *lagoon.DeploymentMissingError: @@ -242,12 +242,12 @@ func evaluateWhenConditionsForTaskInEnvironment(environment tasklib.TaskEnvironm return retBool, nil } -type runTaskInEnvironmentFuncType func(namespace string, incoming lagoon.Task) error +type runTaskInEnvironmentFuncType func(namespace string, prePost string, incoming lagoon.Task) error // runCleanTaskInEnvironment implements runTaskInEnvironmentFuncType and will // 1. make sure the task we pass to the execution environment is free of any data we don't want (hence the new task) // 2. will actually execute the task in the environment. -func runCleanTaskInEnvironment(namespace string, incoming lagoon.Task) error { +func runCleanTaskInEnvironment(namespace string, prePost string, incoming lagoon.Task) error { task := lagoon.NewTask() task.Command = incoming.Command task.Namespace = namespace @@ -257,7 +257,7 @@ func runCleanTaskInEnvironment(namespace string, incoming lagoon.Task) error { task.Name = incoming.Name task.ScaleMaxIterations = incoming.ScaleMaxIterations task.ScaleWaitTime = incoming.ScaleWaitTime - err := lagoon.ExecuteTaskInEnvironment(task) + err := lagoon.ExecuteTaskInEnvironment(task, prePost) return err } diff --git a/cmd/tasks_run_test.go b/cmd/tasks_run_test.go index fe9a58b3..06fbfe12 100644 --- a/cmd/tasks_run_test.go +++ b/cmd/tasks_run_test.go @@ -166,13 +166,14 @@ func Test_iterateTaskGenerator(t *testing.T) { tests := []struct { name string debug bool + prePost string args args wantError bool }{ {name: "Runs with no errors", args: args{ allowDeployMissingErrors: true, - taskRunner: func(namespace string, incoming lagoon.Task) error { + taskRunner: func(namespace string, prePost string, incoming lagoon.Task) error { return nil }, tasks: []lagoon.Task{ @@ -180,12 +181,13 @@ func Test_iterateTaskGenerator(t *testing.T) { }, buildValues: generator.BuildValues{Namespace: "empty"}, }, + prePost: "PreRollout", wantError: false, }, {name: "Allows deploy missing errors and keeps rolling (pre rollout case)", args: args{ allowDeployMissingErrors: true, - taskRunner: func(namespace string, incoming lagoon.Task) error { + taskRunner: func(namespace string, prePost string, incoming lagoon.Task) error { return &lagoon.DeploymentMissingError{} }, tasks: []lagoon.Task{ @@ -193,12 +195,13 @@ func Test_iterateTaskGenerator(t *testing.T) { }, buildValues: generator.BuildValues{Namespace: "empty"}, }, + prePost: "PreRollout", wantError: false, }, {name: "Does not allow deploy missing errors and stops with error (post rollout)", args: args{ allowDeployMissingErrors: false, - taskRunner: func(namespace string, incoming lagoon.Task) error { + taskRunner: func(namespace string, prePost string, incoming lagoon.Task) error { return &lagoon.DeploymentMissingError{} }, tasks: []lagoon.Task{ @@ -206,12 +209,13 @@ func Test_iterateTaskGenerator(t *testing.T) { }, buildValues: generator.BuildValues{Namespace: "empty"}, }, + prePost: "PostRollout", wantError: true, }, {name: "Allows deploy missing errors but stops with any other error (pre rollout)", args: args{ allowDeployMissingErrors: true, - taskRunner: func(namespace string, incoming lagoon.Task) error { + taskRunner: func(namespace string, prePost string, incoming lagoon.Task) error { return &lagoon.PodScalingError{} }, tasks: []lagoon.Task{ @@ -219,12 +223,13 @@ func Test_iterateTaskGenerator(t *testing.T) { }, buildValues: generator.BuildValues{Namespace: "empty"}, }, + prePost: "PostRollout", wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, _ := iterateTaskGenerator(tt.args.allowDeployMissingErrors, tt.args.taskRunner, tt.args.buildValues, tt.debug) + got, _ := iterateTaskGenerator(tt.args.allowDeployMissingErrors, tt.args.taskRunner, tt.args.buildValues, tt.prePost, tt.debug) _, err := got(tasklib.TaskEnvironment{}, tt.args.tasks) if tt.wantError && err == nil { diff --git a/cmd/template_ingress_test.go b/cmd/template_ingress_test.go index 59a30962..20a44562 100644 --- a/cmd/template_ingress_test.go +++ b/cmd/template_ingress_test.go @@ -432,6 +432,44 @@ func TestTemplateRoutes(t *testing.T) { }, want: "../test-resources/template-ingress/test20-results", }, + { + name: "test21 alternative names", + args: args{ + alertContact: "alertcontact", + statusPageID: "statuspageid", + projectName: "example-project", + environmentName: "main", + environmentType: "production", + buildType: "branch", + standbyEnvironment: "main2", + lagoonVersion: "v2.7.x", + branch: "main", + projectVars: `[{"name":"LAGOON_SYSTEM_ROUTER_PATTERN","value":"${service}-${project}-${environment}.example.com","scope":"internal_system"}]`, + envVars: `[]`, + secretPrefix: "fastly-api-", + lagoonYAML: "../test-resources/template-ingress/test21/lagoon.yml", + templatePath: "../test-resources/template-ingress/output", + }, + want: "../test-resources/template-ingress/test21-results", + }, + { + name: "test22 check wildcard", + args: args{ + alertContact: "alertcontact", + statusPageID: "statuspageid", + projectName: "example-project", + environmentName: "main", + environmentType: "production", + buildType: "branch", + lagoonVersion: "v2.7.x", + branch: "main", + projectVars: `[{"name":"LAGOON_SYSTEM_ROUTER_PATTERN","value":"${service}-${project}-${environment}.example.com","scope":"internal_system"}]`, + envVars: `[]`, + lagoonYAML: "../test-resources/template-ingress/test22/lagoon.yml", + templatePath: "../test-resources/template-ingress/output", + }, + want: "../test-resources/template-ingress/test22-results", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/generator/ingress.go b/internal/generator/ingress.go index 2f8715f8..c942349a 100644 --- a/internal/generator/ingress.go +++ b/internal/generator/ingress.go @@ -76,7 +76,10 @@ func generateRoutes( // generate the templates for these independently of any previously generated routes, // this WILL overwrite previously created templates ensuring that anything defined in the `production_routes` // section are created correctly ensuring active/standby will work - *activeStanbyRoutes = generateActiveStandbyRoutes(lagoonEnvVars, lYAML, buildValues) + *activeStanbyRoutes, err = generateActiveStandbyRoutes(lagoonEnvVars, lYAML, buildValues) + if err != nil { + return "", []string{}, []string{}, fmt.Errorf("couldn't generate and merge routes: %v", err) + } // get the first route from the list of routes, replace the previous one if necessary if len(activeStanbyRoutes.Routes) > 0 { // if primary != "" { @@ -244,14 +247,17 @@ func generateActiveStandbyRoutes( envVars []lagoon.EnvironmentVariable, lagoonYAML lagoon.YAML, buildValues BuildValues, -) lagoon.RoutesV2 { +) (lagoon.RoutesV2, error) { activeStanbyRoutes := &lagoon.RoutesV2{} if lagoonYAML.ProductionRoutes != nil { if buildValues.IsActiveEnvironment == true { if lagoonYAML.ProductionRoutes.Active != nil { if lagoonYAML.ProductionRoutes.Active.Routes != nil { for _, routeMap := range lagoonYAML.ProductionRoutes.Active.Routes { - lagoon.GenerateRoutesV2(activeStanbyRoutes, routeMap, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix, true) + err := lagoon.GenerateRoutesV2(activeStanbyRoutes, routeMap, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix, true) + if err != nil { + return *activeStanbyRoutes, err + } } } } @@ -260,13 +266,16 @@ func generateActiveStandbyRoutes( if lagoonYAML.ProductionRoutes.Standby != nil { if lagoonYAML.ProductionRoutes.Standby.Routes != nil { for _, routeMap := range lagoonYAML.ProductionRoutes.Standby.Routes { - lagoon.GenerateRoutesV2(activeStanbyRoutes, routeMap, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix, true) + err := lagoon.GenerateRoutesV2(activeStanbyRoutes, routeMap, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix, true) + if err != nil { + return *activeStanbyRoutes, err + } } } } } } - return *activeStanbyRoutes + return *activeStanbyRoutes, nil } // getRoutesFromEnvVar will collect the value of the LAGOON_ROUTES_JSON @@ -305,9 +314,15 @@ func generateAndMerge( // otherwise it just uses the default environment name for _, routeMap := range lagoonYAML.Environments[buildValues.Branch].Routes { - lagoon.GenerateRoutesV2(n, routeMap, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix, false) + err := lagoon.GenerateRoutesV2(n, routeMap, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix, false) + if err != nil { + return *n, err + } } // merge routes from the API on top of the routes from the `.lagoon.yml` - mainRoutes := lagoon.MergeRoutesV2(*n, api, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix) + mainRoutes, err := lagoon.MergeRoutesV2(*n, api, envVars, buildValues.IngressClass, buildValues.FastlyAPISecretPrefix) + if err != nil { + return *n, err + } return mainRoutes, nil } diff --git a/internal/generator/ingress_test.go b/internal/generator/ingress_test.go index 972c3927..225f085c 100644 --- a/internal/generator/ingress_test.go +++ b/internal/generator/ingress_test.go @@ -162,36 +162,40 @@ func Test_generateAndMerge(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "a.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/bypass-cache", + Domain: "a.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/bypass-cache", + AlternativeNames: []string{}, }, { - Domain: "b.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", + Domain: "b.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + AlternativeNames: []string{}, }, { - Domain: "c.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", + Domain: "c.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + AlternativeNames: []string{}, }, { - Domain: "test1.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - MonitoringPath: "/bypass-cache", - Insecure: helpers.StrPtr("Redirect"), - Annotations: map[string]string{}, + Domain: "test1.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + MonitoringPath: "/bypass-cache", + Insecure: helpers.StrPtr("Redirect"), + Annotations: map[string]string{}, + AlternativeNames: []string{}, }, }, }, @@ -223,20 +227,22 @@ func Test_generateAndMerge(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "test1.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - MonitoringPath: "/bypass-cache", - Insecure: helpers.StrPtr("Redirect"), - Annotations: map[string]string{}, + Domain: "test1.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + MonitoringPath: "/bypass-cache", + Insecure: helpers.StrPtr("Redirect"), + Annotations: map[string]string{}, + AlternativeNames: []string{}, }, { - Domain: "a.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/bypass-cache", + Domain: "a.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/bypass-cache", + AlternativeNames: []string{}, }, }, }, @@ -293,46 +299,50 @@ func Test_generateAndMerge(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "a.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/bypass-cache", - IngressClass: "nginx", + Domain: "a.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/bypass-cache", + IngressClass: "nginx", + AlternativeNames: []string{}, }, { - Domain: "b.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", - IngressClass: "nginx", + Domain: "b.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + IngressClass: "nginx", + AlternativeNames: []string{}, }, { - Domain: "c.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", - IngressClass: "nginx", + Domain: "c.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + IngressClass: "nginx", + AlternativeNames: []string{}, }, { - Domain: "test1.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - MonitoringPath: "/bypass-cache", - Insecure: helpers.StrPtr("Redirect"), - Annotations: map[string]string{}, - IngressClass: "nginx", + Domain: "test1.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + MonitoringPath: "/bypass-cache", + Insecure: helpers.StrPtr("Redirect"), + Annotations: map[string]string{}, + IngressClass: "nginx", + AlternativeNames: []string{}, }, }, }, }, { - name: "test3 - generate routes from lagoon yaml and merge ones from api with hsts", + name: "test4 - generate routes from lagoon yaml and merge ones from api with hsts", args: args{ buildValues: BuildValues{ Branch: "main", @@ -373,18 +383,96 @@ func Test_generateAndMerge(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "a.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(false), - Annotations: map[string]string{}, - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/bypass-cache", - IngressClass: "nginx", - HSTSEnabled: helpers.BoolPtr(true), - HSTSMaxAge: 36000, + Domain: "a.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/bypass-cache", + IngressClass: "nginx", + HSTSEnabled: helpers.BoolPtr(true), + HSTSMaxAge: 36000, + AlternativeNames: []string{}, + }, + }, + }, + }, + { + name: "test5 - wildcard with tls-acme false", + args: args{ + buildValues: BuildValues{ + Branch: "main", + IngressClass: "nginx", + }, + lagoonYAML: lagoon.YAML{ + Environments: lagoon.Environments{ + "main": lagoon.Environment{ + Routes: []map[string][]lagoon.Route{ + { + "nginx": { + { + Ingresses: map[string]lagoon.Ingress{ + "a.example.com": { + TLSAcme: helpers.BoolPtr(false), + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: lagoon.RoutesV2{ + Routes: []lagoon.RouteV2{ + { + Domain: "a.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + IngressClass: "nginx", + AlternativeNames: []string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + { + name: "test6 - wildcard with tls-acme true (should error)", + args: args{ + buildValues: BuildValues{ + Branch: "main", + IngressClass: "nginx", + }, + lagoonYAML: lagoon.YAML{ + Environments: lagoon.Environments{ + "main": lagoon.Environment{ + Routes: []map[string][]lagoon.Route{ + { + "nginx": { + { + Ingresses: map[string]lagoon.Ingress{ + "a.example.com": { + TLSAcme: helpers.BoolPtr(true), + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + }, + }, + }, }, }, }, + wantErr: true, + want: lagoon.RoutesV2{ + Routes: nil, + }, }, } for _, tt := range tests { @@ -396,7 +484,7 @@ func Test_generateAndMerge(t *testing.T) { } lValues, _ := json.Marshal(got) wValues, _ := json.Marshal(tt.want) - if !reflect.DeepEqual(string(lValues), string(wValues)) { + if !reflect.DeepEqual(string(lValues), string(wValues)) && !tt.wantErr { t.Errorf("generateAndMerge() = %v, want %v", string(lValues), string(wValues)) } }) @@ -410,9 +498,10 @@ func Test_generateActiveStandbyRoutes(t *testing.T) { buildValues BuildValues } tests := []struct { - name string - args args - want lagoon.RoutesV2 + name string + args args + want lagoon.RoutesV2 + wantErr bool }{ { name: "test1", @@ -440,13 +529,14 @@ func Test_generateActiveStandbyRoutes(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "active.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Migrate: helpers.BoolPtr(true), - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", + Domain: "active.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Migrate: helpers.BoolPtr(true), + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + AlternativeNames: []string{}, }, }, }, @@ -482,20 +572,21 @@ func Test_generateActiveStandbyRoutes(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "active.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Migrate: helpers.BoolPtr(true), - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", - IngressClass: "nginx", + Domain: "active.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Migrate: helpers.BoolPtr(true), + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + IngressClass: "nginx", + AlternativeNames: []string{}, }, }, }, }, { - name: "test2 - with custom ingress class defined", + name: "test3 - with custom ingress class defined", args: args{ buildValues: BuildValues{ IsActiveEnvironment: true, @@ -526,14 +617,95 @@ func Test_generateActiveStandbyRoutes(t *testing.T) { want: lagoon.RoutesV2{ Routes: []lagoon.RouteV2{ { - Domain: "active.example.com", - LagoonService: "nginx", - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - Migrate: helpers.BoolPtr(true), - Insecure: helpers.StrPtr("Redirect"), - MonitoringPath: "/", - IngressClass: "custom-nginx", + Domain: "active.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Migrate: helpers.BoolPtr(true), + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + IngressClass: "custom-nginx", + AlternativeNames: []string{}, + }, + }, + }, + }, + { + name: "test4 - with wildcard and tls-acme true (should error)", + args: args{ + buildValues: BuildValues{ + IngressClass: "nginx", + IsActiveEnvironment: true, + }, + lagoonYAML: lagoon.YAML{ + ProductionRoutes: &lagoon.ProductionRoutes{ + Active: &lagoon.Environment{ + Routes: []map[string][]lagoon.Route{ + { + "nginx": { + { + Ingresses: map[string]lagoon.Ingress{ + "active.example.com": { + TLSAcme: helpers.BoolPtr(true), + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + }, + }, + }, + }, + }, + envVars: []lagoon.EnvironmentVariable{}, + }, + wantErr: true, + want: lagoon.RoutesV2{ + Routes: nil, + }, + }, + { + name: "test5 - with wildcard and tls-acme false", + args: args{ + buildValues: BuildValues{ + IngressClass: "nginx", + IsActiveEnvironment: true, + }, + lagoonYAML: lagoon.YAML{ + ProductionRoutes: &lagoon.ProductionRoutes{ + Active: &lagoon.Environment{ + Routes: []map[string][]lagoon.Route{ + { + "nginx": { + { + Ingresses: map[string]lagoon.Ingress{ + "active.example.com": { + TLSAcme: helpers.BoolPtr(false), + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + }, + }, + }, + }, + }, + envVars: []lagoon.EnvironmentVariable{}, + }, + want: lagoon.RoutesV2{ + Routes: []lagoon.RouteV2{ + { + Domain: "active.example.com", + LagoonService: "nginx", + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Migrate: helpers.BoolPtr(true), + Insecure: helpers.StrPtr("Redirect"), + MonitoringPath: "/", + IngressClass: "nginx", + AlternativeNames: []string{}, + Wildcard: helpers.BoolPtr(true), }, }, }, @@ -541,10 +713,14 @@ func Test_generateActiveStandbyRoutes(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := generateActiveStandbyRoutes(tt.args.envVars, tt.args.lagoonYAML, tt.args.buildValues) + got, err := generateActiveStandbyRoutes(tt.args.envVars, tt.args.lagoonYAML, tt.args.buildValues) + if (err != nil) != tt.wantErr { + t.Errorf("generateAndMerge() error = %v, wantErr %v", err, tt.wantErr) + return + } lValues, _ := json.Marshal(got) wValues, _ := json.Marshal(tt.want) - if !reflect.DeepEqual(string(lValues), string(wValues)) { + if !reflect.DeepEqual(string(lValues), string(wValues)) && !tt.wantErr { t.Errorf("generateAndMerge() = %v, want %v", string(lValues), string(wValues)) } }) diff --git a/internal/lagoon/routes.go b/internal/lagoon/routes.go index 571ba6b6..c4302672 100644 --- a/internal/lagoon/routes.go +++ b/internal/lagoon/routes.go @@ -2,10 +2,13 @@ package lagoon import ( "encoding/json" + "fmt" "reflect" "strconv" + "strings" "github.com/uselagoon/build-deploy-tool/internal/helpers" + "k8s.io/apimachinery/pkg/util/validation" ) // RoutesV2 is the new routes definition @@ -35,6 +38,7 @@ type RouteV2 struct { HSTSIncludeSubdomains *bool `json:"hstsIncludeSubdomains,omitempty"` HSTSPreload *bool `json:"hstsPreload,omitempty"` Autogenerated bool `json:"-"` + Wildcard *bool `json:"wildcard,omitempty"` } // Ingress represents a Lagoon route. @@ -50,6 +54,8 @@ type Ingress struct { HSTSMaxAge int `json:"hstsMaxAge,omitempty"` HSTSIncludeSubdomains *bool `json:"hstsIncludeSubdomains,omitempty"` HSTSPreload *bool `json:"hstsPreload,omitempty"` + AlternativeNames []string `json:"alternativenames,omitempty"` + Wildcard *bool `json:"wildcard,omitempty"` } // Route can be either a string or a map[string]Ingress, so we must @@ -59,7 +65,17 @@ type Route struct { Ingresses map[string]Ingress } -var defaultHSTSMaxAge = 31536000 +// defaults +var ( + defaultHSTSMaxAge = 31536000 + defaultMonitoringPath string = "/" + defaultFastlyService string = "" + defaultFastlyWatch bool = false + defaultInsecure *string = helpers.StrPtr("Redirect") + defaultTLSAcme *bool = helpers.BoolPtr(true) + defaultActiveStandby *bool = helpers.BoolPtr(true) + defaultAnnotations map[string]string = map[string]string{} +) // UnmarshalJSON implements json.Unmarshaler. func (r *Route) UnmarshalJSON(data []byte) error { @@ -98,20 +114,21 @@ func (r *Route) UnmarshalJSON(data []byte) error { } // GenerateRoutesV2 generate routesv2 definitions from lagoon route mappings -func GenerateRoutesV2(genRoutes *RoutesV2, routeMap map[string][]Route, variables []EnvironmentVariable, defaultIngressClass, secretPrefix string, activeStandby bool) { +func GenerateRoutesV2(yamlRoutes *RoutesV2, routeMap map[string][]Route, variables []EnvironmentVariable, defaultIngressClass, secretPrefix string, activeStandby bool) error { for rName, lagoonRoutes := range routeMap { for _, lagoonRoute := range lagoonRoutes { newRoute := RouteV2{} // set the defaults for routes - newRoute.TLSAcme = helpers.BoolPtr(true) - newRoute.Insecure = helpers.StrPtr("Redirect") - newRoute.MonitoringPath = "/" - newRoute.Annotations = map[string]string{} - newRoute.Fastly.ServiceID = "" - newRoute.Fastly.Watch = false + newRoute.TLSAcme = defaultTLSAcme + newRoute.Insecure = defaultInsecure + newRoute.MonitoringPath = defaultMonitoringPath + newRoute.Annotations = defaultAnnotations + newRoute.Fastly.ServiceID = defaultFastlyService + newRoute.Fastly.Watch = defaultFastlyWatch + newRoute.AlternativeNames = []string{} newRoute.IngressClass = defaultIngressClass if activeStandby { - newRoute.Migrate = helpers.BoolPtr(true) + newRoute.Migrate = defaultActiveStandby } if lagoonRoute.Name == "" { // this route from the lagoon route map contains field overrides @@ -130,6 +147,9 @@ func GenerateRoutesV2(genRoutes *RoutesV2, routeMap map[string][]Route, variable if ingress.Insecure != nil { newRoute.Insecure = ingress.Insecure } + if ingress.AlternativeNames != nil { + newRoute.AlternativeNames = ingress.AlternativeNames + } if ingress.IngressClass != "" { newRoute.IngressClass = ingress.IngressClass } @@ -155,6 +175,17 @@ func GenerateRoutesV2(genRoutes *RoutesV2, routeMap map[string][]Route, variable } } // hsts end + + // handle wildcards + if ingress.Wildcard != nil { + newRoute.Wildcard = ingress.Wildcard + if *newRoute.TLSAcme == true && *newRoute.Wildcard == true { + return fmt.Errorf("Route %s has wildcard: true and tls-acme: true, this is not supported", newRoute.Domain) + } + if ingress.AlternativeNames != nil && *newRoute.Wildcard == true { + return fmt.Errorf("Route %s has wildcard: true and alternativenames defined, this is not supported", newRoute.Domain) + } + } } } else { // this route is just a domain @@ -168,135 +199,150 @@ func GenerateRoutesV2(genRoutes *RoutesV2, routeMap map[string][]Route, variable //@TODO: error handling } - genRoutes.Routes = append(genRoutes.Routes, newRoute) + // validate the domain earlier and fail if it is invalid + if err := validation.IsDNS1123Subdomain(strings.ToLower(newRoute.Domain)); err != nil { + return fmt.Errorf("Route %s in .lagoon.yml is not valid: %v", newRoute.Domain, err) + } + yamlRoutes.Routes = append(yamlRoutes.Routes, newRoute) } } + return nil } // MergeRoutesV2 merge routes from the API onto the previously generated routes. -func MergeRoutesV2(genRoutes RoutesV2, apiRoutes RoutesV2, variables []EnvironmentVariable, defaultIngressClass, secretPrefix string) RoutesV2 { - finalRoutes := RoutesV2{} +func MergeRoutesV2(yamlRoutes RoutesV2, apiRoutes RoutesV2, variables []EnvironmentVariable, defaultIngressClass, secretPrefix string) (RoutesV2, error) { + firstRoundRoutes := RoutesV2{} existsInAPI := false // replace any routes from the lagoon yaml with ones from the api // this only modifies ones that exist in lagoon yaml - for _, route := range genRoutes.Routes { - add := RouteV2{} - for _, aRoute := range apiRoutes.Routes { - if aRoute.Domain == route.Domain { - existsInAPI = true - add = aRoute - add.Fastly = aRoute.Fastly - if aRoute.TLSAcme != nil { - add.TLSAcme = aRoute.TLSAcme - } else { - add.TLSAcme = helpers.BoolPtr(true) - } - if aRoute.Insecure != nil { - add.Insecure = aRoute.Insecure - } else { - add.Insecure = helpers.StrPtr("Redirect") - } - if aRoute.Annotations != nil { - add.Annotations = aRoute.Annotations - } else { - add.Annotations = map[string]string{} - } - if aRoute.IngressClass != "" { - add.IngressClass = aRoute.IngressClass - } else { - add.IngressClass = defaultIngressClass - } - - // handle hsts here - if aRoute.HSTSEnabled != nil { - add.HSTSEnabled = aRoute.HSTSEnabled - } - if aRoute.HSTSIncludeSubdomains != nil { - add.HSTSIncludeSubdomains = aRoute.HSTSIncludeSubdomains - } - if aRoute.HSTSPreload != nil { - add.HSTSPreload = aRoute.HSTSPreload + for _, route := range yamlRoutes.Routes { + routeAdd := RouteV2{} + // validate the domain earlier and fail if it is invalid + if err := validation.IsDNS1123Subdomain(strings.ToLower(route.Domain)); err != nil { + return firstRoundRoutes, fmt.Errorf("Route %s in .lagoon.yml is not valid: %v", route.Domain, err) + } + for _, apiRoute := range apiRoutes.Routes { + if apiRoute.Domain == route.Domain { + // validate the domain earlier and fail if it is invalid + if err := validation.IsDNS1123Subdomain(strings.ToLower(apiRoute.Domain)); err != nil { + return firstRoundRoutes, fmt.Errorf("Route %s in API defined routes is not valid: %v", apiRoute.Domain, err) } - if aRoute.HSTSMaxAge > 0 { - add.HSTSMaxAge = aRoute.HSTSMaxAge - } else { - if add.HSTSEnabled != nil && *add.HSTSEnabled { - add.HSTSMaxAge = defaultHSTSMaxAge // set default hsts value if one not provided - } + existsInAPI = true + var err error + routeAdd, err = handleAPIRoute(defaultIngressClass, apiRoute) + if err != nil { + return firstRoundRoutes, err } - // hsts end } } if existsInAPI { - finalRoutes.Routes = append(finalRoutes.Routes, add) + firstRoundRoutes.Routes = append(firstRoundRoutes.Routes, routeAdd) existsInAPI = false } else { - finalRoutes.Routes = append(finalRoutes.Routes, route) + + if route.AlternativeNames == nil { + route.AlternativeNames = []string{} + } + firstRoundRoutes.Routes = append(firstRoundRoutes.Routes, route) } } // add any that exist in the api only to the final routes list - for _, aRoute := range apiRoutes.Routes { - add := aRoute - add.Fastly = aRoute.Fastly - if aRoute.TLSAcme != nil { - add.TLSAcme = aRoute.TLSAcme - } else { - add.TLSAcme = helpers.BoolPtr(true) - } - if aRoute.Insecure != nil { - add.Insecure = aRoute.Insecure - } else { - add.Insecure = helpers.StrPtr("Redirect") - } - if aRoute.Annotations != nil { - add.Annotations = aRoute.Annotations - } else { - add.Annotations = map[string]string{} - } - if aRoute.IngressClass != "" { - add.IngressClass = aRoute.IngressClass - } else { - add.IngressClass = defaultIngressClass + for _, apiRoute := range apiRoutes.Routes { + if err := validation.IsDNS1123Subdomain(strings.ToLower(apiRoute.Domain)); err != nil { + return firstRoundRoutes, fmt.Errorf("Route %s in API defined routes is not valid: %v", apiRoute.Domain, err) } - // handle hsts here - if aRoute.HSTSEnabled != nil { - add.HSTSEnabled = aRoute.HSTSEnabled - } - if aRoute.HSTSIncludeSubdomains != nil { - add.HSTSIncludeSubdomains = aRoute.HSTSIncludeSubdomains - } - if aRoute.HSTSPreload != nil { - add.HSTSPreload = aRoute.HSTSPreload - } - if aRoute.HSTSMaxAge > 0 { - add.HSTSMaxAge = aRoute.HSTSMaxAge - } else { - if add.HSTSEnabled != nil && *add.HSTSEnabled { - add.HSTSMaxAge = defaultHSTSMaxAge // set default hsts value if one not provided - } + routeAdd, err := handleAPIRoute(defaultIngressClass, apiRoute) + if err != nil { + return firstRoundRoutes, err } - // hsts end - for _, route := range finalRoutes.Routes { - if aRoute.Domain == route.Domain { + for _, route := range firstRoundRoutes.Routes { + if apiRoute.Domain == route.Domain { existsInAPI = true } } if existsInAPI { existsInAPI = false } else { - finalRoutes.Routes = append(finalRoutes.Routes, add) + firstRoundRoutes.Routes = append(firstRoundRoutes.Routes, routeAdd) } } - finalRoutes2 := RoutesV2{} - for _, fRoute := range finalRoutes.Routes { + + // generate the final routes to provide back as "the" route list for this environment + finalRoutes := RoutesV2{} + for _, fRoute := range firstRoundRoutes.Routes { // generate the fastly configuration for this route if required err := GenerateFastlyConfiguration(&fRoute.Fastly, "", fRoute.Fastly.ServiceID, fRoute.Domain, secretPrefix, variables) if err != nil { //@TODO: error handling } - finalRoutes2.Routes = append(finalRoutes2.Routes, fRoute) + fRoute.Domain = strings.ToLower(fRoute.Domain) + finalRoutes.Routes = append(finalRoutes.Routes, fRoute) + } + return finalRoutes, nil +} + +// handleAPIRoute handles setting the defaults for API defined routes +// main lagoon.yml defaults are handled in `GenerateRoutesV2` function +func handleAPIRoute(defaultIngressClass string, apiRoute RouteV2) (RouteV2, error) { + routeAdd := apiRoute + // copy in the apiroute fastly configuration + routeAdd.Fastly = apiRoute.Fastly + if apiRoute.TLSAcme != nil { + routeAdd.TLSAcme = apiRoute.TLSAcme + } else { + routeAdd.TLSAcme = defaultTLSAcme + } + if apiRoute.Insecure != nil { + routeAdd.Insecure = apiRoute.Insecure + } else { + routeAdd.Insecure = defaultInsecure + } + if apiRoute.Annotations != nil { + routeAdd.Annotations = apiRoute.Annotations + } else { + routeAdd.Annotations = defaultAnnotations + } + if apiRoute.AlternativeNames != nil { + routeAdd.AlternativeNames = apiRoute.AlternativeNames + } else { + routeAdd.AlternativeNames = []string{} + } + if apiRoute.IngressClass != "" { + routeAdd.IngressClass = apiRoute.IngressClass + } else { + routeAdd.IngressClass = defaultIngressClass + } + + // handle hsts here + if apiRoute.HSTSEnabled != nil { + routeAdd.HSTSEnabled = apiRoute.HSTSEnabled + } + if apiRoute.HSTSIncludeSubdomains != nil { + routeAdd.HSTSIncludeSubdomains = apiRoute.HSTSIncludeSubdomains + } + if apiRoute.HSTSPreload != nil { + routeAdd.HSTSPreload = apiRoute.HSTSPreload + } + if apiRoute.HSTSMaxAge > 0 { + routeAdd.HSTSMaxAge = apiRoute.HSTSMaxAge + } else { + if routeAdd.HSTSEnabled != nil && *routeAdd.HSTSEnabled { + routeAdd.HSTSMaxAge = defaultHSTSMaxAge // set default hsts value if one not provided + } + } + // hsts end + + // handle wildcards + if apiRoute.Wildcard != nil { + routeAdd.Wildcard = apiRoute.Wildcard + if *routeAdd.TLSAcme == true && *routeAdd.Wildcard == true { + return routeAdd, fmt.Errorf("Route %s has wildcard=true and tls-acme=true, this is not supported", routeAdd.Domain) + } + if apiRoute.AlternativeNames != nil && *routeAdd.Wildcard == true { + return routeAdd, fmt.Errorf("Route %s has wildcard=true and alternativenames defined, this is not supported", routeAdd.Domain) + } } - return finalRoutes2 + return routeAdd, nil } diff --git a/internal/lagoon/routes_test.go b/internal/lagoon/routes_test.go index 74db4f2c..d2ff9be5 100644 --- a/internal/lagoon/routes_test.go +++ b/internal/lagoon/routes_test.go @@ -11,23 +11,24 @@ import ( func TestGenerateRouteStructure(t *testing.T) { type args struct { - genRoutes *RoutesV2 - routeMap map[string][]Route + yamlRoutes *RoutesV2 + yamlRouteMap map[string][]Route variables []EnvironmentVariable defaultIngressClass string secretPrefix string activeStandby bool } tests := []struct { - name string - args args - want *RoutesV2 + name string + args args + want *RoutesV2 + wantErr bool }{ { name: "test1", args: args{ - genRoutes: &RoutesV2{}, - routeMap: map[string][]Route{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ "nginx": { { Name: "example.com", @@ -52,6 +53,7 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, { Domain: "www.example.com", @@ -63,6 +65,7 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, }, }, @@ -70,8 +73,8 @@ func TestGenerateRouteStructure(t *testing.T) { { name: "test2", args: args{ - genRoutes: &RoutesV2{}, - routeMap: map[string][]Route{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ "nginx": { { Name: "example.com", @@ -104,6 +107,7 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, { Domain: "www.example.com", @@ -117,15 +121,64 @@ func TestGenerateRouteStructure(t *testing.T) { Watch: true, ServiceID: "12345", }, + AlternativeNames: []string{}, }, }, }, }, { - name: "test3 - ingress class", + name: "test3", args: args{ - genRoutes: &RoutesV2{}, - routeMap: map[string][]Route{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ + "nginx": { + { + Ingresses: map[string]Ingress{ + "example.com": { + Fastly: Fastly{ + APISecretName: "annotationscom", + Watch: true, + ServiceID: "12345", + }, + AlternativeNames: []string{ + "www.example.com", + "en.example.com", + }, + }, + }, + }, + }, + }, + secretPrefix: "fastly-api-", + activeStandby: false, + }, + want: &RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Fastly: Fastly{ + APISecretName: "fastly-api-annotationscom", + Watch: true, + ServiceID: "12345", + }, + AlternativeNames: []string{ + "www.example.com", + "en.example.com", + }, + }, + }, + }, + }, + { + name: "test4 - ingress class", + args: args{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ "nginx": { { Name: "example.com", @@ -152,6 +205,7 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, { Domain: "www.example.com", @@ -164,15 +218,16 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, }, }, }, { - name: "test4 - custom ingress class on one route", + name: "test5 - custom ingress class on one route", args: args{ - genRoutes: &RoutesV2{}, - routeMap: map[string][]Route{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ "nginx": { { Name: "example.com", @@ -208,6 +263,7 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, { Domain: "www.example.com", @@ -222,15 +278,16 @@ func TestGenerateRouteStructure(t *testing.T) { Watch: true, ServiceID: "12345", }, + AlternativeNames: []string{}, }, }, }, }, { - name: "test5 - hsts", + name: "test6 - hsts", args: args{ - genRoutes: &RoutesV2{}, - routeMap: map[string][]Route{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ "nginx": { { Name: "example.com", @@ -265,6 +322,7 @@ func TestGenerateRouteStructure(t *testing.T) { Fastly: Fastly{ Watch: false, }, + AlternativeNames: []string{}, }, { Domain: "www.example.com", @@ -278,8 +336,67 @@ func TestGenerateRouteStructure(t *testing.T) { Watch: true, ServiceID: "12345", }, - HSTSEnabled: helpers.BoolPtr(true), - HSTSMaxAge: 10000, + HSTSEnabled: helpers.BoolPtr(true), + HSTSMaxAge: 10000, + AlternativeNames: []string{}, + }, + }, + }, + }, + { + name: "test7 - wildcard with tls-acme true (should error)", + args: args{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ + "nginx": { + { + Ingresses: map[string]Ingress{ + "www.example.com": { + TLSAcme: helpers.BoolPtr(true), + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + }, + secretPrefix: "fastly-api-", + activeStandby: false, + }, + wantErr: true, + want: &RoutesV2{ + Routes: nil, + }, + }, + { + name: "test7 - wildcard with tls-acme false", + args: args{ + yamlRoutes: &RoutesV2{}, + yamlRouteMap: map[string][]Route{ + "nginx": { + { + Ingresses: map[string]Ingress{ + "www.example.com": { + TLSAcme: helpers.BoolPtr(false), + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + }, + secretPrefix: "fastly-api-", + activeStandby: false, + }, + want: &RoutesV2{ + Routes: []RouteV2{ + { + Domain: "www.example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + AlternativeNames: []string{}, + Wildcard: helpers.BoolPtr(true), }, }, }, @@ -287,9 +404,13 @@ func TestGenerateRouteStructure(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - GenerateRoutesV2(tt.args.genRoutes, tt.args.routeMap, tt.args.variables, tt.args.defaultIngressClass, tt.args.secretPrefix, tt.args.activeStandby) - if !cmp.Equal(tt.args.genRoutes, tt.want) { - stra, _ := json.Marshal(tt.args.genRoutes) + err := GenerateRoutesV2(tt.args.yamlRoutes, tt.args.yamlRouteMap, tt.args.variables, tt.args.defaultIngressClass, tt.args.secretPrefix, tt.args.activeStandby) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateRouteStructure() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !cmp.Equal(tt.args.yamlRoutes, tt.want) && !tt.wantErr { + stra, _ := json.Marshal(tt.args.yamlRoutes) strb, _ := json.Marshal(tt.want) t.Errorf("GenerateRouteStructure() = %v, want %v", string(stra), string(strb)) } @@ -299,21 +420,22 @@ func TestGenerateRouteStructure(t *testing.T) { func TestMergeRouteStructures(t *testing.T) { type args struct { - genRoutes RoutesV2 + yamlRoutes RoutesV2 apiRoutes RoutesV2 variables []EnvironmentVariable defaultIngressClass string secretPrefix string } tests := []struct { - name string - args args - want RoutesV2 + name string + args args + want RoutesV2 + wantErr bool }{ { name: "test1", args: args{ - genRoutes: RoutesV2{ + yamlRoutes: RoutesV2{ Routes: []RouteV2{ { Domain: "example.com", @@ -396,6 +518,7 @@ func TestMergeRouteStructures(t *testing.T) { ServiceID: "12345", APISecretName: "fastly-api-annotationscom", }, + AlternativeNames: []string{}, }, { Domain: "www.example.com", @@ -406,32 +529,222 @@ func TestMergeRouteStructures(t *testing.T) { Annotations: map[string]string{ "nginx": "nginx", }, + AlternativeNames: []string{}, }, { - Domain: "hsts.example.com", - LagoonService: "nginx", - MonitoringPath: "/", - Insecure: helpers.StrPtr("Redirect"), - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, - HSTSEnabled: helpers.BoolPtr(true), - HSTSMaxAge: 10000, + Domain: "hsts.example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + HSTSEnabled: helpers.BoolPtr(true), + HSTSMaxAge: 10000, + AlternativeNames: []string{}, }, { - Domain: "another.example.com", - LagoonService: "nginx", - MonitoringPath: "/", - Insecure: helpers.StrPtr("Redirect"), - TLSAcme: helpers.BoolPtr(true), - Annotations: map[string]string{}, + Domain: "another.example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + AlternativeNames: []string{}, + }, + }, + }, + }, + { + name: "test2 - wildcard with tls-acme changed to false", + args: args{ + yamlRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + { + Domain: "a.example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + apiRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + { + Domain: "a.example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + secretPrefix: "fastly-api-", + }, + want: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + AlternativeNames: []string{}, + Wildcard: helpers.BoolPtr(true), + }, + { + Domain: "a.example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(false), + Annotations: map[string]string{}, + AlternativeNames: []string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + }, + { + name: "test3 - wildcard with tls-acme true (should error)", + args: args{ + yamlRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + apiRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, }, }, + secretPrefix: "fastly-api-", + }, + wantErr: true, + want: RoutesV2{ + Routes: nil, + }, + }, + { + name: "test4 - invalid yaml route", + args: args{ + yamlRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "*._re/f#3safasF*.was_-..asfexample.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + apiRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "fail@example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + secretPrefix: "fastly-api-", + }, + wantErr: true, + want: RoutesV2{ + Routes: nil, + }, + }, + { + name: "test5 - invalid api route", + args: args{ + yamlRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + apiRoutes: RoutesV2{ + Routes: []RouteV2{ + { + Domain: "fail@example.com", + LagoonService: "nginx", + MonitoringPath: "/", + Insecure: helpers.StrPtr("Redirect"), + TLSAcme: helpers.BoolPtr(true), + Annotations: map[string]string{}, + Wildcard: helpers.BoolPtr(true), + }, + }, + }, + secretPrefix: "fastly-api-", + }, + wantErr: true, + want: RoutesV2{ + Routes: nil, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := MergeRoutesV2(tt.args.genRoutes, tt.args.apiRoutes, tt.args.variables, tt.args.defaultIngressClass, tt.args.secretPrefix); !reflect.DeepEqual(got, tt.want) { + got, err := MergeRoutesV2(tt.args.yamlRoutes, tt.args.apiRoutes, tt.args.variables, tt.args.defaultIngressClass, tt.args.secretPrefix) + if (err != nil) != tt.wantErr { + t.Errorf("MergeRouteStructures() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) && !tt.wantErr { stra, _ := json.Marshal(got) strb, _ := json.Marshal(tt.want) t.Errorf("MergeRouteStructures() = %v, want %v", string(stra), string(strb)) diff --git a/internal/lagoon/tasks.go b/internal/lagoon/tasks.go index b7ad721d..b2ec5201 100644 --- a/internal/lagoon/tasks.go +++ b/internal/lagoon/tasks.go @@ -1,11 +1,10 @@ package lagoon import ( - "bytes" "context" "errors" "fmt" - "io/ioutil" + "os" "strconv" "time" @@ -85,9 +84,9 @@ func getConfig() (*rest.Config, error) { if *kubeconfig == "" { //Fall back on out of cluster // read the deployer token. - token, err := ioutil.ReadFile("/var/run/secrets/lagoon/deployer/token") + token, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") if err != nil { - token, err = ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") + token, err = os.ReadFile("/var/run/secrets/lagoon/deployer/token") if err != nil { return nil, err } @@ -110,7 +109,7 @@ func getConfig() (*rest.Config, error) { } // ExecuteTaskInEnvironment . -func ExecuteTaskInEnvironment(task Task) error { +func ExecuteTaskInEnvironment(task Task, prePost string) error { command := make([]string, 0, 5) if task.Shell != "" { command = append(command, task.Shell) @@ -121,18 +120,19 @@ func ExecuteTaskInEnvironment(task Task) error { command = append(command, "-c") command = append(command, task.Command) - stdout, stderr, err := ExecTaskInPod(task, command, false) //(task.Service, task.Namespace, command, false, task.Container, task.ScaleWaitTime, task.ScaleMaxIterations) + fmt.Printf("##############################################\nBEGIN %s %s\n##############################################\n", prePost, task.Name) + st := time.Now() + + err := ExecTaskInPod(task, command, false) //(task.Service, task.Namespace, command, false, task.Container, task.ScaleWaitTime, task.ScaleMaxIterations) if err != nil { fmt.Printf("Failed to execute task `%v` due to reason `%v`\n", task.Name, err.Error()) } - if len(stdout) > 0 { - fmt.Printf("*** Task STDOUT ***\n %v \n *** STDOUT Ends ***\n", stdout) - } - if len(stderr) > 0 { - fmt.Printf("*** Task STDERR ***\n %v \n *** STDERR Ends ***\n", stderr) - } + et := time.Now() + diff := time.Time{}.Add(et.Sub(st)) + tz, _ := et.Zone() + fmt.Printf("##############################################\nSTEP %s %s: Completed at %s (%s) Duration %s Elapsed %s\n##############################################\n", prePost, task.Name, et.Format("2006-01-02 15:04:05"), tz, diff.Format("15:04:05"), diff.Format("15:04:05")) return err } @@ -142,16 +142,16 @@ func ExecTaskInPod( task Task, command []string, tty bool, -) (string, string, error) { +) error { restCfg, err := getConfig() if err != nil { - return "", "", err + return err } clientset, err := GetK8sClient(restCfg) if err != nil { - return "", "", fmt.Errorf("unable to create client: %v", err) + return fmt.Errorf("unable to create client: %v", err) } depClient := clientset.AppsV1().Deployments(task.Namespace) @@ -162,11 +162,11 @@ func ExecTaskInPod( LabelSelector: lagoonServiceLabel, }) if err != nil { - return "", "", err + return err } if len(deployments.Items) == 0 { - return "", "", &DeploymentMissingError{ErrorText: "No deployments found matching label: " + lagoonServiceLabel} + return &DeploymentMissingError{ErrorText: "No deployments found matching label: " + lagoonServiceLabel} } deployment := &deployments.Items[0] @@ -176,14 +176,14 @@ func ExecTaskInPod( numIterations := 1 for ; !podReady; numIterations++ { if numIterations >= task.ScaleMaxIterations { //break if there's some reason we can't scale the pod - return "", "", errors.New("Failed to scale pods for " + deployment.Name) + return errors.New("Failed to scale pods for " + deployment.Name) } if deployment.Status.ReadyReplicas == 0 { fmt.Println(fmt.Sprintf("No ready replicas found, scaling up. Attempt %d/%d", numIterations, task.ScaleMaxIterations)) scale, err := clientset.AppsV1().Deployments(task.Namespace).GetScale(context.TODO(), deployment.Name, v1.GetOptions{}) if err != nil { - return "", "", err + return err } if scale.Spec.Replicas == 0 { @@ -193,7 +193,7 @@ func ExecTaskInPod( time.Sleep(time.Second * time.Duration(task.ScaleWaitTime)) deployment, err = depClient.Get(context.TODO(), deployment.Name, v1.GetOptions{}) if err != nil { - return "", "", err + return err } } else { podReady = true @@ -208,7 +208,7 @@ func ExecTaskInPod( }) if err != nil { - return "", "", err + return err } var pod corev1.Pod @@ -229,7 +229,7 @@ func ExecTaskInPod( } } if !foundRunningPod { - return "", "", &PodScalingError{ + return &PodScalingError{ ErrorText: "Unable to find running Pod for namespace: " + task.Namespace, } } @@ -250,7 +250,7 @@ func ExecTaskInPod( scheme := runtime.NewScheme() if err := corev1.AddToScheme(scheme); err != nil { - return "", "", fmt.Errorf("error adding to scheme: %v", err) + return fmt.Errorf("error adding to scheme: %v", err) } if len(command) == 0 { command = []string{"sh"} @@ -267,20 +267,19 @@ func ExecTaskInPod( exec, err := remotecommand.NewSPDYExecutor(restCfg, "POST", req.URL()) if err != nil { - return "", "", fmt.Errorf("error while creating Executor: %v", err) + return fmt.Errorf("error while creating Executor: %v", err) } - var stdout, stderr bytes.Buffer err = exec.Stream(remotecommand.StreamOptions{ - Stdout: &stdout, - Stderr: &stderr, + Stdout: os.Stdout, + Stderr: os.Stderr, Tty: tty, }) if err != nil { - return stdout.String(), stderr.String(), fmt.Errorf("Error returned: %v", err) + return fmt.Errorf("Error returned: %v", err) } - return stdout.String(), stderr.String(), nil + return nil } diff --git a/internal/templating/ingress/templates_ingress.go b/internal/templating/ingress/templates_ingress.go index 2fee60c9..88bb863b 100644 --- a/internal/templating/ingress/templates_ingress.go +++ b/internal/templating/ingress/templates_ingress.go @@ -25,20 +25,14 @@ func GenerateIngressTemplate( lValues generator.BuildValues, ) ([]byte, error) { - // lowercase any domains then validate them - routeDomain := strings.ToLower(route.Domain) - if err := validation.IsDNS1123Subdomain(strings.ToLower(routeDomain)); err != nil { - return nil, fmt.Errorf("the provided domain name %s is not valid: %v", route.Domain, err) - } - // truncate the route for use in labels and secretname - truncatedRouteDomain := routeDomain + truncatedRouteDomain := route.Domain if len(truncatedRouteDomain) >= 53 { subdomain := strings.Split(truncatedRouteDomain, ".")[0] if errs := utilvalidation.IsValidLabelValue(subdomain); errs != nil { subdomain = subdomain[:53] } - truncatedRouteDomain = fmt.Sprintf("%s-%s", strings.Split(subdomain, ".")[0], helpers.GetMD5HashWithNewLine(routeDomain)[:5]) + truncatedRouteDomain = fmt.Sprintf("%s-%s", strings.Split(subdomain, ".")[0], helpers.GetMD5HashWithNewLine(route.Domain)[:5]) } // create the ingress object for templating @@ -47,11 +41,32 @@ func GenerateIngressTemplate( Kind: "Ingress", APIVersion: "networking.k8s.io/v1", } - ingress.ObjectMeta.Name = routeDomain + ingress.ObjectMeta.Name = route.Domain if route.Autogenerated { // autogenerated routes just have the service name ingress.ObjectMeta.Name = route.LagoonService } + + // if this is a wildcard ingress, handle templating that here + if route.Wildcard != nil && *route.Wildcard == true { + // prefix the object name with wildcard + ingress.ObjectMeta.Name = fmt.Sprintf("wildcard-%s", ingress.ObjectMeta.Name) + // if the new name exceeds the validation spec, truncate it + if err := validation.IsDNS1123Subdomain(strings.ToLower(ingress.ObjectMeta.Name)); err != nil { + ingress.ObjectMeta.Name = fmt.Sprintf("%s-%s", ingress.ObjectMeta.Name[:len(ingress.ObjectMeta.Name)-10], helpers.GetMD5HashWithNewLine(route.Domain)[:5]) + } + truncatedRouteDomain = fmt.Sprintf("wildcard-%s", truncatedRouteDomain) + if len(truncatedRouteDomain) >= 53 { + subdomain := strings.Split(truncatedRouteDomain, "-")[0] + if errs := utilvalidation.IsValidLabelValue(subdomain); errs != nil { + subdomain = subdomain[:53] + } + truncatedRouteDomain = fmt.Sprintf("%s-%s", strings.Split(subdomain, "-")[0], helpers.GetMD5HashWithNewLine(route.Domain)[:5]) + } + // set the domain to include the wildcard prefix + route.Domain = fmt.Sprintf("*.%s", route.Domain) + } + // add the default labels ingress.ObjectMeta.Labels = map[string]string{ "lagoon.sh/autogenerated": "false", @@ -89,7 +104,7 @@ func GenerateIngressTemplate( primaryIngress, _ := url.Parse(lValues.Route) // check if monitoring enabled, route isn't autogenerated, and the primary ingress from the .lagoon.yml is this processed routedomain // and enable monitoring on the primary ingress only. - if lValues.Monitoring.Enabled && !route.Autogenerated && primaryIngress.Host == routeDomain { + if lValues.Monitoring.Enabled && !route.Autogenerated && primaryIngress.Host == route.Domain { additionalLabels["lagoon.sh/primaryIngress"] = "true" // only add the monitring annotations if monitoring is enabled @@ -185,13 +200,13 @@ func GenerateIngressTemplate( // validate any annotations if err := apivalidation.ValidateAnnotations(ingress.ObjectMeta.Annotations, nil); err != nil { if len(err) != 0 { - return nil, fmt.Errorf("the annotations for %s are not valid: %v", routeDomain, err) + return nil, fmt.Errorf("the annotations for %s are not valid: %v", route.Domain, err) } } // validate any labels if err := metavalidation.ValidateLabels(ingress.ObjectMeta.Labels, nil); err != nil { if len(err) != 0 { - return nil, fmt.Errorf("the labels for %s are not valid: %v", routeDomain, err) + return nil, fmt.Errorf("the labels for %s are not valid: %v", route.Domain, err) } } @@ -220,13 +235,13 @@ func GenerateIngressTemplate( // use the compose service name to check this, as this is how Services are populated from the compose generation for _, service := range lValues.Services { if service.Name == route.ComposeService { - if service.ShortAutogeneratedRouteDomain != "" && len(routeDomain) > 63 { + if service.ShortAutogeneratedRouteDomain != "" && len(route.Domain) > 63 { ingress.Spec.TLS[0].Hosts = append(ingress.Spec.TLS[0].Hosts, service.ShortAutogeneratedRouteDomain) } } } // add the main domain to the tls spec now - ingress.Spec.TLS[0].Hosts = append(ingress.Spec.TLS[0].Hosts, routeDomain) + ingress.Spec.TLS[0].Hosts = append(ingress.Spec.TLS[0].Hosts, route.Domain) // default service port is http in all lagoon deployments servicePort := networkv1.ServiceBackendPort{ @@ -251,7 +266,7 @@ func GenerateIngressTemplate( // add the main domain as the first rule in the spec ingress.Spec.Rules = []networkv1.IngressRule{ { - Host: routeDomain, + Host: route.Domain, IngressRuleValue: networkv1.IngressRuleValue{ HTTP: &networkv1.HTTPIngressRuleValue{ Paths: []networkv1.HTTPIngressPath{ diff --git a/internal/templating/ingress/templates_ingress_test.go b/internal/templating/ingress/templates_ingress_test.go index 5c290ace..8f2c4cd7 100644 --- a/internal/templating/ingress/templates_ingress_test.go +++ b/internal/templating/ingress/templates_ingress_test.go @@ -257,10 +257,10 @@ func TestGenerateKubeTemplate(t *testing.T) { want: "test-resources/result-custom-ingress5.yaml", }, { - name: "test6 - invalid domain", + name: "test6 - invalid annotation", args: args{ route: lagoon.RouteV2{ - Domain: "fail@.extra-long-name.a-really-long-name-that-should-truncate.www.example.com", + Domain: "extra-long-name.a-really-long-name-that-should-truncate.www.example.com", LagoonService: "nginx", MonitoringPath: "/", Insecure: helpers.StrPtr("Redirect"), @@ -268,6 +268,7 @@ func TestGenerateKubeTemplate(t *testing.T) { Migrate: helpers.BoolPtr(false), Annotations: map[string]string{ "custom-annotation": "custom annotation value", + "@invalid": "this is an invalid annotation", }, Fastly: lagoon.Fastly{ Watch: false, @@ -294,7 +295,7 @@ func TestGenerateKubeTemplate(t *testing.T) { wantErr: true, }, { - name: "test7 - invalid annotation", + name: "test7 - invalid label", args: args{ route: lagoon.RouteV2{ Domain: "extra-long-name.a-really-long-name-that-should-truncate.www.example.com", @@ -305,7 +306,9 @@ func TestGenerateKubeTemplate(t *testing.T) { Migrate: helpers.BoolPtr(false), Annotations: map[string]string{ "custom-annotation": "custom annotation value", - "@invalid": "this is an invalid annotation", + }, + Labels: map[string]string{ + "@invalid": "this is an invalid annotation", }, Fastly: lagoon.Fastly{ Watch: false, @@ -332,10 +335,10 @@ func TestGenerateKubeTemplate(t *testing.T) { wantErr: true, }, { - name: "test8 - invalid label", + name: "test8 - custom ingress with exceptionally long subdomain", args: args{ route: lagoon.RouteV2{ - Domain: "extra-long-name.a-really-long-name-that-should-truncate.www.example.com", + Domain: "hmm-this-is-a-really-long-branch-name-designed-to-test-a-specific-feature.www.example.com", LagoonService: "nginx", MonitoringPath: "/", Insecure: helpers.StrPtr("Redirect"), @@ -344,9 +347,6 @@ func TestGenerateKubeTemplate(t *testing.T) { Annotations: map[string]string{ "custom-annotation": "custom annotation value", }, - Labels: map[string]string{ - "@invalid": "this is an invalid annotation", - }, Fastly: lagoon.Fastly{ Watch: false, }, @@ -369,17 +369,17 @@ func TestGenerateKubeTemplate(t *testing.T) { }, activeStandby: false, }, - wantErr: true, + want: "test-resources/result-custom-ingress6.yaml", }, { - name: "test9 - too long domain", + name: "test9 - wildcard ingress", args: args{ route: lagoon.RouteV2{ - Domain: "extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.www.example.com", + Domain: "www.example.com", LagoonService: "nginx", MonitoringPath: "/", Insecure: helpers.StrPtr("Redirect"), - TLSAcme: helpers.BoolPtr(true), + TLSAcme: helpers.BoolPtr(false), Migrate: helpers.BoolPtr(false), Annotations: map[string]string{ "custom-annotation": "custom annotation value", @@ -388,16 +388,17 @@ func TestGenerateKubeTemplate(t *testing.T) { Watch: false, }, IngressClass: "nginx", + Wildcard: helpers.BoolPtr(true), }, values: generator.BuildValues{ Project: "example-project", - Environment: "environment-with-really-really-reall-3fdb", + Environment: "environment", EnvironmentType: "development", - Namespace: "myexample-project-environment-with-really-really-reall-3fdb", + Namespace: "myexample-project-environment", BuildType: "branch", LagoonVersion: "v2.x.x", Kubernetes: "lagoon.local", - Branch: "environment-with-really-really-reall-3fdb", + Branch: "environment", Monitoring: generator.MonitoringConfig{ AlertContact: "abcdefg", StatusPageID: "12345", @@ -406,17 +407,17 @@ func TestGenerateKubeTemplate(t *testing.T) { }, activeStandby: false, }, - wantErr: true, + want: "test-resources/result-wildcard-ingress1.yaml", }, { - name: "test10 - custom ingress with exceptionally long subdomain", + name: "test10 - wildcard ingress", args: args{ route: lagoon.RouteV2{ - Domain: "hmm-this-is-a-really-long-branch-name-designed-to-test-a-specific-feature.www.example.com", + Domain: "this-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.www.example.com", LagoonService: "nginx", MonitoringPath: "/", Insecure: helpers.StrPtr("Redirect"), - TLSAcme: helpers.BoolPtr(true), + TLSAcme: helpers.BoolPtr(false), Migrate: helpers.BoolPtr(false), Annotations: map[string]string{ "custom-annotation": "custom annotation value", @@ -425,16 +426,17 @@ func TestGenerateKubeTemplate(t *testing.T) { Watch: false, }, IngressClass: "nginx", + Wildcard: helpers.BoolPtr(true), }, values: generator.BuildValues{ Project: "example-project", - Environment: "environment-with-really-really-reall-3fdb", + Environment: "environment", EnvironmentType: "development", - Namespace: "myexample-project-environment-with-really-really-reall-3fdb", + Namespace: "myexample-project-environment", BuildType: "branch", LagoonVersion: "v2.x.x", Kubernetes: "lagoon.local", - Branch: "environment-with-really-really-reall-3fdb", + Branch: "environment", Monitoring: generator.MonitoringConfig{ AlertContact: "abcdefg", StatusPageID: "12345", @@ -443,7 +445,7 @@ func TestGenerateKubeTemplate(t *testing.T) { }, activeStandby: false, }, - want: "test-resources/result-custom-ingress6.yaml", + want: "test-resources/result-wildcard-ingress2.yaml", }, } for _, tt := range tests { diff --git a/internal/templating/ingress/test-resources/result-wildcard-ingress1.yaml b/internal/templating/ingress/test-resources/result-wildcard-ingress1.yaml new file mode 100644 index 00000000..10d657ac --- /dev/null +++ b/internal/templating/ingress/test-resources/result-wildcard-ingress1.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + acme.cert-manager.io/http01-ingress-class: nginx + custom-annotation: custom annotation value + fastly.amazee.io/watch: "false" + ingress.kubernetes.io/ssl-redirect: "true" + kubernetes.io/tls-acme: "false" + lagoon.sh/branch: environment + lagoon.sh/version: v2.x.x + nginx.ingress.kubernetes.io/server-snippet: | + add_header X-Robots-Tag "noindex, nofollow"; + nginx.ingress.kubernetes.io/ssl-redirect: "true" + creationTimestamp: null + labels: + app.kubernetes.io/instance: wildcard-www.example.com + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: custom-ingress + helm.sh/chart: custom-ingress-0.1.0 + lagoon.sh/autogenerated: "false" + lagoon.sh/buildType: branch + lagoon.sh/environment: environment + lagoon.sh/environmentType: development + lagoon.sh/project: example-project + lagoon.sh/service: wildcard-www.example.com + lagoon.sh/service-type: custom-ingress + name: wildcard-www.example.com +spec: + ingressClassName: nginx + rules: + - host: '*.www.example.com' + http: + paths: + - backend: + service: + name: nginx + port: + name: http + path: / + pathType: Prefix + tls: + - hosts: + - '*.www.example.com' + secretName: wildcard-www.example.com-tls +status: + loadBalancer: {} diff --git a/internal/templating/ingress/test-resources/result-wildcard-ingress2.yaml b/internal/templating/ingress/test-resources/result-wildcard-ingress2.yaml new file mode 100644 index 00000000..8dc4e2b3 --- /dev/null +++ b/internal/templating/ingress/test-resources/result-wildcard-ingress2.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + acme.cert-manager.io/http01-ingress-class: nginx + custom-annotation: custom annotation value + fastly.amazee.io/watch: "false" + ingress.kubernetes.io/ssl-redirect: "true" + kubernetes.io/tls-acme: "false" + lagoon.sh/branch: environment + lagoon.sh/version: v2.x.x + nginx.ingress.kubernetes.io/server-snippet: | + add_header X-Robots-Tag "noindex, nofollow"; + nginx.ingress.kubernetes.io/ssl-redirect: "true" + creationTimestamp: null + labels: + app.kubernetes.io/instance: wildcard-this-truncate-f1945 + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: custom-ingress + helm.sh/chart: custom-ingress-0.1.0 + lagoon.sh/autogenerated: "false" + lagoon.sh/buildType: branch + lagoon.sh/environment: environment + lagoon.sh/environmentType: development + lagoon.sh/project: example-project + lagoon.sh/service: wildcard-this-truncate-f1945 + lagoon.sh/service-type: custom-ingress + name: wildcard-this-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.www.e-f1945 +spec: + ingressClassName: nginx + rules: + - host: '*.this-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.www.example.com' + http: + paths: + - backend: + service: + name: nginx + port: + name: http + path: / + pathType: Prefix + tls: + - hosts: + - '*.this-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.extra-long-name.a-really-long-name-that-should-truncate.www.example.com' + secretName: wildcard-this-truncate-f1945-tls +status: + loadBalancer: {} diff --git a/legacy/Dockerfile.kubectl-build-deploy-dind b/legacy/Dockerfile.kubectl-build-deploy-dind index 6b422328..0b7724a7 100644 --- a/legacy/Dockerfile.kubectl-build-deploy-dind +++ b/legacy/Dockerfile.kubectl-build-deploy-dind @@ -18,8 +18,6 @@ COPY scripts /kubectl-build-deploy/scripts COPY helmcharts /kubectl-build-deploy/helmcharts -ENV IMAGECACHE_REGISTRY=imagecache.amazeeio.cloud - ENV DBAAS_OPERATOR_HTTP=dbaas.lagoon.svc:5000 RUN curl -sSL https://github.com/uselagoon/lagoon-linter/releases/download/v0.7.0/lagoon-linter_0.7.0_linux_amd64.tar.gz \ diff --git a/legacy/build-deploy-docker-compose.sh b/legacy/build-deploy-docker-compose.sh index 1421995e..10791684 100755 --- a/legacy/build-deploy-docker-compose.sh +++ b/legacy/build-deploy-docker-compose.sh @@ -99,10 +99,17 @@ set -x function beginBuildStep() { [ "$1" ] || return #Buildstep start + [ "$2" ] || return #buildstep echo -e "##############################################\nBEGIN ${1}\n##############################################" - sleep 0.5s + # patch the buildpod with the buildstep + if [ "${SCC_CHECK}" == false ]; then + kubectl patch -n ${NAMESPACE} pod ${LAGOON_BUILD_NAME} \ + -p "{\"metadata\":{\"labels\":{\"lagoon.sh/buildStep\":\"${2}\"}}}" &> /dev/null + # tiny sleep to allow patch to complete before logs roll again + sleep 0.5s + fi } function patchBuildStep() { @@ -124,23 +131,15 @@ function patchBuildStep() { diffTotalTime=$(date -d @${diffTotalSeconds} +"%H:%M:%S" -u) echo -e "##############################################\nSTEP ${6}: Completed at ${3} (${timeZone}) Duration ${diffTime} Elapsed ${diffTotalTime}\n##############################################" - - # patch the buildpod with the buildstep - if [ "${SCC_CHECK}" == false ]; then - kubectl patch -n ${4} pod ${LAGOON_BUILD_NAME} \ - -p "{\"metadata\":{\"labels\":{\"lagoon.sh/buildStep\":\"${5}\"}}}" &> /dev/null - - # tiny sleep to allow patch to complete before logs roll again - sleep 0.5s - fi } + ############################################## ### PREPARATION ############################################## set +x buildStartTime="$(date +"%Y-%m-%d %H:%M:%S")" -beginBuildStep "Initial Environment Setup" +beginBuildStep "Initial Environment Setup" "initialSetup" echo "STEP: Preparation started ${buildStartTime}" set -x @@ -150,6 +149,16 @@ set -x set +x +# set the imagecache registry if it is provided +IMAGECACHE_REGISTRY="" +if [ ! -z "$(featureFlag IMAGECACHE_REGISTRY)" ]; then + IMAGECACHE_REGISTRY=$(featureFlag IMAGECACHE_REGISTRY) + # add trailing slash if it is missing + length=${#IMAGECACHE_REGISTRY} + last_char=${IMAGECACHE_REGISTRY:length-1:1} + [[ $last_char != "/" ]] && IMAGECACHE_REGISTRY="$IMAGECACHE_REGISTRY/"; : +fi + # Load path of docker-compose that should be used DOCKER_COMPOSE_YAML=($(cat .lagoon.yml | shyaml get-value docker-compose-yaml)) @@ -256,7 +265,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${buildStartTime}" "${currentStepEnd}" "${NAMESPACE}" "initialSetup" "Initial Environment Setup" previousStepEnd=${currentStepEnd} -beginBuildStep "Configure Variables" +beginBuildStep "Configure Variables" "configuringVariables" set -x DEPLOY_TYPE=$(cat .lagoon.yml | shyaml get-value environments.${BRANCH//./\\.}.deploy-type default) @@ -571,7 +580,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${buildStartTime}" "${currentStepEnd}" "${NAMESPACE}" "configureVars" "Configure Variables" previousStepEnd=${currentStepEnd} -beginBuildStep "Image Builds" +beginBuildStep "Image Builds" "buildingImages" set -x ############################################## ### CACHE IMAGE LIST GENERATION @@ -796,7 +805,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "imageBuildComplete" "Image Builds" previousStepEnd=${currentStepEnd} -beginBuildStep "Pre-Rollout Tasks" +beginBuildStep "Pre-Rollout Tasks" "runningPreRolloutTasks" set -x ############################################## @@ -807,13 +816,16 @@ if [ "${LAGOON_PREROLLOUT_DISABLED}" != "true" ]; then build-deploy-tool tasks pre-rollout else echo "pre-rollout tasks are currently disabled LAGOON_PREROLLOUT_DISABLED is set to true" + set +x + currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" + patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "preRolloutsCompleted" "Pre-Rollout Tasks" + set -x fi set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" -patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "preRolloutsCompleted" "Pre-Rollout Tasks" previousStepEnd=${currentStepEnd} -beginBuildStep "Service Configuration Phase 1" +beginBuildStep "Service Configuration Phase 1" "serviceConfigurationPhase1" set -x @@ -857,7 +869,9 @@ set +x if [ "$(featureFlag ROOTLESS_WORKLOAD)" = enabled ]; then yq3 merge -ix -- /kubectl-build-deploy/values.yaml /kubectl-build-deploy/rootless.values.yaml fi - +if [ "$(featureFlag FS_ON_ROOT_MISMATCH)" = enabled ]; then + yq3 write -i -- /kubectl-build-deploy/values.yaml 'podSecurityContext.fsGroupChangePolicy' "OnRootMismatch" +fi if [ "${SCC_CHECK}" != "false" ]; then # openshift permissions are different, this is to set the fsgroup to the supplemental group from the openshift annotations # this applies it to all deployments in this environment because we don't isolate by service type its applied to all @@ -920,7 +934,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "serviceConfigurationComplete" "Service Configuration Phase 1" previousStepEnd=${currentStepEnd} -beginBuildStep "Service Configuration Phase 2" +beginBuildStep "Service Configuration Phase 2" "serviceConfigurationPhase2" set -x ############################################## @@ -1072,6 +1086,12 @@ if [ ! "$AUTOGEN_ROUTES_DISABLED" == true ]; then build-deploy-tool template autogenerated-ingress else echo ">> Autogenerated ingress templates disabled for this build" + # delete any autogenerated ingress in the namespace as they are disabled + if kubectl -n ${NAMESPACE} get ingress -l "lagoon.sh/autogenerated=true" &> /dev/null; then + echo ">> Removing any autogenerated ingress" + kubectl -n ${NAMESPACE} delete ingress -l "lagoon.sh/autogenerated=true" + echo ">> Autogenerated ingress removed" + fi # end custom route fi @@ -1098,6 +1118,9 @@ do # Load the requested class and plan for this service DBAAS_ENVIRONMENT="${MAP_SERVICE_NAME_TO_DBAAS_ENVIRONMENT["${SERVICE_NAME}"]}" yq3 write -i -- /kubectl-build-deploy/${SERVICE_NAME}-values.yaml 'environment' $DBAAS_ENVIRONMENT + if [ ! -z "$IMAGECACHE_REGISTRY" ]; then + yq3 write -i -- /kubectl-build-deploy/${SERVICE_NAME}-values.yaml 'imageCache' $IMAGECACHE_REGISTRY + fi helm template ${SERVICE_NAME} /kubectl-build-deploy/helmcharts/${SERVICE_TYPE} -s $HELM_DBAAS_TEMPLATE -f /kubectl-build-deploy/values.yaml -f /kubectl-build-deploy/${SERVICE_NAME}-values.yaml "${HELM_ARGUMENTS[@]}" > $YAML_FOLDER/service-${SERVICE_NAME}.yaml DBAAS+=("${SERVICE_NAME}:${SERVICE_TYPE}") fi @@ -1107,7 +1130,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "serviceConfiguration2Complete" "Service Configuration Phase 2" previousStepEnd=${currentStepEnd} -beginBuildStep "Route/Ingress Configuration" +beginBuildStep "Route/Ingress Configuration" "configuringRoutes" TEMPLATE_PARAMETERS=() @@ -1139,7 +1162,7 @@ fi currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "routeConfigurationComplete" "Route/Ingress Configuration" previousStepEnd=${currentStepEnd} -beginBuildStep "Backup Configuration" +beginBuildStep "Backup Configuration" "configuringBackups" # Run the backup generation script BACKUPS_DISABLED=false @@ -1177,7 +1200,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "backupConfigurationComplete" "Backup Configuration" previousStepEnd=${currentStepEnd} -beginBuildStep "Image Push to Registry" +beginBuildStep "Image Push to Registry" "pushingImages" set -x ############################################## @@ -1341,7 +1364,7 @@ if [ "$BUILD_TYPE" == "pullrequest" ] || [ "$BUILD_TYPE" == "branch" ]; then skopeo copy --retry-times 5 --dest-tls-verify=false docker://${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} # If image not from an external registry and no docker hub creds were supplied, pull image from the imagecache else - skopeo copy --retry-times 5 --dest-tls-verify=false docker://${IMAGECACHE_REGISTRY}/${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} + skopeo copy --retry-times 5 --dest-tls-verify=false docker://${IMAGECACHE_REGISTRY}${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} fi # If the private registry counter is 1 and no external registry was listed, we know a private docker hub was specified else @@ -1349,7 +1372,7 @@ if [ "$BUILD_TYPE" == "pullrequest" ] || [ "$BUILD_TYPE" == "branch" ]; then fi # If no private registries, use the imagecache else - skopeo copy --retry-times 5 --dest-tls-verify=false docker://${IMAGECACHE_REGISTRY}/${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} + skopeo copy --retry-times 5 --dest-tls-verify=false docker://${IMAGECACHE_REGISTRY}${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} fi IMAGE_HASHES[${IMAGE_NAME}]=$(skopeo inspect --retry-times 5 docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} --tls-verify=false | jq ".Name + \"@\" + .Digest" -r) @@ -1390,7 +1413,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "imagePushComplete" "Image Push to Registry" previousStepEnd=${currentStepEnd} -beginBuildStep "Deployment Templating" +beginBuildStep "Deployment Templating" "templatingDeployments" set -x ############################################## @@ -1542,7 +1565,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "deploymentTemplatingComplete" "Deployment Templating" previousStepEnd=${currentStepEnd} -beginBuildStep "Applying Deployments" +beginBuildStep "Applying Deployments" "applyingDeployments" set -x ############################################## @@ -1608,7 +1631,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "deploymentApplyComplete" "Applying Deployments" previousStepEnd=${currentStepEnd} -beginBuildStep "Cronjob Cleanup" +beginBuildStep "Cronjob Cleanup" "cleaningUpCronjobs" set -x ############################################## @@ -1621,7 +1644,7 @@ IFS=' ' read -a SPLIT_CURRENT_CRONJOBS <<< $CURRENT_CRONJOBS for SINGLE_NATIVE_CRONJOB in ${SPLIT_CURRENT_CRONJOBS[@]} do - re="\<$SINGLE_NATIVE_CRONJOB\>" + re="\<^$SINGLE_NATIVE_CRONJOB$\>" text=$( IFS=' '; echo "${NATIVE_CRONJOB_CLEANUP_ARRAY[*]}") if [[ "$text" =~ $re ]]; then #echo "Single cron found: ${SINGLE_NATIVE_CRONJOB}" @@ -1636,7 +1659,7 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "cronjobCleanupComplete" "Cronjob Cleanup" previousStepEnd=${currentStepEnd} -beginBuildStep "Post-Rollout Tasks" +beginBuildStep "Post-Rollout Tasks" "runningPostRolloutTasks" set -x ############################################## @@ -1648,13 +1671,16 @@ if [ "${LAGOON_POSTROLLOUT_DISABLED}" != "true" ]; then build-deploy-tool tasks post-rollout else echo "post-rollout tasks are currently disabled LAGOON_POSTROLLOUT_DISABLED is set to true" + set +x + currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" + patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "postRolloutsCompleted" "Post-Rollout Tasks" + set -x fi set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" -patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "postRolloutsCompleted" "Post-Rollout Tasks" previousStepEnd=${currentStepEnd} -beginBuildStep "Build and Deploy" +beginBuildStep "Build and Deploy" "finalizingBuild" set -x ############################################## @@ -1688,7 +1714,7 @@ set -x set +x if [ "$(featureFlag INSIGHTS)" = enabled ]; then - beginBuildStep "Insights Gathering" + beginBuildStep "Insights Gathering" "gatheringInsights" ############################################## ### RUN insights gathering and store in configmap ############################################## diff --git a/legacy/build-deploy.sh b/legacy/build-deploy.sh index 89c306a9..4179119a 100755 --- a/legacy/build-deploy.sh +++ b/legacy/build-deploy.sh @@ -68,6 +68,7 @@ else if [[ -f "/var/run/secrets/lagoon/deployer/token" ]]; then DEPLOYER_TOKEN=$(cat /var/run/secrets/lagoon/deployer/token) fi +fi if [ -z ${DEPLOYER_TOKEN} ]; then echo "No deployer token found"; exit 1; fi diff --git a/legacy/helmcharts/basic-persistent/values.yaml b/legacy/helmcharts/basic-persistent/values.yaml index acaa1885..b8a3ff16 100644 --- a/legacy/helmcharts/basic-persistent/values.yaml +++ b/legacy/helmcharts/basic-persistent/values.yaml @@ -18,7 +18,6 @@ nameOverride: "" fullnameOverride: "" podSecurityContext: {} - # fsGroup: 2000 securityContext: {} # capabilities: diff --git a/legacy/helmcharts/cli-persistent/values.yaml b/legacy/helmcharts/cli-persistent/values.yaml index 1df2ddc6..4f736c5c 100644 --- a/legacy/helmcharts/cli-persistent/values.yaml +++ b/legacy/helmcharts/cli-persistent/values.yaml @@ -20,7 +20,6 @@ nameOverride: "" fullnameOverride: "" podSecurityContext: {} - # fsGroup: 2000 securityContext: {} # capabilities: diff --git a/legacy/helmcharts/elasticsearch/templates/deployment.yaml b/legacy/helmcharts/elasticsearch/templates/deployment.yaml index 67bb205e..0ab2d092 100644 --- a/legacy/helmcharts/elasticsearch/templates/deployment.yaml +++ b/legacy/helmcharts/elasticsearch/templates/deployment.yaml @@ -49,7 +49,7 @@ spec: # This init container sets the appropriate limits for mmap counts on the hosting node. # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html - name: set-max-map-count - image: busybox:latest + image: {{ .Values.imageCache }}library/busybox:latest imagePullPolicy: {{ .Values.imagePullPolicy }} securityContext: privileged: true diff --git a/legacy/helmcharts/elasticsearch/values.yaml b/legacy/helmcharts/elasticsearch/values.yaml index 4399e328..374fcb2c 100644 --- a/legacy/helmcharts/elasticsearch/values.yaml +++ b/legacy/helmcharts/elasticsearch/values.yaml @@ -4,6 +4,7 @@ replicaCount: 1 +imageCache: "" image: "" environmentType: production diff --git a/legacy/helmcharts/mariadb-dbaas/templates/prebackuppod.yaml b/legacy/helmcharts/mariadb-dbaas/templates/prebackuppod.yaml index 392761d7..083e0138 100644 --- a/legacy/helmcharts/mariadb-dbaas/templates/prebackuppod.yaml +++ b/legacy/helmcharts/mariadb-dbaas/templates/prebackuppod.yaml @@ -70,7 +70,7 @@ spec: key: {{ include "mariadb-dbaas.fullnameUppercase" . }}_READREPLICA_HOSTS name: lagoon-env {{- end }} - image: imagecache.amazeeio.cloud/amazeeio/alpine-mysql-client + image: {{ .Values.imageCache }}uselagoon/database-tools:latest imagePullPolicy: Always name: {{ include "mariadb-dbaas.fullname" . }}-prebackuppod {{ end }} diff --git a/legacy/helmcharts/mariadb-dbaas/values.yaml b/legacy/helmcharts/mariadb-dbaas/values.yaml index 46ef84ba..38f8de1b 100644 --- a/legacy/helmcharts/mariadb-dbaas/values.yaml +++ b/legacy/helmcharts/mariadb-dbaas/values.yaml @@ -4,4 +4,6 @@ environment: "" -readReplicaHosts: "" \ No newline at end of file +readReplicaHosts: "" + +imageCache: "" diff --git a/legacy/helmcharts/mongodb-dbaas/templates/prebackuppod.yaml b/legacy/helmcharts/mongodb-dbaas/templates/prebackuppod.yaml index 4f893420..ab2f0fcd 100644 --- a/legacy/helmcharts/mongodb-dbaas/templates/prebackuppod.yaml +++ b/legacy/helmcharts/mongodb-dbaas/templates/prebackuppod.yaml @@ -61,7 +61,7 @@ spec: configMapKeyRef: key: {{ include "mongodb-dbaas.fullnameUppercase" . }}_DB_AUTHTLS name: lagoon-env - image: imagecache.amazeeio.cloud/uselagoon/php-8.0-cli + image: {{ .Values.imageCache }}uselagoon/database-tools:latest imagePullPolicy: Always name: {{ include "mongodb-dbaas.fullname" . }}-prebackuppod {{ end }} diff --git a/legacy/helmcharts/mongodb-dbaas/values.yaml b/legacy/helmcharts/mongodb-dbaas/values.yaml index da55d1ce..495f8cc0 100644 --- a/legacy/helmcharts/mongodb-dbaas/values.yaml +++ b/legacy/helmcharts/mongodb-dbaas/values.yaml @@ -3,3 +3,5 @@ # Declare variables to be passed into your templates. environment: "" + +imageCache: "" diff --git a/legacy/helmcharts/nginx-php-persistent/templates/deployment.yaml b/legacy/helmcharts/nginx-php-persistent/templates/deployment.yaml index 579f1158..a96ac80d 100644 --- a/legacy/helmcharts/nginx-php-persistent/templates/deployment.yaml +++ b/legacy/helmcharts/nginx-php-persistent/templates/deployment.yaml @@ -60,7 +60,7 @@ spec: find /storage -type d -exec chmod a+x {} + touch "$SENTINEL" fi - image: busybox:musl + image: {{ .Values.imageCache }}library/busybox:musl imagePullPolicy: IfNotPresent name: fix-storage-permissions securityContext: diff --git a/legacy/helmcharts/nginx-php-persistent/values.yaml b/legacy/helmcharts/nginx-php-persistent/values.yaml index 6ee09e5b..349d9dc6 100644 --- a/legacy/helmcharts/nginx-php-persistent/values.yaml +++ b/legacy/helmcharts/nginx-php-persistent/values.yaml @@ -4,6 +4,7 @@ replicaCount: 1 +imageCache: "" images: nginx: "" php: "" @@ -20,7 +21,6 @@ nameOverride: "" fullnameOverride: "" podSecurityContext: {} - # fsGroup: 2000 securityContext: {} # capabilities: diff --git a/legacy/helmcharts/node-persistent/values.yaml b/legacy/helmcharts/node-persistent/values.yaml index 0c4642fe..0ce6dfef 100644 --- a/legacy/helmcharts/node-persistent/values.yaml +++ b/legacy/helmcharts/node-persistent/values.yaml @@ -18,7 +18,6 @@ nameOverride: "" fullnameOverride: "" podSecurityContext: {} - # fsGroup: 2000 securityContext: {} # capabilities: diff --git a/legacy/helmcharts/opensearch/templates/deployment.yaml b/legacy/helmcharts/opensearch/templates/deployment.yaml index 83410a86..7acb586d 100644 --- a/legacy/helmcharts/opensearch/templates/deployment.yaml +++ b/legacy/helmcharts/opensearch/templates/deployment.yaml @@ -49,7 +49,7 @@ spec: # This init container sets the appropriate limits for mmap counts on the hosting node. # https://opensearch.org/docs/latest/opensearch/install/important-settings/ - name: set-max-map-count - image: busybox:latest + image: {{ .Values.imageCache }}library/busybox:latest imagePullPolicy: {{ .Values.imagePullPolicy }} securityContext: privileged: true diff --git a/legacy/helmcharts/opensearch/values.yaml b/legacy/helmcharts/opensearch/values.yaml index bd5c85a3..a8842b35 100644 --- a/legacy/helmcharts/opensearch/values.yaml +++ b/legacy/helmcharts/opensearch/values.yaml @@ -4,6 +4,7 @@ replicaCount: 1 +imageCache: "" image: "" environmentType: production diff --git a/legacy/helmcharts/postgres-dbaas/templates/prebackuppod.yaml b/legacy/helmcharts/postgres-dbaas/templates/prebackuppod.yaml index c6465d2f..398f1d88 100644 --- a/legacy/helmcharts/postgres-dbaas/templates/prebackuppod.yaml +++ b/legacy/helmcharts/postgres-dbaas/templates/prebackuppod.yaml @@ -61,7 +61,7 @@ spec: key: {{ include "postgres-dbaas.fullnameUppercase" . }}_READREPLICA_HOSTS name: lagoon-env {{- end }} - image: imagecache.amazeeio.cloud/uselagoon/php-8.0-cli + image: {{ .Values.imageCache }}uselagoon/database-tools:latest imagePullPolicy: Always name: {{ include "postgres-dbaas.fullname" . }}-prebackuppod {{ end }} diff --git a/legacy/helmcharts/postgres-dbaas/values.yaml b/legacy/helmcharts/postgres-dbaas/values.yaml index 20fb54f0..03e79048 100644 --- a/legacy/helmcharts/postgres-dbaas/values.yaml +++ b/legacy/helmcharts/postgres-dbaas/values.yaml @@ -4,4 +4,6 @@ environment: "" -readReplicaHosts: "" \ No newline at end of file +readReplicaHosts: "" + +imageCache: "" diff --git a/legacy/helmcharts/python-persistent/values.yaml b/legacy/helmcharts/python-persistent/values.yaml index 425c1285..8bbdf7c4 100644 --- a/legacy/helmcharts/python-persistent/values.yaml +++ b/legacy/helmcharts/python-persistent/values.yaml @@ -18,7 +18,6 @@ nameOverride: "" fullnameOverride: "" podSecurityContext: {} - # fsGroup: 2000 securityContext: {} # capabilities: diff --git a/legacy/helmcharts/worker-persistent/values.yaml b/legacy/helmcharts/worker-persistent/values.yaml index 2829cb66..a0bfa83a 100644 --- a/legacy/helmcharts/worker-persistent/values.yaml +++ b/legacy/helmcharts/worker-persistent/values.yaml @@ -20,7 +20,6 @@ nameOverride: "" fullnameOverride: "" podSecurityContext: {} - # fsGroup: 2000 securityContext: {} # capabilities: diff --git a/legacy/scripts/exec-kubernetes-copy-to-registry.sh b/legacy/scripts/exec-kubernetes-copy-to-registry.sh index 4b7826bb..034b5248 100644 --- a/legacy/scripts/exec-kubernetes-copy-to-registry.sh +++ b/legacy/scripts/exec-kubernetes-copy-to-registry.sh @@ -1,2 +1,2 @@ #!/bin/bash -skopeo copy --retry-times 5 --dest-tls-verify=false docker://${IMAGECACHE_REGISTRY}/${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} +skopeo copy --retry-times 5 --dest-tls-verify=false docker://${IMAGECACHE_REGISTRY}${PULL_IMAGE} docker://${REGISTRY}/${PROJECT}/${ENVIRONMENT}/${IMAGE_NAME}:${IMAGE_TAG:-latest} diff --git a/test-resources/template-ingress/test11/lagoon.yml b/test-resources/template-ingress/test11/lagoon.yml index ca84a886..84ba6edd 100644 --- a/test-resources/template-ingress/test11/lagoon.yml +++ b/test-resources/template-ingress/test11/lagoon.yml @@ -1,5 +1,5 @@ --- -docker-compose-yaml: ../test-resources/template-autogenerated/test20/docker-compose.yml +docker-compose-yaml: ../test-resources/template-ingress/test11/docker-compose.yml project: content-example-com diff --git a/test-resources/template-ingress/test12/lagoon.yml b/test-resources/template-ingress/test12/lagoon.yml index 9d44d43c..6e937dd6 100644 --- a/test-resources/template-ingress/test12/lagoon.yml +++ b/test-resources/template-ingress/test12/lagoon.yml @@ -1,4 +1,4 @@ -docker-compose-yaml: ../test-resources/template-ingress/test1/docker-compose.yml +docker-compose-yaml: ../test-resources/template-ingress/test12/docker-compose.yml environment_variables: git_sha: "true" diff --git a/test-resources/template-ingress/test21-results/example.com.yaml b/test-resources/template-ingress/test21-results/example.com.yaml new file mode 100644 index 00000000..e143e7f9 --- /dev/null +++ b/test-resources/template-ingress/test21-results/example.com.yaml @@ -0,0 +1,72 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + fastly.amazee.io/watch: "false" + ingress.kubernetes.io/ssl-redirect: "true" + kubernetes.io/tls-acme: "true" + lagoon.sh/branch: main + lagoon.sh/version: v2.7.x + monitor.stakater.com/enabled: "true" + monitor.stakater.com/overridePath: / + nginx.ingress.kubernetes.io/ssl-redirect: "true" + uptimerobot.monitor.stakater.com/alert-contacts: alertcontact + uptimerobot.monitor.stakater.com/interval: "60" + uptimerobot.monitor.stakater.com/status-pages: statuspageid + creationTimestamp: null + labels: + app.kubernetes.io/instance: example.com + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: custom-ingress + dioscuri.amazee.io/migrate: "false" + helm.sh/chart: custom-ingress-0.1.0 + lagoon.sh/autogenerated: "false" + lagoon.sh/buildType: branch + lagoon.sh/environment: main + lagoon.sh/environmentType: production + lagoon.sh/primaryIngress: "true" + lagoon.sh/project: example-project + lagoon.sh/service: example.com + lagoon.sh/service-type: custom-ingress + name: example.com +spec: + rules: + - host: example.com + http: + paths: + - backend: + service: + name: node + port: + name: http + path: / + pathType: Prefix + - host: www.example.com + http: + paths: + - backend: + service: + name: node + port: + name: http + path: / + pathType: Prefix + - host: en.example.com + http: + paths: + - backend: + service: + name: node + port: + name: http + path: / + pathType: Prefix + tls: + - hosts: + - example.com + - www.example.com + - en.example.com + secretName: example.com-tls +status: + loadBalancer: {} diff --git a/test-resources/template-ingress/test21/docker-compose.yml b/test-resources/template-ingress/test21/docker-compose.yml new file mode 100644 index 00000000..85386270 --- /dev/null +++ b/test-resources/template-ingress/test21/docker-compose.yml @@ -0,0 +1,20 @@ +version: '2' +services: + node: + networks: + - amazeeio-network + - default + build: + context: . + dockerfile: node.dockerfile + labels: + lagoon.type: node + volumes: + - .:/app:delegated + environment: + - LAGOON_LOCALDEV_HTTP_PORT=3000 + - LAGOON_ROUTE=http://node.docker.amazee.io + +networks: + amazeeio-network: + external: true \ No newline at end of file diff --git a/test-resources/template-ingress/test21/lagoon.yml b/test-resources/template-ingress/test21/lagoon.yml new file mode 100644 index 00000000..cb50c4cc --- /dev/null +++ b/test-resources/template-ingress/test21/lagoon.yml @@ -0,0 +1,13 @@ +docker-compose-yaml: ../test-resources/template-ingress/test21/docker-compose.yml + +environment_variables: + git_sha: "true" + +environments: + main: + routes: + - node: + - example.com: + alternativenames: + - www.example.com + - en.example.com diff --git a/test-resources/template-ingress/test22-results/example.com.yaml b/test-resources/template-ingress/test22-results/example.com.yaml new file mode 100644 index 00000000..ce022329 --- /dev/null +++ b/test-resources/template-ingress/test22-results/example.com.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + fastly.amazee.io/watch: "false" + ingress.kubernetes.io/ssl-redirect: "true" + kubernetes.io/tls-acme: "false" + lagoon.sh/branch: main + lagoon.sh/version: v2.7.x + monitor.stakater.com/enabled: "false" + monitor.stakater.com/overridePath: / + nginx.ingress.kubernetes.io/ssl-redirect: "true" + creationTimestamp: null + labels: + app.kubernetes.io/instance: wildcard-example.com + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: custom-ingress + dioscuri.amazee.io/migrate: "false" + helm.sh/chart: custom-ingress-0.1.0 + lagoon.sh/autogenerated: "false" + lagoon.sh/buildType: branch + lagoon.sh/environment: main + lagoon.sh/environmentType: production + lagoon.sh/project: example-project + lagoon.sh/service: wildcard-example.com + lagoon.sh/service-type: custom-ingress + name: wildcard-example.com +spec: + rules: + - host: '*.example.com' + http: + paths: + - backend: + service: + name: node + port: + name: http + path: / + pathType: Prefix + tls: + - hosts: + - '*.example.com' + secretName: wildcard-example.com-tls +status: + loadBalancer: {} diff --git a/test-resources/template-ingress/test22/docker-compose.yml b/test-resources/template-ingress/test22/docker-compose.yml new file mode 100644 index 00000000..85386270 --- /dev/null +++ b/test-resources/template-ingress/test22/docker-compose.yml @@ -0,0 +1,20 @@ +version: '2' +services: + node: + networks: + - amazeeio-network + - default + build: + context: . + dockerfile: node.dockerfile + labels: + lagoon.type: node + volumes: + - .:/app:delegated + environment: + - LAGOON_LOCALDEV_HTTP_PORT=3000 + - LAGOON_ROUTE=http://node.docker.amazee.io + +networks: + amazeeio-network: + external: true \ No newline at end of file diff --git a/test-resources/template-ingress/test22/lagoon.yml b/test-resources/template-ingress/test22/lagoon.yml new file mode 100644 index 00000000..9f44e4c6 --- /dev/null +++ b/test-resources/template-ingress/test22/lagoon.yml @@ -0,0 +1,12 @@ +docker-compose-yaml: ../test-resources/template-ingress/test22/docker-compose.yml + +environment_variables: + git_sha: "true" + +environments: + main: + routes: + - node: + - example.com: + tls-acme: false + wildcard: true