diff --git a/.gitignore b/.gitignore index 50fd426..241d36d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bin vendor .idea/ dist/ +ingress-controller diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d79580..1616820 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ kind export kubeconfig ## Setup development env -Replace also the domain name to use in `hack/test/example-ingress.yaml` from `kubernetes.localhost` to your domain (ensure also that the subdomain `example1` and `example2` are resolved to the server public IP) +Replace also the domain name to use in `kubernetes/sample/example-ingress.yaml` from `kubernetes.localhost` to your domain (ensure also that the subdomain `example1` and `example2` are resolved to the server public IP) Create a namespace to host the caddy ingress controller: ``` @@ -36,7 +36,7 @@ kubectl create ns caddy-system Then we can start skaffold using: ``` -skaffold dev --port-forward +make dev ``` this will automatically: diff --git a/Makefile b/Makefile index a47c94e..2a26943 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ build: @mkdir -p bin CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ingress-controller ./cmd/caddy + +dev: build + skaffold dev --port-forward diff --git a/charts/caddy-ingress-controller/templates/deployment.yaml b/charts/caddy-ingress-controller/templates/deployment.yaml index 988ffbf..ff3c5fe 100644 --- a/charts/caddy-ingress-controller/templates/deployment.yaml +++ b/charts/caddy-ingress-controller/templates/deployment.yaml @@ -87,7 +87,7 @@ spec: periodSeconds: 10 httpGet: port: 9765 - path: /metrics + path: /healthz {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/go.mod b/go.mod index f76ee21..9e8abff 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,11 @@ require ( k8s.io/client-go v0.23.6 ) +require ( + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect +) + require ( filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect @@ -100,6 +105,7 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/stretchr/testify v1.8.0 github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74 // indirect github.com/urfave/cli v1.22.5 // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -128,7 +134,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect howett.net/plist v1.0.0 // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect diff --git a/go.sum b/go.sum index 0e4385c..4216c8e 100644 --- a/go.sum +++ b/go.sum @@ -1074,6 +1074,8 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1083,6 +1085,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74 h1:uFx5aih29p2IaRUF0lJwtVViCXStlvnPPE3NEmM4Ivs= github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0= @@ -1862,6 +1866,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/caddy/convert_test.go b/internal/caddy/convert_test.go new file mode 100644 index 0000000..543263a --- /dev/null +++ b/internal/caddy/convert_test.go @@ -0,0 +1,37 @@ +package caddy + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/caddyserver/ingress/pkg/store" +) + +func TestConvertToCaddyConfig(t *testing.T) { + tests := []struct { + name string + expectedConfigPath string + }{ + { + name: "default", + expectedConfigPath: "./test_data/default.json", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg, err := Converter{}.ConvertToCaddyConfig(store.NewStore(store.Options{}, &store.PodInfo{})) + require.NoError(t, err) + + cfgJson, err := json.Marshal(cfg) + require.NoError(t, err) + + expectedCfg, err := os.ReadFile(test.expectedConfigPath) + require.NoError(t, err) + + require.JSONEq(t, string(expectedCfg), string(cfgJson)) + }) + } +} diff --git a/internal/caddy/global/healthz.go b/internal/caddy/global/healthz.go new file mode 100644 index 0000000..6b9a4d6 --- /dev/null +++ b/internal/caddy/global/healthz.go @@ -0,0 +1,48 @@ +package global + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/ingress/pkg/converter" + "github.com/caddyserver/ingress/pkg/store" +) + +type HealthzPlugin struct{} + +func (p HealthzPlugin) IngressPlugin() converter.PluginInfo { + return converter.PluginInfo{ + Name: "healthz", + Priority: -20, + New: func() converter.Plugin { return new(HealthzPlugin) }, + } +} + +func init() { + converter.RegisterPlugin(HealthzPlugin{}) +} + +func (p HealthzPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { + healthzHandler := caddyhttp.StaticResponse{StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusOK))} + + healthzRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(healthzHandler, "handler", healthzHandler.CaddyModule().ID.Name(), nil), + }, + MatcherSetsRaw: []caddy.ModuleMap{{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{"/healthz"}, nil), + }}, + } + + config.GetMetricsServer().Routes = append(config.GetMetricsServer().Routes, healthzRoute) + return nil +} + +// Interface guards +var ( + _ = converter.GlobalMiddleware(HealthzPlugin{}) +) diff --git a/internal/caddy/global/metrics.go b/internal/caddy/global/metrics.go index ee1e868..70fc785 100644 --- a/internal/caddy/global/metrics.go +++ b/internal/caddy/global/metrics.go @@ -23,20 +23,15 @@ func init() { } func (p MetricsPlugin) GlobalHandler(config *converter.Config, store *store.Store) error { - httpApp := config.Apps["http"].(*caddyhttp.App) - if store.ConfigMap.Metrics { - httpApp.Servers[converter.MetricsServer] = &caddyhttp.Server{ - Listen: []string{":9765"}, - AutoHTTPS: &caddyhttp.AutoHTTPSConfig{Disabled: true}, - Routes: []caddyhttp.Route{{ - HandlersRaw: []json.RawMessage{json.RawMessage(`{ "handler": "metrics" }`)}, - MatcherSetsRaw: []caddy.ModuleMap{{ - "path": caddyconfig.JSON(caddyhttp.MatchPath{"/metrics"}, nil), - }}, + metricsRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{json.RawMessage(`{ "handler": "metrics" }`)}, + MatcherSetsRaw: []caddy.ModuleMap{{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{"/metrics"}, nil), }}, } - + + config.GetMetricsServer().Routes = append(config.GetMetricsServer().Routes, metricsRoute) } return nil } diff --git a/internal/caddy/test_data/default.json b/internal/caddy/test_data/default.json new file mode 100644 index 0000000..1156fa5 --- /dev/null +++ b/internal/caddy/test_data/default.json @@ -0,0 +1,27 @@ +{ + "admin": {}, + "storage": { "module": "secret_store", "namespace": "", "leaseId": "" }, + "apps": { + "http": { + "servers": { + "ingress_server": { + "listen": [":80", ":443"], + "tls_connection_policies": [{}], + "automatic_https": {} + }, + "metrics_server": { + "listen": [":9765"], + "routes": [ + { + "match": [{ "path": ["/healthz"] }], + "handle": [{ "handler": "static_response", "status_code": 200 }] + } + ], + "automatic_https": { "disable": true } + } + } + }, + "tls": {} + }, + "logging": {} +} diff --git a/pkg/converter/config.go b/pkg/converter/config.go index ec69f2a..ffb7805 100644 --- a/pkg/converter/config.go +++ b/pkg/converter/config.go @@ -30,6 +30,10 @@ func (c Config) GetHTTPServer() *caddyhttp.Server { return c.Apps["http"].(*caddyhttp.App).Servers[HttpServer] } +func (c Config) GetMetricsServer() *caddyhttp.Server { + return c.Apps["http"].(*caddyhttp.App).Servers[MetricsServer] +} + func (c Config) GetTLSApp() *caddytls.TLS { return c.Apps["tls"].(*caddytls.TLS) } @@ -50,6 +54,10 @@ func NewConfig() *Config { &caddytls.ConnectionPolicy{}, }, }, + MetricsServer: { + Listen: []string{":9765"}, + AutoHTTPS: &caddyhttp.AutoHTTPSConfig{Disabled: true}, + }, }, }, }, diff --git a/skaffold.yaml b/skaffold.yaml index deba087..a0236bb 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -1,4 +1,4 @@ -apiVersion: skaffold/v2beta3 +apiVersion: skaffold/v2beta29 kind: Config metadata: name: caddy-ingress-controller @@ -12,6 +12,10 @@ deploy: namespace: caddy-system chartPath: charts/caddy-ingress-controller recreatePods: true + artifactOverrides: + image: caddy/ingress + imageStrategy: + helm: {} kubectl: manifests: - kubernetes/sample/*.yaml