This commit is contained in:
Piotr Biernat 2024-07-20 12:59:01 +02:00
parent 185316b9ec
commit 05753d63b3
19 changed files with 288 additions and 44 deletions

13
.app.config Normal file
View File

@ -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"
}
}

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
.vscode
api-gateway/plugins/src/git.pbiernat.dev/traefik/plugin-egommerce-auth
api-gateway/plugins/src/git.pbiernat.io/traefik/plugin-egommerce-auth

6
.gitmodules vendored
View File

@ -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

View File

@ -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

View File

@ -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 "$@"

View File

@ -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"

View File

@ -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"

View File

@ -1,3 +0,0 @@
module git.pbiernat.dev/traefik/plugin-requestid
go 1.18

View File

@ -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: {}

View File

@ -0,0 +1,3 @@
module git.pbiernat.io/traefik/plugin-requestid
go 1.18

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
module github.com/fusionmedialimited/retryif
go 1.18

View File

@ -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()
}

View File

@ -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}

View File

@ -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