From a4154f669b3d6d90f38d1ea0fb7dd83ec1e95b38 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 1 Nov 2021 16:49:30 +1100 Subject: [PATCH 1/2] feat: add support for production_routes, and a warning for environments that arent valid --- internal/lagoonyml/lagoon.go | 9 ++- internal/lagoonyml/lint.go | 41 ++++++++++- internal/lagoonyml/lint_test.go | 4 ++ internal/lagoonyml/routeannotation.go | 72 +++++++++++++++++++ .../lagoonyml/testdata/invalid.0.lagoon.yml | 19 +++++ .../testdata/valid.broken.0.lagoon.yml | 24 +++++++ 6 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 internal/lagoonyml/testdata/valid.broken.0.lagoon.yml diff --git a/internal/lagoonyml/lagoon.go b/internal/lagoonyml/lagoon.go index 2b4dfcf..a64de4a 100644 --- a/internal/lagoonyml/lagoon.go +++ b/internal/lagoonyml/lagoon.go @@ -29,5 +29,12 @@ type Environment struct { // Lagoon represents the .lagoon.yml file. type Lagoon struct { - Environments map[string]Environment `json:"environments"` + ProductionRoutes ProductionRoutes `json:"production_routes"` + Environments map[string]Environment `json:"environments"` +} + +// ProductionRoutes represents active/standby route configurations. +type ProductionRoutes struct { + Active Environment `json:"active"` + Standby Environment `json:"standby"` } diff --git a/internal/lagoonyml/lint.go b/internal/lagoonyml/lint.go index 4334d2d..836f999 100644 --- a/internal/lagoonyml/lint.go +++ b/internal/lagoonyml/lint.go @@ -16,14 +16,53 @@ type Linter func(*Lagoon) error // `.lagoon.yml` is valid. func Lint(path string, linters ...Linter) error { var l Lagoon + l.Environments = make(map[string]Environment) + rawYAML, err := os.ReadFile(path) if err != nil { return fmt.Errorf("couldn't read %v: %v", path, err) } - err = yaml.Unmarshal(rawYAML, &l) + + // unmarshal the raw yaml into a map[string]interface{} + var li map[string]interface{} + err = yaml.Unmarshal(rawYAML, &li) if err != nil { return fmt.Errorf("couldn't unmarshal %v: %v", path, err) } + + // check each block for ability to be unmarshalled + for key, block := range li { + if b, ok := block.(map[string]interface{}); ok { + switch key { + case "environments": + for env, config := range b { + c, _ := yaml.Marshal(config) + var le Environment + err = yaml.Unmarshal(c, &le) + if err != nil { + fmt.Printf("Warning: Not valid configuration for environment '%s': %v\n", env, err) + } + l.Environments[env] = le + } + case "production_routes": + for env, config := range b { + c, _ := yaml.Marshal(config) + var le Environment + err = yaml.Unmarshal(c, &le) + if err != nil { + fmt.Printf("Warning: Not valid configuration for production_routes '%s': %v\n", env, err) + } + if env == "active" { + l.ProductionRoutes.Active = le + } else if env == "standby" { + l.ProductionRoutes.Standby = le + } + } + } + } + } + + // run the linter for _, linter := range linters { if err := linter(&l); err != nil { return &ErrLint{ diff --git a/internal/lagoonyml/lint_test.go b/internal/lagoonyml/lint_test.go index ba98808..44c761e 100644 --- a/internal/lagoonyml/lint_test.go +++ b/internal/lagoonyml/lint_test.go @@ -35,6 +35,10 @@ func TestLint(t *testing.T) { input: "testdata/invalid.1.lagoon.yml", valid: false, }, + "valid.broken.0.lagoon.yml": { + input: "testdata/valid.broken.0.lagoon.yml", + valid: true, + }, } for name, tc := range testCases { t.Run(name, func(tt *testing.T) { diff --git a/internal/lagoonyml/routeannotation.go b/internal/lagoonyml/routeannotation.go index 44d39ae..ef15a55 100644 --- a/internal/lagoonyml/routeannotation.go +++ b/internal/lagoonyml/routeannotation.go @@ -78,6 +78,78 @@ func RouteAnnotation() Linter { } } } + for _, routeMap := range l.ProductionRoutes.Active.Routes { + for rName, lagoonRoutes := range routeMap { + for _, lagoonRoute := range lagoonRoutes { + for iName, ingress := range lagoonRoute.Ingresses { + // auth-snippet + if _, ok := ingress.Annotations[authSnippet]; ok { + return fmt.Errorf( + "invalid %s annotation on active environment, route %s, ingress %s: %s", + authSnippet, rName, iName, + "this annotation is restricted") + } + // configuration-snippet + if annotation, ok := validate(ingress.Annotations, validSnippets, + configurationSnippet); !ok { + return fmt.Errorf( + "invalid %s annotation on active environment, route %s, ingress %s: %s", + configurationSnippet, rName, iName, annotation) + } + // modsecurity-snippet + if _, ok := ingress.Annotations[modsecuritySnippet]; ok { + return fmt.Errorf( + "invalid %s annotation on active environment, route %s, ingress %s: %s", + modsecuritySnippet, rName, iName, + "this annotation is restricted") + } + // server-snippet + if annotation, ok := validate(ingress.Annotations, validSnippets, + serverSnippet); !ok { + return fmt.Errorf( + "invalid %s annotation on active environment, route %s, ingress %s: %s", + serverSnippet, rName, iName, annotation) + } + } + } + } + } + for _, routeMap := range l.ProductionRoutes.Standby.Routes { + for rName, lagoonRoutes := range routeMap { + for _, lagoonRoute := range lagoonRoutes { + for iName, ingress := range lagoonRoute.Ingresses { + // auth-snippet + if _, ok := ingress.Annotations[authSnippet]; ok { + return fmt.Errorf( + "invalid %s annotation on standby environment, route %s, ingress %s: %s", + authSnippet, rName, iName, + "this annotation is restricted") + } + // configuration-snippet + if annotation, ok := validate(ingress.Annotations, validSnippets, + configurationSnippet); !ok { + return fmt.Errorf( + "invalid %s annotation on standby environment, route %s, ingress %s: %s", + configurationSnippet, rName, iName, annotation) + } + // modsecurity-snippet + if _, ok := ingress.Annotations[modsecuritySnippet]; ok { + return fmt.Errorf( + "invalid %s annotation on standby environment, route %s, ingress %s: %s", + modsecuritySnippet, rName, iName, + "this annotation is restricted") + } + // server-snippet + if annotation, ok := validate(ingress.Annotations, validSnippets, + serverSnippet); !ok { + return fmt.Errorf( + "invalid %s annotation on standby environment, route %s, ingress %s: %s", + serverSnippet, rName, iName, annotation) + } + } + } + } + } return nil } } diff --git a/internal/lagoonyml/testdata/invalid.0.lagoon.yml b/internal/lagoonyml/testdata/invalid.0.lagoon.yml index cd3e568..e133b01 100644 --- a/internal/lagoonyml/testdata/invalid.0.lagoon.yml +++ b/internal/lagoonyml/testdata/invalid.0.lagoon.yml @@ -16,3 +16,22 @@ environments: if ($request_uri !~ \"^/abc\") { return 301 https://dev.example.com$request_uri; } + +production_routes: + active: + routes: + - nginx: + - "www.example.com": + tls-acme: true + insecure: Redirect + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + if ($request_uri !~ \"^/abc\") { + return 301 https://dev.example.com$request_uri; + } + standby: + routes: + - nginx: + - "www.standby.example.com": + tls-acme: "false" + insecure: Redirect \ No newline at end of file diff --git a/internal/lagoonyml/testdata/valid.broken.0.lagoon.yml b/internal/lagoonyml/testdata/valid.broken.0.lagoon.yml new file mode 100644 index 0000000..ff766b3 --- /dev/null +++ b/internal/lagoonyml/testdata/valid.broken.0.lagoon.yml @@ -0,0 +1,24 @@ +environments: + cronjobs: + routes: + - name: a cronjob defined as environment + schedule: "* * * * *" + command: echo "broken definition" + service: cli + main: + routes: + - nginx: + - example.com + - "www.example.com": + tls-acme: 'true' + insecure: Redirect + hsts: max-age=31536000 + - "example.com": + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set_real_ip_from 1.2.3.4/32; + - "dev.example.com": + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set_real_ip_from 1.2.3.4/32; + add_header Content-type text/plain; \ No newline at end of file From 0ad6173fc1182d9d25cac6bc322da47d7134cbac Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 1 Nov 2021 17:38:33 +1100 Subject: [PATCH 2/2] chore: minor message word changes --- internal/lagoonyml/lint.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/lagoonyml/lint.go b/internal/lagoonyml/lint.go index 836f999..5a54afc 100644 --- a/internal/lagoonyml/lint.go +++ b/internal/lagoonyml/lint.go @@ -27,7 +27,7 @@ func Lint(path string, linters ...Linter) error { var li map[string]interface{} err = yaml.Unmarshal(rawYAML, &li) if err != nil { - return fmt.Errorf("couldn't unmarshal %v: %v", path, err) + return fmt.Errorf(".lagoon.yml configuration not valid for %v: %v", path, err) } // check each block for ability to be unmarshalled @@ -40,7 +40,7 @@ func Lint(path string, linters ...Linter) error { var le Environment err = yaml.Unmarshal(c, &le) if err != nil { - fmt.Printf("Warning: Not valid configuration for environment '%s': %v\n", env, err) + fmt.Printf("Warning: .lagoon.yml configuration not valid for environment '%s': %v\n", env, err) } l.Environments[env] = le } @@ -50,7 +50,7 @@ func Lint(path string, linters ...Linter) error { var le Environment err = yaml.Unmarshal(c, &le) if err != nil { - fmt.Printf("Warning: Not valid configuration for production_routes '%s': %v\n", env, err) + fmt.Printf("Warning: .lagoon.yml configuration not valid for production_routes '%s': %v\n", env, err) } if env == "active" { l.ProductionRoutes.Active = le