From aa04b68d86b198ebf2a49fef69d25cfa695a34e2 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Mon, 18 Oct 2021 12:54:38 +0200 Subject: [PATCH 01/10] * add some rudimentary metrics --- packages/go/metrics/metrics.go | 15 +++++++++++++++ proxy/proxy.go | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/go/metrics/metrics.go diff --git a/packages/go/metrics/metrics.go b/packages/go/metrics/metrics.go new file mode 100644 index 0000000..33b4011 --- /dev/null +++ b/packages/go/metrics/metrics.go @@ -0,0 +1,15 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +func NewCounter(name string, help string) prometheus.Counter { + return promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "contentful", + Subsystem: "proxy", + Name: name, + Help: help, + }) +} diff --git a/proxy/proxy.go b/proxy/proxy.go index ee4514f..2de088a 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "github.com/foomo/contentfulproxy/packages/go/log" + "github.com/foomo/contentfulproxy/packages/go/metrics" "net/http" + "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" ) @@ -15,6 +17,12 @@ type Info struct { BackendURL string `json:"backendurl,omitempty"` } +type Metrics struct { + NumUpdate prometheus.Counter + NumProxyRequest prometheus.Counter + NumApiRequest prometheus.Counter +} + type Proxy struct { l *zap.Logger cache *Cache @@ -22,11 +30,13 @@ type Proxy struct { pathPrefix func() string chanRequestJob chan requestJob chanFlushJob chan requestFlush + metrics *Metrics } func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case p.pathPrefix() + "/update": + p.metrics.NumUpdate.Inc() command := requestFlush("doit") p.chanFlushJob <- command return @@ -43,6 +53,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: p.l.Info("serve get request", zap.String("url", r.RequestURI)) + p.metrics.NumProxyRequest.Inc() cacheID := getCacheIDForRequest(r) cachedResponse, ok := p.cache.get(cacheID) if !ok { @@ -59,6 +70,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { } cachedResponse = jobDone.cachedResponse p.l.Info("serve response after cache creation", log.FURL(r.RequestURI), log.FCacheId(string(cacheID))) + p.metrics.NumApiRequest.Inc() } else { p.l.Info("serve response from cache", log.FURL(r.RequestURI), log.FCacheId(string(cacheID))) } @@ -88,6 +100,7 @@ func NewProxy(ctx context.Context, l *zap.Logger, backendURL func() string, path pathPrefix: pathPrefix, chanRequestJob: chanRequest, chanFlushJob: chanFlush, + metrics: getMetrics(), }, nil } @@ -142,3 +155,10 @@ func jsonResponse(w http.ResponseWriter, v interface{}, statusCode int) { http.Error(w, "could not marshal info export", http.StatusInternalServerError) } } +func getMetrics() *Metrics { + return &Metrics{ + NumUpdate: metrics.NewCounter("numupdates", "number of times the update webhook was called"), + NumApiRequest: metrics.NewCounter("numapirequests", "number of times the proxy performed a contentful api-request"), + NumProxyRequest: metrics.NewCounter("numproxyrequests", "number of times the proxy received an api-request"), + } +} From 0668e3a09c72ebca716ee64b108a9b39edf5632f Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 15:05:41 +0100 Subject: [PATCH 02/10] feat: add release version to docker-push github action --- .github/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b61e171..6e25837 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,6 +50,10 @@ jobs: id: buildx uses: docker/setup-buildx-action@v1 + - name: Set tag on env + id: vars + run: echo ::set-env name=RELEASE_VERSION::$(echo ${GITHUB_REF#refs/*/}) + - name: Build and push id: docker_build uses: docker/build-push-action@v2 @@ -57,7 +61,7 @@ jobs: context: ./ file: ./Dockerfile push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/contentfulproxy:latest + tags: ${{ secrets.DOCKER_HUB_USERNAME }}/contentfulproxy:$RELEASE_VERSION - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} From 13d94e64ed28e358ebe93c4925b8913f14315a5e Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 15:12:50 +0100 Subject: [PATCH 03/10] chore: fix linting problems --- go.mod | 4 ++-- proxy/proxy.go | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 6f68fa5..bb7ab7f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.17 require ( github.com/foomo/keel v0.3.1 + github.com/prometheus/client_golang v1.10.0 + github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.7.0 go.uber.org/zap v1.19.1 ) @@ -27,7 +29,6 @@ require ( github.com/pelletier/go-toml v1.7.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.10.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.18.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect @@ -36,7 +37,6 @@ require ( github.com/spf13/cast v1.3.0 // indirect github.com/spf13/jwalterweatherman v1.0.0 // indirect github.com/spf13/pflag v1.0.3 // indirect - github.com/spf13/viper v1.7.1 // indirect github.com/subosito/gotenv v1.2.0 // indirect go.opentelemetry.io/contrib v0.20.0 // indirect go.opentelemetry.io/contrib/instrumentation/host v0.20.0 // indirect diff --git a/proxy/proxy.go b/proxy/proxy.go index 055893d..4d3ee40 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -3,9 +3,10 @@ package proxy import ( "context" "encoding/json" - "github.com/foomo/contentfulproxy/packages/go/metrics" "net/http" + "github.com/foomo/contentfulproxy/packages/go/metrics" + "github.com/prometheus/client_golang/prometheus" "github.com/foomo/contentfulproxy/packages/go/log" @@ -21,7 +22,7 @@ type Info struct { type Metrics struct { NumUpdate prometheus.Counter NumProxyRequest prometheus.Counter - NumApiRequest prometheus.Counter + NumAPIRequest prometheus.Counter } type Proxy struct { @@ -71,7 +72,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { } cachedResponse = jobDone.cachedResponse p.l.Info("serve response after cache creation", log.FURL(r.RequestURI), log.FCacheID(string(cacheID))) - p.metrics.NumApiRequest.Inc() + p.metrics.NumAPIRequest.Inc() } else { p.l.Info("serve response from cache", log.FURL(r.RequestURI), log.FCacheID(string(cacheID))) } @@ -156,10 +157,11 @@ func jsonResponse(w http.ResponseWriter, v interface{}, statusCode int) { http.Error(w, "could not marshal info export", http.StatusInternalServerError) } } + func getMetrics() *Metrics { return &Metrics{ - NumUpdate: metrics.NewCounter("numupdates", "number of times the update webhook was called"), - NumApiRequest: metrics.NewCounter("numapirequests", "number of times the proxy performed a contentful api-request"), + NumUpdate: metrics.NewCounter("numupdates", "number of times the update webhook was called"), + NumAPIRequest: metrics.NewCounter("numapirequests", "number of times the proxy performed a contentful api-request"), NumProxyRequest: metrics.NewCounter("numproxyrequests", "number of times the proxy received an api-request"), } } From a1279e10fd679065cdeab49618c4b4696426abd2 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 15:35:14 +0100 Subject: [PATCH 04/10] devops: docker-hub release version --- .github/workflows/release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e25837..2bcaf71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,13 +50,15 @@ jobs: id: buildx uses: docker/setup-buildx-action@v1 - - name: Set tag on env + - name: Set tag on output id: vars - run: echo ::set-env name=RELEASE_VERSION::$(echo ${GITHUB_REF#refs/*/}) + run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} - name: Build and push id: docker_build uses: docker/build-push-action@v2 + env: + RELEASE_VERSION: ${{ steps.vars.outputs.tag }} with: context: ./ file: ./Dockerfile From faeea9fabf53965824fc945fb8ab59a71683d6b6 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 15:52:23 +0100 Subject: [PATCH 05/10] devops: docker-hub release version --- .github/workflows/release.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2bcaf71..60d1df5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,6 +40,12 @@ jobs: - name: Check Out Repo uses: actions/checkout@v2 + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: foomo/contentfulproxy + - name: Login to Docker Hub uses: docker/login-action@v1 with: @@ -50,20 +56,15 @@ jobs: id: buildx uses: docker/setup-buildx-action@v1 - - name: Set tag on output - id: vars - run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} - - name: Build and push id: docker_build uses: docker/build-push-action@v2 - env: - RELEASE_VERSION: ${{ steps.vars.outputs.tag }} with: context: ./ file: ./Dockerfile push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/contentfulproxy:$RELEASE_VERSION + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} From 7b799e8d584ce002bf8a0900e7e130b63d8b4326 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 16:02:11 +0100 Subject: [PATCH 06/10] devops: docker-hub release version --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60d1df5..af3a3df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,6 +45,11 @@ jobs: uses: docker/metadata-action@v3 with: images: foomo/contentfulproxy + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} - name: Login to Docker Hub uses: docker/login-action@v1 From 147fa4d2827ef539cbb242c69b7a91b868968ca5 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 16:08:03 +0100 Subject: [PATCH 07/10] devops: docker-hub release version --- .github/workflows/release.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af3a3df..8dd1161 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,9 +46,6 @@ jobs: with: images: foomo/contentfulproxy tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Login to Docker Hub From 84693b15a1d6b2383365e81bd6b495a801b01037 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 16 Nov 2021 16:18:39 +0100 Subject: [PATCH 08/10] devops: docker-hub release version --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8dd1161..a4e99cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: with: images: foomo/contentfulproxy tags: | - type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} - name: Login to Docker Hub uses: docker/login-action@v1 From 4dc8b8fcd51baa486669add9a3f6cd71191f9cfe Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 23 Nov 2021 16:42:24 +0100 Subject: [PATCH 09/10] * fixed missing stripping of path-segment * added logging of error messages --- Dockerfile | 1 + proxy/cache.go | 8 ++++++-- proxy/jobs.go | 13 ++++++++++--- proxy/proxy.go | 16 ++++++++++------ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 56b0509..440812b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN upx /src/bin/contentfulproxy ############################## ###### STAGE: PACKAGE ###### ############################## +# TODO: use non-root FROM alpine:latest ENV CONTENTFULPROXY_SERVER_ADDR=0.0.0.0:80 diff --git a/proxy/cache.go b/proxy/cache.go index 7ed3512..0708f40 100644 --- a/proxy/cache.go +++ b/proxy/cache.go @@ -85,8 +85,8 @@ func NewCache(l *zap.Logger, webHooks func() []string) *Cache { return c } -func getCacheIDForRequest(r *http.Request) cacheID { - id := r.URL.RequestURI() +func getCacheIDForRequest(r *http.Request, pathPrefix func() string) cacheID { + id := stripPrefixFromUrl(r.URL.RequestURI(), pathPrefix) keys := make([]string, len(r.Header)) i := 0 for k := range r.Header { @@ -107,3 +107,7 @@ func getCacheIDForRequest(r *http.Request) cacheID { id = hex.EncodeToString(hash.Sum(nil)) return cacheID(id) } + +func stripPrefixFromUrl(url string, pathPrefix func() string) string { + return strings.Replace(url, pathPrefix(), "", 1) +} diff --git a/proxy/jobs.go b/proxy/jobs.go index d659650..a3e1c1c 100644 --- a/proxy/jobs.go +++ b/proxy/jobs.go @@ -1,6 +1,10 @@ package proxy -import "net/http" +import ( + "github.com/foomo/contentfulproxy/packages/go/log" + "go.uber.org/zap" + "net/http" +) type requestJobDone struct { cachedResponse *cachedResponse @@ -15,9 +19,12 @@ type requestJob struct { type jobRunner func(job requestJob, id cacheID) -func getJobRunner(c *Cache, backendURL func() string, chanJobDone chan requestJobDone) jobRunner { +func getJobRunner(l *zap.Logger, c *Cache, backendURL func() string, pathPrefix func() string, chanJobDone chan requestJobDone) jobRunner { return func(job requestJob, id cacheID) { - req, err := http.NewRequest("GET", backendURL()+job.request.URL.RequestURI(), nil) + // backend url is the contentful api domain like https://cdn.contenful.com + calledURL := backendURL() + stripPrefixFromUrl(job.request.URL.RequestURI(), pathPrefix) + l.Info("URL called by job-runner", log.FURL(calledURL)) + req, err := http.NewRequest("GET", calledURL, nil) if err != nil { chanJobDone <- requestJobDone{ id: id, diff --git a/proxy/proxy.go b/proxy/proxy.go index 4d3ee40..e7698bd 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -10,6 +10,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/foomo/contentfulproxy/packages/go/log" + keellog "github.com/foomo/keel/log" "go.uber.org/zap" ) @@ -56,7 +57,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { case http.MethodGet: p.l.Info("serve get request", zap.String("url", r.RequestURI)) p.metrics.NumProxyRequest.Inc() - cacheID := getCacheIDForRequest(r) + cacheID := getCacheIDForRequest(r, p.pathPrefix) cachedResponse, ok := p.cache.get(cacheID) if !ok { chanDone := make(chan requestJobDone) @@ -66,15 +67,17 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { } jobDone := <-chanDone if jobDone.err != nil { - p.l.Error("Cache / job error", zap.String("url", r.RequestURI)) + keellog.WithError(p.l, jobDone.err).Error("Cache / job error") http.Error(w, "Cache / job error", http.StatusInternalServerError) return } cachedResponse = jobDone.cachedResponse p.l.Info("serve response after cache creation", log.FURL(r.RequestURI), log.FCacheID(string(cacheID))) + p.l.Info("length of response", keellog.FValue(len(cachedResponse.response))) p.metrics.NumAPIRequest.Inc() } else { p.l.Info("serve response from cache", log.FURL(r.RequestURI), log.FCacheID(string(cacheID))) + p.l.Info("length of response", keellog.FValue(len(cachedResponse.response))) } for key, values := range cachedResponse.header { for _, value := range values { @@ -83,7 +86,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { } _, err := w.Write(cachedResponse.response) if err != nil { - p.l.Info("writing cached response failed", log.FURL(r.RequestURI), log.FCacheID(string(cacheID))) + keellog.WithError(p.l, err).Error("writing cached response failed", log.FCacheID(string(cacheID))) } default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) @@ -94,7 +97,7 @@ func NewProxy(ctx context.Context, l *zap.Logger, backendURL func() string, path chanRequest := make(chan requestJob) chanFlush := make(chan requestFlush) c := NewCache(l, webHooks) - go getLoop(ctx, l, backendURL, c, chanRequest, chanFlush) + go getLoop(ctx, l, backendURL, pathPrefix, c, chanRequest, chanFlush) return &Proxy{ l: l, cache: c, @@ -110,13 +113,14 @@ func getLoop( ctx context.Context, l *zap.Logger, backendURL func() string, + pathPrefix func() string, c *Cache, chanRequestJob chan requestJob, chanFlush chan requestFlush, ) { pendingRequests := map[cacheID][]chan requestJobDone{} chanJobDone := make(chan requestJobDone) - jobRunner := getJobRunner(c, backendURL, chanJobDone) + jobRunner := getJobRunner(l, c, backendURL, pathPrefix, chanJobDone) for { select { case <-chanFlush: @@ -124,7 +128,7 @@ func getLoop( c.update() c.callWebHooks() case nextJob := <-chanRequestJob: - cacheID := getCacheIDForRequest(nextJob.request) + cacheID := getCacheIDForRequest(nextJob.request, pathPrefix) pendingRequests[cacheID] = append(pendingRequests[cacheID], nextJob.chanDone) requests := pendingRequests[cacheID] if len(requests) == 1 { From 5a5a170f2e0af856f62d6f4ddda47af7b560a887 Mon Sep 17 00:00:00 2001 From: Daniel Thomas Date: Tue, 23 Nov 2021 16:47:05 +0100 Subject: [PATCH 10/10] * fix linting issues --- proxy/cache.go | 4 ++-- proxy/jobs.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/proxy/cache.go b/proxy/cache.go index 0708f40..19aee7a 100644 --- a/proxy/cache.go +++ b/proxy/cache.go @@ -86,7 +86,7 @@ func NewCache(l *zap.Logger, webHooks func() []string) *Cache { } func getCacheIDForRequest(r *http.Request, pathPrefix func() string) cacheID { - id := stripPrefixFromUrl(r.URL.RequestURI(), pathPrefix) + id := stripPrefixFromURL(r.URL.RequestURI(), pathPrefix) keys := make([]string, len(r.Header)) i := 0 for k := range r.Header { @@ -108,6 +108,6 @@ func getCacheIDForRequest(r *http.Request, pathPrefix func() string) cacheID { return cacheID(id) } -func stripPrefixFromUrl(url string, pathPrefix func() string) string { +func stripPrefixFromURL(url string, pathPrefix func() string) string { return strings.Replace(url, pathPrefix(), "", 1) } diff --git a/proxy/jobs.go b/proxy/jobs.go index a3e1c1c..9c17c81 100644 --- a/proxy/jobs.go +++ b/proxy/jobs.go @@ -1,9 +1,10 @@ package proxy import ( + "net/http" + "github.com/foomo/contentfulproxy/packages/go/log" "go.uber.org/zap" - "net/http" ) type requestJobDone struct { @@ -22,7 +23,7 @@ type jobRunner func(job requestJob, id cacheID) func getJobRunner(l *zap.Logger, c *Cache, backendURL func() string, pathPrefix func() string, chanJobDone chan requestJobDone) jobRunner { return func(job requestJob, id cacheID) { // backend url is the contentful api domain like https://cdn.contenful.com - calledURL := backendURL() + stripPrefixFromUrl(job.request.URL.RequestURI(), pathPrefix) + calledURL := backendURL() + stripPrefixFromURL(job.request.URL.RequestURI(), pathPrefix) l.Info("URL called by job-runner", log.FURL(calledURL)) req, err := http.NewRequest("GET", calledURL, nil) if err != nil {