From 05753d63b39f43b072a5a5dfc8a3f6cb60924fad Mon Sep 17 00:00:00 2001 From: Piotr Biernat Date: Sat, 20 Jul 2024 12:59:01 +0200 Subject: [PATCH] refactor --- .app.config | 13 ++ .gitignore | 2 +- .gitmodules | 6 +- Dockerfile | 11 +- api-gateway/entrypoint.sh | 8 +- api-gateway/etc/tls.yml | 10 +- api-gateway/etc/traefik.yml | 53 +++--- .../traefik/plugin-requestid/go.mod | 3 - .../traefik/plugin-requestid/.traefik.yml | 2 +- .../traefik/plugin-requestid/README.md | 0 .../traefik/plugin-requestid/go.mod | 3 + .../traefik/plugin-requestid/requestid.go | 0 .../traefik/plugin-requestid/uuid.go | 0 .../fusionmedialimited/retryif/.traefik.yml | 11 ++ .../fusionmedialimited/retryif/Makefile | 20 ++ .../fusionmedialimited/retryif/go.mod | 3 + .../fusionmedialimited/retryif/retryif.go | 179 ++++++++++++++++++ deploy/image-build.sh | 2 +- deploy/image-push.sh | 6 +- 19 files changed, 288 insertions(+), 44 deletions(-) create mode 100644 .app.config delete mode 100644 api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/go.mod rename api-gateway/plugins/src/{git.pbiernat.dev => git.pbiernat.io}/traefik/plugin-requestid/.traefik.yml (62%) rename api-gateway/plugins/src/{git.pbiernat.dev => git.pbiernat.io}/traefik/plugin-requestid/README.md (100%) create mode 100644 api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/go.mod rename api-gateway/plugins/src/{git.pbiernat.dev => git.pbiernat.io}/traefik/plugin-requestid/requestid.go (100%) rename api-gateway/plugins/src/{git.pbiernat.dev => git.pbiernat.io}/traefik/plugin-requestid/uuid.go (100%) create mode 100644 api-gateway/plugins/src/github.com/fusionmedialimited/retryif/.traefik.yml create mode 100644 api-gateway/plugins/src/github.com/fusionmedialimited/retryif/Makefile create mode 100644 api-gateway/plugins/src/github.com/fusionmedialimited/retryif/go.mod create mode 100644 api-gateway/plugins/src/github.com/fusionmedialimited/retryif/retryif.go diff --git a/.app.config b/.app.config new file mode 100644 index 0000000..29a3a0f --- /dev/null +++ b/.app.config @@ -0,0 +1,13 @@ +{ + "ID": "api-gateway", + "Name": "api-gateway", + "Address": "__IP__", + "Port": 443, + "Check": { + "HTTP": "http://__IP__:443/ping", + "Interval": "10s", + "Timeout": "1s", + "Status": "passing", + "DeregisterCriticalServiceAfter": "10s" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d353ed6..ffb0e45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vscode -api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-egommerce-auth \ No newline at end of file +api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-egommerce-auth \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 7cf439d..2def5d0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-egommerce-auth"] - path = api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-egommerce-auth - url = git@git.pbiernat.dev:traefik/plugin-egommerce-auth.git +[submodule "api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-egommerce-auth"] + path = api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-egommerce-auth + url = git@git.pbiernat.io:traefik/plugin-egommerce-auth.git diff --git a/Dockerfile b/Dockerfile index 8b793a9..790315b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM traefik:v3.0 +FROM traefik:v3.1 ARG BUILD_TIME @@ -8,11 +8,14 @@ LABEL dev.egommerce.image.service="api-gateway" LABEL dev.egommerce.image.version="1.0" LABEL dev.egommerce.image.build_time=${BUILD_TIME} +# Fix for running Go apps in container @https://stackoverflow.com/a/35613430 +# RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 + COPY ./api-gateway/etc /etc/traefik COPY ./api-gateway/plugins /plugins-local -COPY ./api-gateway/entrypoint.sh ./api-gateway/wait-for-it.sh / +COPY ./api-gateway/entrypoint.sh ./api-gateway/wait-for-it.sh ./.app.config / + +EXPOSE 443 8080 ENTRYPOINT ["/entrypoint.sh"] CMD ["traefik"] - -EXPOSE 443 8080 diff --git a/api-gateway/entrypoint.sh b/api-gateway/entrypoint.sh index 44d45c7..20a3cde 100755 --- a/api-gateway/entrypoint.sh +++ b/api-gateway/entrypoint.sh @@ -1,5 +1,8 @@ #!/bin/sh -set +e +set +e + +update-ca-certificates +update-resolv waitForService() { @@ -33,5 +36,6 @@ else echo "= '$1' is not a Traefik command: assuming shell execution." 1>&2 fi -# echo "Executing: $@" +register-service + exec "$@" diff --git a/api-gateway/etc/tls.yml b/api-gateway/etc/tls.yml index 73eb169..8bda6af 100644 --- a/api-gateway/etc/tls.yml +++ b/api-gateway/etc/tls.yml @@ -1,9 +1,11 @@ tls: certificates: - certFile: /etc/traefik/certs/gateway.pem - keyFile: /etc/traefik/certs/gateway.key + - certFile: "/etc/traefik/certs/gw.internal.crt" + keyFile: "/etc/traefik/certs/gw.key" + - certFile: "/etc/traefik/certs/gw.local.crt" + keyFile: "/etc/traefik/certs/gw.key" stores: default: defaultCertificate: - certFile: /etc/traefik/certs/gateway.pem - keyFile: /etc/traefik/certs/gateway.key + certFile: "/etc/traefik/certs/gw.internal.crt" + keyFile: "/etc/traefik/certs/gw.key" diff --git a/api-gateway/etc/traefik.yml b/api-gateway/etc/traefik.yml index b4ab85b..de7f602 100644 --- a/api-gateway/etc/traefik.yml +++ b/api-gateway/etc/traefik.yml @@ -1,6 +1,6 @@ ################################################################ global: - checkNewVersion: false + checkNewVersion: true sendAnonymousUsage: false ################################################################ @@ -20,25 +20,28 @@ entryPoints: metrics: address: :8084 -certificatesResolvers: - tls: - acme: - email: keedosn+egommerce@gmail.com - storage: acme.json - httpChallenge: - # used during the challenge - entryPoint: https +# certificatesResolvers: +# tls: +# acme: +# email: keedosn+egommerce@gmail.com +# storage: acme.json +# httpChallenge: +# # used during the challenge +# entryPoint: https ################################################################ -# serversTransport: - # insecureSkipVerify: true - # rootCAs: - # - /etc/traefik/certs/client.cert +serversTransport: + insecureSkipVerify: true # dev only... + rootCAs: + - /etc/traefik/certs/gw.internal.crt ################################################################ +ping: + entryPoint: "traefik" + api: - insecure: true - # dashboard: true + insecure: true # dev only + dashboard: true # dev only ################################################################ providers: @@ -46,22 +49,24 @@ providers: filename: /etc/traefik/tls.yml docker: exposedByDefault: false + # refreshInterval: 1s # Default host rule. # Optional # Default: "Host(`{{ normalize .Name }}`)" # defaultRule: Host(`{{ normalize .Name }}.docker.localhost`) ################################################################ consulCatalog: + refreshInterval: 1s exposedByDefault: false - refreshInterval: 5s - # ^^ configure in stack`s yml api-registry `command:` section: --providers.consulcatalog.refreshInterval=10s + # connectAware: true + # connectByDefault: true + # serviceName: traefik endpoint: - address: api-registry:8500 - # ^^ FIXME: Use ENV var + scheme: http ################################################################ -# log: -# level: DEBUG +log: + level: DEBUG ################################################################ accessLog: {} @@ -78,6 +83,8 @@ metrics: experimental: localPlugins: requestid: - moduleName: "git.pbiernat.dev/traefik/plugin-requestid" + moduleName: "git.pbiernat.io/traefik/plugin-requestid" auth: - moduleName: "git.pbiernat.dev/traefik/plugin-egommerce-auth" + moduleName: "git.pbiernat.io/traefik/plugin-egommerce-auth" + retryif: + moduleName: "github.com/fusionmedialimited/retryif" diff --git a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/go.mod b/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/go.mod deleted file mode 100644 index d74eef9..0000000 --- a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module git.pbiernat.dev/traefik/plugin-requestid - -go 1.18 diff --git a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/.traefik.yml b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/.traefik.yml similarity index 62% rename from api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/.traefik.yml rename to api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/.traefik.yml index 02fd233..0443dce 100644 --- a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/.traefik.yml +++ b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/.traefik.yml @@ -1,6 +1,6 @@ displayName: Add X-Request-ID Header type: middleware -import: git.pbiernat.dev/traefik/plugin-requestid +import: git.pbiernat.io/traefik/plugin-requestid summary: 'Add a X-Request-ID header for tracing' testData: {} \ No newline at end of file diff --git a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/README.md b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/README.md similarity index 100% rename from api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/README.md rename to api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/README.md diff --git a/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/go.mod b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/go.mod new file mode 100644 index 0000000..9a962f3 --- /dev/null +++ b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/go.mod @@ -0,0 +1,3 @@ +module git.pbiernat.io/traefik/plugin-requestid + +go 1.18 diff --git a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/requestid.go b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/requestid.go similarity index 100% rename from api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/requestid.go rename to api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/requestid.go diff --git a/api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/uuid.go b/api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/uuid.go similarity index 100% rename from api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-requestid/uuid.go rename to api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-requestid/uuid.go diff --git a/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/.traefik.yml b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/.traefik.yml new file mode 100644 index 0000000..9f4e19e --- /dev/null +++ b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/.traefik.yml @@ -0,0 +1,11 @@ +displayName: Traefik Retry If +type: middleware + +import: github.com/fusionmedialimited/retryif + +summary: Retry requests based on status codes + +testData: + attempts: 3 + statusCodes: + - 503 \ No newline at end of file diff --git a/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/Makefile b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/Makefile new file mode 100644 index 0000000..04f6f05 --- /dev/null +++ b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/Makefile @@ -0,0 +1,20 @@ +.PHONY: lint test vendor clean + +export GO111MODULE=on + +default: lint test + +lint: + golangci-lint run + +test: + go test -v -cover ./... + +yaegi_test: + yaegi test -v . + +vendor: + go mod vendor + +clean: + rm -rf ./vendor \ No newline at end of file diff --git a/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/go.mod b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/go.mod new file mode 100644 index 0000000..4657808 --- /dev/null +++ b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/go.mod @@ -0,0 +1,3 @@ +module github.com/fusionmedialimited/retryif + +go 1.18 \ No newline at end of file diff --git a/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/retryif.go b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/retryif.go new file mode 100644 index 0000000..f89f259 --- /dev/null +++ b/api-gateway/plugins/src/github.com/fusionmedialimited/retryif/retryif.go @@ -0,0 +1,179 @@ +package retryif + +import ( + "bufio" + "context" + "fmt" + "io" + "net" + "net/http" + "strconv" +) + +const typeName = "RetryIf" + +// retry is a middleware that retries requests. +type retry struct { + config *RetryConfig + next http.Handler + name string +} + +type RetryConfig struct { + // Attempts defines how many times the request should be retried. + Attempts int `json:"attempts,omitempty" toml:"attempts,omitempty" yaml:"attempts,omitempty" export:"true"` + + // StatusCodes defines the status codes which will trigger a retry if received + StatusCodes []int `json:"statusCodes,omitempty" toml:"statusCodes,omitempty" yaml:"statusCodes,omitempty" export:"true"` +} + +func CreateConfig() *RetryConfig { + return &RetryConfig{ + Attempts: 2, + StatusCodes: []int{503}, + } +} + +// New returns a new retry middleware. +func New(ctx context.Context, next http.Handler, config *RetryConfig, name string) (http.Handler, error) { + if config.Attempts <= 0 { + return nil, fmt.Errorf("incorrect (or empty) value for attempt (%d)", config.Attempts) + } + + return &retry{ + config: config, + next: next, + name: name, + }, nil +} + +func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if r.config.Attempts == 1 { + r.next.ServeHTTP(rw, req) + return + } + + closableBody := req.Body + if closableBody != nil { + defer closableBody.Close() + } + + // if we might make multiple attempts, swap the body for an io.NopCloser + // cf https://github.com/traefik/traefik/issues/1008 + req.Body = io.NopCloser(closableBody) + + attempts := 1 + + operation := func() error { + shouldRetry := attempts < r.config.Attempts + retryResponseWriter := newResponseWriter(rw, shouldRetry, r.config.StatusCodes, attempts-1) + + r.next.ServeHTTP(retryResponseWriter, req) + + if !retryResponseWriter.ShouldRetry() { + return nil + } + + attempts++ + return fmt.Errorf("attempt %d failed", attempts-1) + } + + var err error + for true { + err = operation() + if err == nil { + break + } + } +} + +func newResponseWriter(rw http.ResponseWriter, shouldRetry bool, codes []int, attempt int) *responseWriter { + return &responseWriter{ + responseWriter: rw, + headers: make(http.Header), + shouldRetry: shouldRetry, + statusCodes: codes, + attempt: attempt, + } +} + +type responseWriter struct { + attempt int + statusCodes []int + responseWriter http.ResponseWriter + headers http.Header + shouldRetry bool + written bool +} + +func (r *responseWriter) ShouldRetry() bool { + return r.shouldRetry +} + +func (r *responseWriter) DisableRetries() { + r.shouldRetry = false +} + +func (r *responseWriter) Header() http.Header { + if r.written { + return r.responseWriter.Header() + } + return r.headers +} + +func (r *responseWriter) Write(buf []byte) (int, error) { + if r.ShouldRetry() { + return len(buf), nil + } + return r.responseWriter.Write(buf) +} + +func (r *responseWriter) WriteHeader(code int) { + if r.ShouldRetry() && !r.ShouldRetryOnCode(code) { + r.DisableRetries() + } + + if r.ShouldRetry() { + return + } + + // In that case retry case is set to false which means we at least managed + // to write headers to the backend : we are not going to perform any further retry. + // So it is now safe to alter current response headers with headers collected during + // the latest try before writing headers to client. + headers := r.responseWriter.Header() + headers.Set("X-RetryIf-Retries", strconv.Itoa(r.attempt)) + for header, value := range r.headers { + headers[header] = value + } + + r.responseWriter.WriteHeader(code) + r.written = true +} + +func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hijacker, ok := r.responseWriter.(http.Hijacker) + if !ok { + return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter) + } + return hijacker.Hijack() +} + +func (r *responseWriter) Flush() { + if flusher, ok := r.responseWriter.(http.Flusher); ok { + flusher.Flush() + } +} + +func (r *responseWriter) ShouldRetryOnCode(stCode int) bool { + for _, code := range r.statusCodes { + if code == stCode { + return true + } + } + return false +} + +func (r *responseWriter) CloseNotify() <-chan bool { + return r.responseWriter.(http.CloseNotifier).CloseNotify() +} diff --git a/deploy/image-build.sh b/deploy/image-build.sh index 5a1a89e..8d50871 100755 --- a/deploy/image-build.sh +++ b/deploy/image-build.sh @@ -1,7 +1,7 @@ #!/bin/sh # RUN IN REPO ROOT DIR !! -export IMAGE_NAME="git.pbiernat.dev/egommerce/api-gateway" +export IMAGE_NAME="git.pbiernat.io/egommerce/api-gateway" export BUILD_TIME=$(date +"%Y%m%d%H%M%S") TARGET=${1:-latest} diff --git a/deploy/image-push.sh b/deploy/image-push.sh index c6657c8..c2596d2 100755 --- a/deploy/image-push.sh +++ b/deploy/image-push.sh @@ -1,9 +1,11 @@ #!/bin/sh # RUN IN REPO ROOT DIR !! -export IMAGE_NAME="git.pbiernat.dev/egommerce/api-gateway" +export IMAGE_NAME="git.pbiernat.io/egommerce/api-gateway" TARGET=${1:-latest} -echo $DOCKER_PASSWORD | docker login git.pbiernat.dev -u $DOCKER_USERNAME --password-stdin +echo $DOCKER_PASSWORD | docker login git.pbiernat.io -u $DOCKER_USERNAME --password-stdin docker push "$IMAGE_NAME:$TARGET" + +curl http://127.0.0.1:9001/api/webhooks/da6eb8e6-bf58-46ff-8546-1bf925cb7ea0