From 62522a3d49c0df1646e137ebad812720250074ea Mon Sep 17 00:00:00 2001 From: Ricardo Katz Date: Thu, 18 Jul 2024 19:20:52 -0300 Subject: [PATCH] Bootstrap initial crossplane work (#11653) * Bootstrap initial crossplane work * Disable some tests to speed up stuff --- .golangci.yml | 1 - go.mod | 12 +- go.sum | 11 ++ go.work.sum | 3 + .../controller/template/crossplane/config.go | 38 ++++ .../template/crossplane/crossplane.go | 70 +++++++ .../crossplane/crossplane_internal_test.go | 90 +++++++++ .../crossplane_internal_utils_test.go | 58 ++++++ .../template/crossplane/crossplane_test.go | 28 +++ .../controller/template/crossplane/events.go | 36 ++++ .../controller/template/crossplane/http.go | 187 ++++++++++++++++++ .../controller/template/crossplane/utils.go | 100 ++++++++++ 12 files changed, 630 insertions(+), 4 deletions(-) create mode 100644 internal/ingress/controller/template/crossplane/config.go create mode 100644 internal/ingress/controller/template/crossplane/crossplane.go create mode 100644 internal/ingress/controller/template/crossplane/crossplane_internal_test.go create mode 100644 internal/ingress/controller/template/crossplane/crossplane_internal_utils_test.go create mode 100644 internal/ingress/controller/template/crossplane/crossplane_test.go create mode 100644 internal/ingress/controller/template/crossplane/events.go create mode 100644 internal/ingress/controller/template/crossplane/http.go create mode 100644 internal/ingress/controller/template/crossplane/utils.go diff --git a/.golangci.yml b/.golangci.yml index 0cf49e6af..3aa2d9357 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,7 +26,6 @@ linters: - ginkgolinter - gocheckcompilerdirectives - goconst - - gocritic - gocyclo - godox - gofmt diff --git a/go.mod b/go.mod index 660cbd4d0..63b3f277e 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,12 @@ require ( sigs.k8s.io/mdtoc v1.1.0 ) +require ( + github.com/jstemmer/go-junit-report v1.0.0 // indirect + github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 // indirect + github.com/nginxinc/nginx-go-crossplane v0.4.50 // indirect +) + require ( github.com/Anddd7/pb v0.0.0-20240425032658-369b0f6a404c github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -108,15 +114,15 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect go.starlark.net v0.0.0-20240123142251-f86470692795 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v5 v5.9.0 // indirect diff --git a/go.sum b/go.sum index 1fdca4a71..5a4798df6 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v1.0.0 h1:8X1gzZpR+nVQLAht+L/foqOeX2l9DTZoaIPbEQHxsds= +github.com/jstemmer/go-junit-report v1.0.0/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -126,6 +128,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 h1:NicmruxkeqHjDv03SfSxqmaLuisddudfP3h5wdXFbhM= +github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1/go.mod h1:eyp4DdUJAKkr9tvxR3jWhw2mDK7CWABMG5r9uyaKC7I= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= @@ -153,6 +157,8 @@ github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833 h1:t4WWQ9I797y7QU github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833/go.mod h1:0CznHmXSjMEqs5Tezj/w2emQoM41wzYM9KpDKUHPYag= github.com/ncabatoff/process-exporter v0.8.2 h1:g08B7UMSn9nrEjCrqGtBG/IagnCAc/2lSGzGZgcxJFs= github.com/ncabatoff/process-exporter v0.8.2/go.mod h1:MxEOWl740VK/hlWycJkq91VrA2mI+U9Bvc1wuyAaxA4= +github.com/nginxinc/nginx-go-crossplane v0.4.50 h1:xlPlA3F3gPirvMRp8lR5ZVNBFim7wbIs3zACon9QXUQ= +github.com/nginxinc/nginx-go-crossplane v0.4.50/go.mod h1:fgSibLM12jGRsh7QHpgL8wTKMEbfc594vSLK9ovwM6U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -238,6 +244,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -246,6 +254,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -285,6 +294,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum index 847afcea0..6cbe30b78 100644 --- a/go.work.sum +++ b/go.work.sum @@ -831,6 +831,7 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF+koVGaX5xngRIZ5bVjbmPnaxE/dR08uY= +github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -947,6 +948,7 @@ golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= @@ -962,6 +964,7 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/internal/ingress/controller/template/crossplane/config.go b/internal/ingress/controller/template/crossplane/config.go new file mode 100644 index 000000000..007bdf1a8 --- /dev/null +++ b/internal/ingress/controller/template/crossplane/config.go @@ -0,0 +1,38 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + ngx_crossplane "github.com/nginxinc/nginx-go-crossplane" +) + +func (c *crossplaneTemplate) buildConfig() { + // Write basic directives + config := &ngx_crossplane.Config{ + Parsed: ngx_crossplane.Directives{ + buildDirective("pid", c.tplConfig.PID), + buildDirective("daemon", "off"), + buildDirective("worker_processes", c.tplConfig.Cfg.WorkerProcesses), + buildDirective("worker_rlimit_nofile", c.tplConfig.Cfg.MaxWorkerOpenFiles), + buildDirective("worker_shutdown_timeout", c.tplConfig.Cfg.WorkerShutdownTimeout), + }, + } + if c.tplConfig.Cfg.WorkerCPUAffinity != "" { + config.Parsed = append(config.Parsed, buildDirective("worker_cpu_affinity", c.tplConfig.Cfg.WorkerCPUAffinity)) + } + c.config = config +} diff --git a/internal/ingress/controller/template/crossplane/crossplane.go b/internal/ingress/controller/template/crossplane/crossplane.go new file mode 100644 index 000000000..c674cc6fe --- /dev/null +++ b/internal/ingress/controller/template/crossplane/crossplane.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + "bytes" + + ngx_crossplane "github.com/nginxinc/nginx-go-crossplane" + + "k8s.io/ingress-nginx/internal/ingress/controller/config" +) + +/* +Unsupported directives: +- opentelemetry +- modsecurity +- any stream directive (TCP/UDP forwarding) +- geoip2 +*/ + +// On this case we will try to use the go ngx_crossplane to write the template instead of the template renderer + +type crossplaneTemplate struct { + options *ngx_crossplane.BuildOptions + config *ngx_crossplane.Config + tplConfig *config.TemplateConfig +} + +func NewCrossplaneTemplate() *crossplaneTemplate { + lua := ngx_crossplane.Lua{} + return &crossplaneTemplate{ + options: &ngx_crossplane.BuildOptions{ + Builders: []ngx_crossplane.RegisterBuilder{ + lua.RegisterBuilder(), + }, + }, + } +} + +func (c *crossplaneTemplate) Write(conf *config.TemplateConfig) ([]byte, error) { + c.tplConfig = conf + + // build root directives + c.buildConfig() + + // build events directive + c.buildEvents() + + // build http directive + c.buildHTTP() + + var buf bytes.Buffer + + err := ngx_crossplane.Build(&buf, *c.config, &ngx_crossplane.BuildOptions{}) + return buf.Bytes(), err +} diff --git a/internal/ingress/controller/template/crossplane/crossplane_internal_test.go b/internal/ingress/controller/template/crossplane/crossplane_internal_test.go new file mode 100644 index 000000000..6479b197e --- /dev/null +++ b/internal/ingress/controller/template/crossplane/crossplane_internal_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + "testing" + + ngx_crossplane "github.com/nginxinc/nginx-go-crossplane" + "github.com/stretchr/testify/require" + + "k8s.io/ingress-nginx/internal/ingress/controller/config" +) + +// THIS FILE SHOULD BE USED JUST FOR INTERNAL TESTS - Private functions + +func Test_Internal_buildEvents(t *testing.T) { + t.Run("should fill correctly events directives with defaults", func(t *testing.T) { + c := ngx_crossplane.Config{} + tplConfig := &config.TemplateConfig{ + Cfg: config.NewDefault(), + } + + expectedEvents := &ngx_crossplane.Config{ + File: "", + Parsed: ngx_crossplane.Directives{ + { + Directive: "events", + Block: ngx_crossplane.Directives{ + buildDirective("worker_connections", 16384), + buildDirective("use", "epool"), + buildDirective("multi_accept", true), + }, + }, + }, + } + + cplane := NewCrossplaneTemplate() + cplane.config = &c + cplane.tplConfig = tplConfig + cplane.buildEvents() + require.Equal(t, expectedEvents, cplane.config) + }) + + t.Run("should fill correctly events directives with specific values", func(t *testing.T) { + c := ngx_crossplane.Config{} + tplConfig := &config.TemplateConfig{ + Cfg: config.Configuration{ + MaxWorkerConnections: 50, + EnableMultiAccept: false, + DebugConnections: []string{"127.0.0.1/32", "192.168.0.10"}, + }, + } + + expectedEvents := &ngx_crossplane.Config{ + File: "", + Parsed: ngx_crossplane.Directives{ + { + Directive: "events", + Block: ngx_crossplane.Directives{ + buildDirective("worker_connections", 50), + buildDirective("use", "epool"), + buildDirective("multi_accept", false), + buildDirective("debug_connection", "127.0.0.1/32"), + buildDirective("debug_connection", "192.168.0.10"), + }, + }, + }, + } + + cplane := NewCrossplaneTemplate() + cplane.config = &c + cplane.tplConfig = tplConfig + cplane.buildEvents() + require.Equal(t, expectedEvents, cplane.config) + }) +} diff --git a/internal/ingress/controller/template/crossplane/crossplane_internal_utils_test.go b/internal/ingress/controller/template/crossplane/crossplane_internal_utils_test.go new file mode 100644 index 000000000..e9ac96691 --- /dev/null +++ b/internal/ingress/controller/template/crossplane/crossplane_internal_utils_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/ingress-nginx/internal/ingress/controller/config" +) + +// THIS FILE SHOULD BE USED JUST FOR INTERNAL TESTS - Private functions + +func Test_Internal_buildDirectives(t *testing.T) { + t.Run("should be able to run a directive with a single argument", func(t *testing.T) { + directive := buildDirective("somedirective", "bla") + require.Equal(t, directive.Directive, "somedirective", []string{"bla"}) + }) + t.Run("should be able to run a directive with multiple different arguments", func(t *testing.T) { + directive := buildDirective("somedirective", "bla", 5, true, seconds(10), []string{"xpto", "bla"}) + require.Equal(t, directive.Directive, "somedirective", []string{"bla", "5", "on", "10s", "xpto", "bla"}) + }) +} + +func Test_Internal_boolToStr(t *testing.T) { + require.Equal(t, boolToStr(true), "on") + require.Equal(t, boolToStr(false), "off") +} + +func Test_Internal_buildLuaDictionaries(t *testing.T) { + t.Skip("Maps are not sorted, need to fix this") + cfg := &config.Configuration{ + LuaSharedDicts: map[string]int{ + "somedict": 1024, + "otherdict": 1025, + }, + } + directives := buildLuaSharedDictionaries(cfg) + require.Len(t, directives, 2) + require.Equal(t, "lua_shared_dict", directives[0].Directive) + require.Equal(t, []string{"somedict", "1M"}, directives[0].Args) + require.Equal(t, "lua_shared_dict", directives[1].Directive) + require.Equal(t, []string{"otherdict", "1025K"}, directives[1].Args) +} diff --git a/internal/ingress/controller/template/crossplane/crossplane_test.go b/internal/ingress/controller/template/crossplane/crossplane_test.go new file mode 100644 index 000000000..bc83cc79a --- /dev/null +++ b/internal/ingress/controller/template/crossplane/crossplane_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane_test + +import "testing" + +// TestCrossplaneTemplate should be a roundtrip test. +// We should initialize the scenarios based on the template configuration +// Then Parse and write a crossplane configuration, and roundtrip/parse back to check +// if the directives matches +// we should ignore line numbers and comments +func TestCrossplaneTemplate(t *testing.T) { + // implement +} diff --git a/internal/ingress/controller/template/crossplane/events.go b/internal/ingress/controller/template/crossplane/events.go new file mode 100644 index 000000000..179358a5f --- /dev/null +++ b/internal/ingress/controller/template/crossplane/events.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + ngx_crossplane "github.com/nginxinc/nginx-go-crossplane" +) + +func (c *crossplaneTemplate) buildEvents() { + events := &ngx_crossplane.Directive{ + Directive: "events", + Block: ngx_crossplane.Directives{ + buildDirective("worker_connections", c.tplConfig.Cfg.MaxWorkerConnections), + buildDirective("use", "epool"), + buildDirective("multi_accept", c.tplConfig.Cfg.EnableMultiAccept), + }, + } + for k := range c.tplConfig.Cfg.DebugConnections { + events.Block = append(events.Block, buildDirective("debug_connection", c.tplConfig.Cfg.DebugConnections[k])) + } + c.config.Parsed = append(c.config.Parsed, events) +} diff --git a/internal/ingress/controller/template/crossplane/http.go b/internal/ingress/controller/template/crossplane/http.go new file mode 100644 index 000000000..a8343d996 --- /dev/null +++ b/internal/ingress/controller/template/crossplane/http.go @@ -0,0 +1,187 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + "fmt" + "strconv" + "strings" + + ngx_crossplane "github.com/nginxinc/nginx-go-crossplane" +) + +func (c *crossplaneTemplate) initHTTPDirectives() ngx_crossplane.Directives { + cfg := c.tplConfig.Cfg + httpBlock := ngx_crossplane.Directives{ + buildDirective("lua_package_path", "/etc/nginx/lua/?.lua;;"), + buildDirective("include", "/etc/nginx/mime.types"), + buildDirective("default_type", cfg.DefaultType), + buildDirective("real_ip_recursive", "on"), + buildDirective("aio", "threads"), + buildDirective("aio_write", cfg.EnableAioWrite), + buildDirective("server_tokens", cfg.ShowServerTokens), + buildDirective("resolver", buildResolversInternal(cfg.Resolver, cfg.DisableIpv6DNS)), + buildDirective("tcp_nopush", "on"), + buildDirective("tcp_nodelay", "on"), + buildDirective("log_subrequest", "on"), + buildDirective("reset_timedout_connection", "on"), + buildDirective("keepalive_timeout", seconds(cfg.KeepAlive)), + buildDirective("keepalive_requests", cfg.KeepAliveRequests), + buildDirective("client_body_temp_path", "/tmp/nginx/client-body"), + buildDirective("fastcgi_temp_path", "/tmp/nginx/fastcgi-temp"), + buildDirective("proxy_temp_path", "/tmp/nginx/proxy-temp"), + buildDirective("client_header_buffer_size", cfg.ClientHeaderBufferSize), + buildDirective("client_header_timeout", seconds(cfg.ClientHeaderTimeout)), + buildDirective("large_client_header_buffers", cfg.LargeClientHeaderBuffers), + buildDirective("client_body_buffer_size", cfg.ClientBodyBufferSize), + buildDirective("client_body_timeout", seconds(cfg.ClientBodyTimeout)), + buildDirective("types_hash_max_size", "2048"), + buildDirective("server_names_hash_max_size", cfg.ServerNameHashMaxSize), + buildDirective("server_names_hash_bucket_size", cfg.ServerNameHashBucketSize), + buildDirective("map_hash_bucket_size", cfg.MapHashBucketSize), + buildDirective("proxy_headers_hash_max_size", cfg.ProxyHeadersHashMaxSize), + buildDirective("proxy_headers_hash_bucket_size", cfg.ProxyHeadersHashBucketSize), + buildDirective("variables_hash_bucket_size", cfg.VariablesHashBucketSize), + buildDirective("variables_hash_max_size", cfg.VariablesHashMaxSize), + buildDirective("underscores_in_headers", cfg.EnableUnderscoresInHeaders), + buildDirective("ignore_invalid_headers", cfg.IgnoreInvalidHeaders), + buildDirective("limit_req_status", cfg.LimitReqStatusCode), + buildDirective("limit_conn_status", cfg.LimitConnStatusCode), + buildDirective("uninitialized_variable_warn", "off"), + buildDirective("server_name_in_redirect", "off"), + buildDirective("port_in_redirect", "off"), + buildDirective("ssl_protocols", strings.Split(cfg.SSLProtocols, " ")), + buildDirective("ssl_early_data", cfg.SSLEarlyData), + buildDirective("ssl_session_tickets", cfg.SSLSessionTickets), + buildDirective("ssl_buffer_size", cfg.SSLBufferSize), + buildDirective("ssl_ecdh_curve", cfg.SSLECDHCurve), + buildDirective("ssl_certificate", cfg.DefaultSSLCertificate.PemFileName), + buildDirective("ssl_certificate_key", cfg.DefaultSSLCertificate.PemFileName), + buildDirective("proxy_ssl_session_reuse", "on"), + buildDirective("proxy_cache_path", []string{ + "/tmp/nginx/nginx-cache-auth", "levels=1:2", "keys_zone=auth_cache:10m", + "max_size=128m", "inactive=30m", "use_temp_path=off", + }), + } + return httpBlock +} + +func (c *crossplaneTemplate) buildHTTP() { + cfg := c.tplConfig.Cfg + httpBlock := c.initHTTPDirectives() + httpBlock = append(httpBlock, buildLuaSharedDictionaries(&c.tplConfig.Cfg)...) + + // Real IP dealing + if (cfg.UseForwardedHeaders || cfg.UseProxyProtocol) || cfg.EnableRealIP { + if cfg.UseProxyProtocol { + httpBlock = append(httpBlock, buildDirective("real_ip_header", "proxy_protocol")) + } else { + httpBlock = append(httpBlock, buildDirective("real_ip_header", cfg.ForwardedForHeader)) + } + + for k := range cfg.ProxyRealIPCIDR { + httpBlock = append(httpBlock, buildDirective("set_real_ip_from", cfg.ProxyRealIPCIDR[k])) + } + } + + if cfg.GRPCBufferSizeKb > 0 { + httpBlock = append(httpBlock, buildDirective("grpc_buffer_size", strconv.Itoa(cfg.GRPCBufferSizeKb)+"k")) + } + + // HTTP2 Configuration + if cfg.HTTP2MaxHeaderSize != "" && cfg.HTTP2MaxFieldSize != "" { + httpBlock = append(httpBlock, buildDirective("http2_max_field_size", cfg.HTTP2MaxFieldSize)) + httpBlock = append(httpBlock, buildDirective("http2_max_header_size", cfg.HTTP2MaxHeaderSize)) + if cfg.HTTP2MaxRequests > 0 { + httpBlock = append(httpBlock, buildDirective("http2_max_requests", cfg.HTTP2MaxRequests)) + } + } + + if cfg.UseGzip { + httpBlock = append(httpBlock, buildDirective("gzip", "on")) + httpBlock = append(httpBlock, buildDirective("gzip_comp_level", cfg.GzipLevel)) + httpBlock = append(httpBlock, buildDirective("gzip_http_version", "1.1")) + httpBlock = append(httpBlock, buildDirective("gzip_min_length", cfg.GzipMinLength)) + httpBlock = append(httpBlock, buildDirective("gzip_types", cfg.GzipTypes)) + httpBlock = append(httpBlock, buildDirective("gzip_proxied", "any")) + httpBlock = append(httpBlock, buildDirective("gzip_vary", "on")) + + if cfg.GzipDisable != "" { + httpBlock = append(httpBlock, buildDirective("gzip_disable", strings.Split(cfg.GzipDisable, ""))) + } + } + + if !cfg.ShowServerTokens { + httpBlock = append(httpBlock, buildDirective("more_clear_headers", "Server")) + } + + if len(c.tplConfig.AddHeaders) > 0 { + additionalHeaders := make([]string, 0) + for headerName, headerValue := range c.tplConfig.AddHeaders { + additionalHeaders = append(additionalHeaders, fmt.Sprintf("%s: %s", headerName, headerValue)) + } + httpBlock = append(httpBlock, buildDirective("more_set_headers", additionalHeaders)) + } + + escape := "" + if cfg.LogFormatEscapeNone { + escape = "escape=none" + } else if cfg.LogFormatEscapeJSON { + escape = "escape=json" + } + + httpBlock = append(httpBlock, buildDirective("log_format", "upstreaminfo", escape, cfg.LogFormatUpstream)) + + // buildMap directive + mapLogDirective := &ngx_crossplane.Directive{ + Directive: "map", + Args: []string{"$request_uri", "$loggable"}, + Block: make(ngx_crossplane.Directives, 0), + } + for k := range cfg.SkipAccessLogURLs { + mapLogDirective.Block = append(mapLogDirective.Block, buildDirective(cfg.SkipAccessLogURLs[k], "0")) + } + mapLogDirective.Block = append(mapLogDirective.Block, buildDirective("default", "1")) + httpBlock = append(httpBlock, mapLogDirective) + // end of build mapLog + + if cfg.DisableAccessLog || cfg.DisableHTTPAccessLog { + httpBlock = append(httpBlock, buildDirective("access_log", "off")) + } else { + logDirectives := []string{"upstreaminfo", "if=$loggable"} + if cfg.EnableSyslog { + httpBlock = append(httpBlock, buildDirective("access_log", fmt.Sprintf("syslog:server%s:%d", cfg.SyslogHost, cfg.SyslogPort), logDirectives)) + } else { + accessLog := cfg.AccessLogPath + if cfg.HTTPAccessLogPath != "" { + accessLog = cfg.HTTPAccessLogPath + } + httpBlock = append(httpBlock, buildDirective("access_log", accessLog, logDirectives)) + } + } + + if cfg.EnableSyslog { + httpBlock = append(httpBlock, buildDirective("error_log", fmt.Sprintf("syslog:server%s:%d", cfg.SyslogHost, cfg.SyslogPort), cfg.ErrorLogLevel)) + } else { + httpBlock = append(httpBlock, buildDirective("error_log", cfg.ErrorLogPath, cfg.ErrorLogLevel)) + } + + c.config.Parsed = append(c.config.Parsed, &ngx_crossplane.Directive{ + Directive: "http", + Block: httpBlock, + }) +} diff --git a/internal/ingress/controller/template/crossplane/utils.go b/internal/ingress/controller/template/crossplane/utils.go new file mode 100644 index 000000000..8c07b61c2 --- /dev/null +++ b/internal/ingress/controller/template/crossplane/utils.go @@ -0,0 +1,100 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crossplane + +import ( + "fmt" + "net" + "strconv" + + ngx_crossplane "github.com/nginxinc/nginx-go-crossplane" + + "k8s.io/ingress-nginx/internal/ingress/controller/config" + ing_net "k8s.io/ingress-nginx/internal/net" +) + +type seconds int + +func buildDirective(directive string, args ...any) *ngx_crossplane.Directive { + argsVal := make([]string, 0) + for k := range args { + switch v := args[k].(type) { + case string: + argsVal = append(argsVal, v) + case []string: + argsVal = append(argsVal, v...) + case int: + argsVal = append(argsVal, strconv.Itoa(v)) + case bool: + argsVal = append(argsVal, boolToStr(v)) + case seconds: + argsVal = append(argsVal, strconv.Itoa(int(v))+"s") + } + } + return &ngx_crossplane.Directive{ + Directive: directive, + Args: argsVal, + } +} + +func buildLuaSharedDictionaries(cfg *config.Configuration) []*ngx_crossplane.Directive { + out := make([]*ngx_crossplane.Directive, 0, len(cfg.LuaSharedDicts)) + for name, size := range cfg.LuaSharedDicts { + sizeStr := dictKbToStr(size) + out = append(out, buildDirective("lua_shared_dict", name, sizeStr)) + } + + return out +} + +// TODO: The utils below should be moved to a level where they can be consumed by any template writer + +// buildResolvers returns the resolvers reading the /etc/resolv.conf file +func buildResolversInternal(res []net.IP, disableIpv6 bool) []string { + r := make([]string, 0) + for _, ns := range res { + if ing_net.IsIPV6(ns) { + if disableIpv6 { + continue + } + r = append(r, fmt.Sprintf("[%s]", ns)) + } else { + r = append(r, ns.String()) + } + } + r = append(r, "valid=30s") + + if disableIpv6 { + r = append(r, "ipv6=off") + } + + return r +} + +func boolToStr(b bool) string { + if b { + return "on" + } + return "off" +} + +func dictKbToStr(size int) string { + if size%1024 == 0 { + return fmt.Sprintf("%dM", size/1024) + } + return fmt.Sprintf("%dK", size) +}