diff --git a/controllers/controller.go b/controllers/controller.go index 405fabc78..ce7060505 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -819,11 +819,17 @@ func (r *RpaasInstanceReconciler) renderTemplate(ctx context.Context, instance * return "", err } - return cr.Render(nginx.ConfigurationData{ - Instance: instance, - Config: &plan.Spec.Config, - LoadedModules: modules, - }) + config := nginx.ConfigurationData{ + Instance: instance, + Config: &plan.Spec.Config, + Modules: make(map[string]interface{}), + } + + for _, mod := range modules { + config.Modules[mod] = nil + } + + return cr.Render(config) } func (r *RpaasInstanceReconciler) getConfigurationBlocks(ctx context.Context, instance *v1alpha1.RpaasInstance, plan *v1alpha1.RpaasPlan) (nginx.ConfigurationBlocks, error) { diff --git a/controllers/controller_test.go b/controllers/controller_test.go index a293ed0a6..e8c3e87dd 100644 --- a/controllers/controller_test.go +++ b/controllers/controller_test.go @@ -1961,6 +1961,12 @@ func Test_nameForCronJob(t *testing.T) { } } +type fakeImageMetadata struct{} + +func (i *fakeImageMetadata) Modules(img string) ([]string, error) { + return []string{"mod1"}, nil +} + func newRpaasInstanceReconciler(objs ...runtime.Object) *RpaasInstanceReconciler { scheme := extensionsruntime.NewScheme() return &RpaasInstanceReconciler{ @@ -1968,5 +1974,6 @@ func newRpaasInstanceReconciler(objs ...runtime.Object) *RpaasInstanceReconciler Log: ctrl.Log, Scheme: scheme, RolloutNginxEnabled: true, + ImageMetadata: &fakeImageMetadata{}, } } diff --git a/go.mod b/go.mod index df31500e0..13062fd48 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.13 require ( github.com/HdrHistogram/hdrhistogram-go v1.0.0 // indirect + github.com/Masterminds/sprig/v3 v3.1.0 github.com/ajg/form v1.5.1 github.com/davecgh/go-spew v1.1.1 + github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 github.com/fsnotify/fsnotify v1.4.9 github.com/globocom/echo-prometheus v0.1.2 github.com/go-logr/logr v0.2.1 @@ -13,6 +15,7 @@ require ( github.com/google/gops v0.3.12 github.com/gorilla/websocket v1.4.2 github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5 + github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.11 github.com/labstack/echo/v4 v4.1.17 github.com/mitchellh/mapstructure v1.1.2 diff --git a/go.sum b/go.sum index fc0fb38c8..6b80539a7 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,14 @@ github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5H github.com/HdrHistogram/hdrhistogram-go v1.0.0 h1:jivTvI9tBw5B8wW9Qd0uoQ2qaajb29y4TPhYTgh8Lb0= github.com/HdrHistogram/hdrhistogram-go v1.0.0/go.mod h1:YzE1EgsuAz8q9lfGdlxBZo2Ma655+PfKp2mlzcAqIFw= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y= +github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= @@ -419,9 +425,13 @@ github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= @@ -526,6 +536,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -540,6 +552,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -697,6 +711,8 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -828,6 +844,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/internal/pkg/rpaas/nginx/configuration_render.go b/internal/pkg/rpaas/nginx/configuration_render.go index b61e4ecb9..fa68f7491 100644 --- a/internal/pkg/rpaas/nginx/configuration_render.go +++ b/internal/pkg/rpaas/nginx/configuration_render.go @@ -12,6 +12,7 @@ import ( "strings" "text/template" + sprig "github.com/Masterminds/sprig/v3" "github.com/tsuru/rpaas-operator/api/v1alpha1" "github.com/tsuru/rpaas-operator/pkg/util" "k8s.io/apimachinery/pkg/api/resource" @@ -33,9 +34,11 @@ type ConfigurationBlocks struct { } type ConfigurationData struct { - Config *v1alpha1.NginxConfig - Instance *v1alpha1.RpaasInstance - LoadedModules []string + Config *v1alpha1.NginxConfig + Instance *v1alpha1.RpaasInstance + // Modules is a map of installed modules, using a map instead of a slice + // allow us to use `hasKey` inside templates. + Modules map[string]interface{} } type rpaasConfigurationRenderer struct { @@ -190,7 +193,7 @@ func tlsSessionTicketTimeout(instance *v1alpha1.RpaasInstance) int { return nkeys * int(keyRotationInterval) } -var templateFuncs = template.FuncMap(map[string]interface{}{ +var internalTemplateFuncs = template.FuncMap(map[string]interface{}{ "boolValue": v1alpha1.BoolValue, "buildLocationKey": buildLocationKey, "hasRootPath": hasRootPath, @@ -217,6 +220,14 @@ var templateFuncs = template.FuncMap(map[string]interface{}{ }, }) +var templateFuncs = func() template.FuncMap { + funcs := sprig.GenericFuncMap() + for k, v := range internalTemplateFuncs { + funcs[k] = v + } + return template.FuncMap(funcs) +}() + var defaultMainTemplate = template.Must(template.New("main"). Funcs(templateFuncs). Parse(rawNginxConfiguration)) @@ -230,6 +241,7 @@ var rawNginxConfiguration = ` {{- $all := . -}} {{- $config := .Config -}} {{- $instance := .Instance -}} +{{- $modules := .Modules -}} # This file was generated by RPaaS (https://github.com/tsuru/rpaas-operator.git) # Do not modify this file, any change will be lost. @@ -242,7 +254,12 @@ user {{ . }}; worker_processes {{ . }}; {{- end }} + +{{- range $mod, $_ := $modules }} +load_module "modules/{{ $mod }}.so"; +{{- else }} include modules/*.conf; +{{- end }} {{ template "root" . }} diff --git a/internal/pkg/rpaas/nginx/configuration_render_test.go b/internal/pkg/rpaas/nginx/configuration_render_test.go index d963e036e..c6ebb15e6 100644 --- a/internal/pkg/rpaas/nginx/configuration_render_test.go +++ b/internal/pkg/rpaas/nginx/configuration_render_test.go @@ -36,6 +36,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { assert.NotRegexp(t, `user(.+);`, result) assert.NotRegexp(t, `worker_processes(.+);`, result) assert.NotRegexp(t, `worker_connections(.+);`, result) + assert.Regexp(t, `include modules/\*\.conf;`, result) assert.Regexp(t, `access_log /dev/stdout combined;`, result) assert.Regexp(t, `error_log /dev/stderr;`, result) assert.Regexp(t, `server {\n\s+listen 8800;\n\s+}\n+`, result) @@ -514,6 +515,19 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { \s+\}`, result) }, }, + { + name: "with custom modules", + data: ConfigurationData{ + Config: &v1alpha1.NginxConfig{}, + Instance: &v1alpha1.RpaasInstance{}, + Modules: map[string]interface{}{"mod1": nil, "mod2": nil}, + }, + assertion: func(t *testing.T, result string) { + assert.NotRegexp(t, `include modules/\*\.conf;`, result) + assert.Regexp(t, `load_module "modules/mod1.so";`, result) + assert.Regexp(t, `load_module "modules/mod2.so";`, result) + }, + }, } for _, tt := range tests { diff --git a/internal/registry/image.go b/internal/registry/image.go index fed6026ed..7df3e465b 100644 --- a/internal/registry/image.go +++ b/internal/registry/image.go @@ -5,6 +5,7 @@ package registry import ( + "crypto/tls" "encoding/json" "net/http" "strings" @@ -22,18 +23,21 @@ type ImageMetadata interface { } type imageMetadataRetriever struct { - mu sync.Mutex - labelsCache map[string]map[string]string + mu sync.Mutex + labelsCache map[string]map[string]string + registryCache map[string]*registry.Registry + insecure bool } func NewImageMetadata() *imageMetadataRetriever { return &imageMetadataRetriever{ - labelsCache: map[string]map[string]string{}, + labelsCache: map[string]map[string]string{}, + registryCache: map[string]*registry.Registry{}, } } func (r *imageMetadataRetriever) Modules(image string) ([]string, error) { - labels, err := r.getLabels(image) + labels, err := r.cachedLabels(image) if err != nil { return nil, err } @@ -47,7 +51,7 @@ func (r *imageMetadataRetriever) Modules(image string) ([]string, error) { return strings.Split(labels[modulesLabel], ","), nil } -func (r *imageMetadataRetriever) getLabels(image string) (map[string]string, error) { +func (r *imageMetadataRetriever) cachedLabels(image string) (map[string]string, error) { r.mu.Lock() defer r.mu.Unlock() @@ -55,7 +59,7 @@ func (r *imageMetadataRetriever) getLabels(image string) (map[string]string, err return r.labelsCache[image], nil } - labels, err := getImageLabels(image) + labels, err := r.imageLabels(image) if err != nil { return nil, err } @@ -64,7 +68,7 @@ func (r *imageMetadataRetriever) getLabels(image string) (map[string]string, err return labels, nil } -func getImageLabels(image string) (map[string]string, error) { +func (r *imageMetadataRetriever) imageLabels(image string) (map[string]string, error) { type historyEntry struct { Config struct { Labels map[string]string @@ -72,7 +76,7 @@ func getImageLabels(image string) (map[string]string, error) { } parts := parseImage(image) - hub := newRegistry(parts.registry) + hub := r.registry(parts.registry) manifest, err := hub.Manifest(parts.image, parts.tag) if err != nil { return nil, err @@ -91,6 +95,31 @@ func getImageLabels(image string) (map[string]string, error) { return entry.Config.Labels, nil } +func (r *imageMetadataRetriever) registry(registryHost string) *registry.Registry { + if reg := r.registryCache[registryHost]; reg != nil { + return reg + } + url := "https://" + registryHost + transport := http.DefaultTransport + if r.insecure { + newTransport := http.DefaultTransport.(*http.Transport).Clone() + newTransport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } + transport = newTransport + } + reg := ®istry.Registry{ + URL: url, + Client: &http.Client{ + Transport: registry.WrapTransport(transport, url, "", ""), + Timeout: 30 * time.Second, + }, + Logf: registry.Quiet, + } + r.registryCache[registryHost] = reg + return reg +} + type dockerImage struct { registry string image string @@ -124,15 +153,3 @@ func parseImage(imageName string) dockerImage { } return img } - -func newRegistry(registryHost string) *registry.Registry { - url := "https://" + registryHost - return ®istry.Registry{ - URL: url, - Client: &http.Client{ - Transport: registry.WrapTransport(http.DefaultTransport, url, "", ""), - Timeout: 30 * time.Second, - }, - Logf: registry.Quiet, - } -} diff --git a/internal/registry/image_test.go b/internal/registry/image_test.go index 71bb600f5..58834dbb2 100644 --- a/internal/registry/image_test.go +++ b/internal/registry/image_test.go @@ -1,11 +1,52 @@ package registry import ( + "net/http" + "net/http/httptest" + "net/url" "testing" + "github.com/docker/libtrust" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestImageMetadataRetriever_Modules(t *testing.T) { + r := NewImageMetadata() + r.insecure = true + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v2/my/img/manifests/v1", r.URL.Path) + assert.Equal(t, "GET", r.Method) + w.Header().Add("Content-Type", "application/vnd.docker.distribution.manifest.v1+json") + sig, err := libtrust.NewJSONSignatureFromMap(map[string]interface{}{ + "schemaVersion": 1, + "name": "my/img", + "tag": "v1", + "history": []map[string]interface{}{{ + "v1Compatibility": `{ + "config":{ + "Labels":{"io.tsuru.nginx-modules":"mod1,mod2"} + } + }`, + }}, + }) + require.NoError(t, err) + pk, err := libtrust.GenerateECP256PrivateKey() + require.NoError(t, err) + err = sig.Sign(pk) + require.NoError(t, err) + data, err := sig.PrettySignature("signatures") + require.NoError(t, err) + w.Write(data) + })) + defer srv.Close() + u, err := url.Parse(srv.URL) + require.NoError(t, err) + mod, err := r.Modules(u.Host + "/my/img:v1") + require.NoError(t, err) + assert.Equal(t, []string{"mod1", "mod2"}, mod) +} + func TestParseImage(t *testing.T) { tests := []struct { imageURI string