From 0e3125d49b1ba3863fed600d630bc7445b3e860f Mon Sep 17 00:00:00 2001 From: Charly Molter Date: Fri, 11 Oct 2024 18:26:16 +0200 Subject: [PATCH] start rewrite app in go Signed-off-by: Charly Molter --- .github/workflows/ci.yaml | 20 + .gitignore | 2 + .golangci.yaml | 3 + .goreleaser.yaml | 48 ++ .mise.toml | 5 + .tools_versions.yaml | 7 + Dockerfile | 16 +- ERRORS.md | 11 + Makefile | 71 +++ README.md | 8 +- app/Dockerfile | 4 + app/internal/api/gen.go | 374 +++++++++++++++ app/internal/base/handler.go | 145 ++++++ app/main.go | 271 +++++++++++ app/package-lock.json | 574 ----------------------- app/package.json | 30 -- app/public/embed.go | 6 + app/public/index.html | 865 +++++++++++++++++------------------ app/server.js | 142 ------ go.mod | 57 +++ go.sum | 124 +++++ openapi-config.yaml | 6 + openapi.yaml | 171 +++++++ yarn.lock | 4 - 24 files changed, 1750 insertions(+), 1214 deletions(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 .golangci.yaml create mode 100644 .goreleaser.yaml create mode 100644 .mise.toml create mode 100644 .tools_versions.yaml create mode 100644 ERRORS.md create mode 100644 Makefile create mode 100644 app/Dockerfile create mode 100644 app/internal/api/gen.go create mode 100644 app/internal/base/handler.go create mode 100644 app/main.go delete mode 100644 app/package-lock.json delete mode 100644 app/package.json create mode 100644 app/public/embed.go delete mode 100644 app/server.js create mode 100644 go.mod create mode 100644 go.sum create mode 100644 openapi-config.yaml create mode 100644 openapi.yaml delete mode 100644 yarn.lock diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..983e8ad --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,20 @@ +on: # yamllint disable-line rule:truthy + pull_request: + branches: + - main + push: + branches: + - main + - next +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - run: curl https://mise.run | sh + - run: make check + - run: make build + - run: make test diff --git a/.gitignore b/.gitignore index eb79dd5..979a5aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules .idea +bin/ +dist/ diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..e528ec2 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,3 @@ +--- +issues: + exclude-rules: diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..9b98105 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,48 @@ +--- +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +before: + hooks: + - make generate + - make check + +builds: + - env: + - CGO_ENABLED=0 + main: ./app + id: kuma-counter-demo + goos: + - linux + - darwin + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +dockers: + - id: demo-app + image_templates: + - kumahq/kuma-counter-demo + dockerfile: ./app/Dockerfile diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..a7ba069 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,5 @@ +[env] +MISE_DATA_DIR='bin/' + +[settings] +experimental = true # To enable installation of Go tools from source with mise. diff --git a/.tools_versions.yaml b/.tools_versions.yaml new file mode 100644 index 0000000..b6103dd --- /dev/null +++ b/.tools_versions.yaml @@ -0,0 +1,7 @@ +--- +# renovate: datasource=github-releases depName=golangci/golangci-lint +golangci-lint: 1.61.0 +# removate: datasource=github-releases depName=github.com/goreleaser/goreleaser/v2 +goreleaser: 2.3.2 +# removate: datasource=github-releases denName=com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen +oapi-codegen: 2.4.1 diff --git a/Dockerfile b/Dockerfile index c167980..515623f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,8 @@ ARG ARCH=amd64 -FROM --platform=linux/${ARCH} node:alpine +FROM golang:1.19 -RUN apk add dumb-init +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} go build ./... -o /kuma-counter-demo -COPY /app/package.json /app/package.json -RUN npm install --prefix /app +FROM --platform=linux/${ARCH} distroless -COPY /app/public /app/public -COPY /app/server.js /app/server.js - -EXPOSE 5000 - -WORKDIR "/app" -ENTRYPOINT ["dumb-init", "--"] -CMD ["node", "./server.js"] +CMD ["/kuma-counter-demo"] diff --git a/ERRORS.md b/ERRORS.md new file mode 100644 index 0000000..6eff611 --- /dev/null +++ b/ERRORS.md @@ -0,0 +1,11 @@ +# Errors + +The list of error details that can be returned by our app. + +## REDIS-FAILURE + +Redis failed in some way + +## DEMOAPP-FAILURE + +Demo-app failed diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2c8609a --- /dev/null +++ b/Makefile @@ -0,0 +1,71 @@ +# Some sensible Make defaults: https://tech.davis-hansson.com/p/make/ +SHELL := bash +.SHELLFLAGS := -eu -o pipefail -c + +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +TOOLS_VERSIONS_FILE = $(PROJECT_DIR)/.tools_versions.yaml + +export MISE_DATA_DIR = $(PROJECT_DIR)/bin/ +MISE := $(shell which mise) +.PHONY: mise +mise: + @mise -V >/dev/null || (echo "mise - https://github.com/jdx/mise - not found. Please install it." && exit 1) + +GOLANGCI_LINT_VERSION = $(shell yq -ojson -r '.golangci-lint' < $(TOOLS_VERSIONS_FILE)) +GOLANGCI_LINT = $(PROJECT_DIR)/bin/installs/golangci-lint/$(GOLANGCI_LINT_VERSION)/bin/golangci-lint +.PHONY: golangci-lint.download +golangci-lint.download: | mise ## Download golangci-lint locally if necessary. + $(MISE) install -y -q golangci-lint@$(GOLANGCI_LINT_VERSION) + +GORELEASER_VERSION = $(shell yq -ojson -r '.goreleaser' < $(TOOLS_VERSIONS_FILE)) +GORELEASER = $(PROJECT_DIR)/bin/installs/goreleaser/$(GORELEASER_VERSION)/bin/goreleaser +.PHONY: goreleaser.download +goreleaser.download: | mise ## Download goreleaser locally if necessary. + $(MISE) install -y -q goreleaser@$(GORELEASER_VERSION) + +OAPI_CODEGEN_VERSION = $(shell yq -ojson -r '.oapi-codegen' < $(TOOLS_VERSIONS_FILE)) +OAPI_CODEGEN = $(PROJECT_DIR)/bin/installs/oapi-codegen/$(OAPI_CODEGEN_VERSION)/bin/oapi-codegen +.PHONY: oapi-codegen.download +oapi-codegen.download: | mise ## Download oapi-codegen locally if necessary. + $(MISE) install -y -q oapi-codegen@$(OAPI_CODEGEN_VERSION) + +app/internal/api/gen.go: openapi.yaml | oapi-codegen.download + $(OAPI_CODEGEN) --config openapi-config.yaml $< + +.PHONY: clean +clean: + @rm -rf app/internal/api/gen.go + @rm -rf dist/ + @rm -rf bin/ + +.PHONY: all +all: check build test run + +.PHONY: check +check: tidy fmt lint + +.PHONY: fmt +fmt: + go fmt ./... + +.PHONY: lint +lint: tidy golangci-lint + +.PHONY: tidy +tidy: + go mod tidy + +.PHONY: golangci-lint +golangci-lint: | golangci-lint.download + $(GOLANGCI_LINT) run --verbose --config $(PWD)/.golangci.yaml $(GOLANGCI_LINT_FLAGS) + +build: | goreleaser.download + $(GORELEASER) release --snapshot --clean + +.PHONY: generate +generate: app/internal/api/gen.go + go generate ./... + +.PHONY: test +test: + go test $$(go list ./... | grep -v 'e2e') diff --git a/README.md b/README.md index ac06b50..d8780c0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Kuma Counter Demo +[![Run in Insomnia}](https://insomnia.rest/images/run.svg)](https://insomnia.rest/run/?label=Kuma-count-demo&uri=https%3A%2F%2Fgithub.com%2Fkumahq%2Fkuma-counter-demo%2Fblob%2Fmaster%2Fopenapi.yaml) + [![][kuma-logo]][kuma-url] [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/kumahq/kuma/blob/master/LICENSE) @@ -201,4 +203,8 @@ To enforce response status code you need to set header `x-set-response-status-co ```shell curl localhost:5001/increment -XPOST -H "x-set-response-status-code: 503" -``` \ No newline at end of file +``` + +### ErrorCodes + +#### Error Redis failure diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..a50e62d --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,4 @@ +FROM scratch + +ENTRYPOINT ["/kuma-counter-demo"] +ADD kuma-counter-demo / diff --git a/app/internal/api/gen.go b/app/internal/api/gen.go new file mode 100644 index 0000000..fd46c5f --- /dev/null +++ b/app/internal/api/gen.go @@ -0,0 +1,374 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gorilla/mux" +) + +// Counter defines model for Counter. +type Counter struct { + // Counter The incremented counter value + Counter int `json:"counter"` + + // Zone Zone data from Redis + Zone string `json:"zone"` +} + +// DeleteCounterResponse defines model for DeleteCounterResponse. +type DeleteCounterResponse = Counter + +// Error standard error +type Error struct { + // Detail Details about the error. + Detail *string `json:"detail,omitempty"` + + // Instance The portal traceback code + Instance string `json:"instance"` + + // InvalidParameters TODO + InvalidParameters *[]InvalidParameters `json:"invalid_parameters,omitempty"` + + // Status The HTTP status code. + Status int `json:"status"` + + // Title The error response code. + Title string `json:"title"` + + // Type The error type. + Type *string `json:"type,omitempty"` +} + +// GetCounterResponse defines model for GetCounterResponse. +type GetCounterResponse = Counter + +// InvalidParameters defines model for InvalidParameters. +type InvalidParameters struct { + Choices *[]string `json:"choices,omitempty"` + Field *string `json:"field,omitempty"` + Reason *string `json:"reason,omitempty"` + Rule *string `json:"rule,omitempty"` +} + +// PostCounterResponse defines model for PostCounterResponse. +type PostCounterResponse = Counter + +// VersionResponse defines model for VersionResponse. +type VersionResponse struct { + // Color Application color in HEX format + Color string `json:"color"` + + // Version Application version + Version string `json:"version"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Reset the counter + // (DELETE /counter) + DeleteCounter(w http.ResponseWriter, r *http.Request) + // Get the current counter value + // (GET /counter) + GetCounter(w http.ResponseWriter, r *http.Request) + // Increment the counter + // (POST /counter) + PostCounter(w http.ResponseWriter, r *http.Request) + // Get the application version and color + // (GET /version) + GetVersion(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// DeleteCounter operation middleware +func (siw *ServerInterfaceWrapper) DeleteCounter(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteCounter(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetCounter operation middleware +func (siw *ServerInterfaceWrapper) GetCounter(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetCounter(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// PostCounter operation middleware +func (siw *ServerInterfaceWrapper) PostCounter(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PostCounter(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetVersion operation middleware +func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetVersion(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/counter", wrapper.DeleteCounter).Methods("DELETE") + + r.HandleFunc(options.BaseURL+"/counter", wrapper.GetCounter).Methods("GET") + + r.HandleFunc(options.BaseURL+"/counter", wrapper.PostCounter).Methods("POST") + + r.HandleFunc(options.BaseURL+"/version", wrapper.GetVersion).Methods("GET") + + return r +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xW32/bNhD+Vwhuj4olO81a6C1rstbAsBppUAwrguEsnW02EqmRJ7dpof99IKkftsU4", + "GbAAecgbJR7Ju+/7jh9/8EyVlZIoyfD0BzfZBktww7eqloTaDiutKtQk0E1kw0SOJtOiIqEkT/n1BpmQ", + "mcYSJWHO2kC2haJGHnG6q5CnXEjCNWreRPy7kjje5y8lkeVAwFZalewKc2GG5Ya0kGveNBHX+E8tNOY8", + "/dxn1W5608er5RfMiEf82wl+g7IqfBWXfjzdq2h22uXEa3PyFQ3xxh50gQUStohcoamUNPiCTBPxS61V", + "oGBDIHPQOUM3Hx0AlSOBKMbLLtx/w2CpamK0Qb9+wiPeZtiCaetDY6EkhJJ9BcOkIrZStczHgERcSJtR", + "hmFqKqUJCkYaMlxCdssylePemT4idRHpbPbm9ezN2Sx5/cvp2XR2mkyn4TO3UIj87wo0lEioTeD0Dxcf", + "eMQFYelmf9a44in/KR7aMm57Mp77/RbDdk1/KmgNd/bbEFBtwmW+v75eMB/gKtyD9VXyKqRDElTcg5qj", + "hum2G8Y78j8Usd/uY8T/uH9fO//o7Q4U34LQZb9D/01fUavcRzYDm+7K9kCCucpq29gjGQ6qe4yAOuo8", + "E22WgaI3RJVJ4/hWyTVUYiLkSsW3dQknbbee5FiqWCo68Ym4Rn2H9HJ/je+vcVONUdkokflh36f3yHno", + "w5XAIg9GagRjwQhN1b7Xxnh0cmjTZTv5HsLRRHyhzAvZAbI/oTZCyWOYFCE3O6+qQmRgv5gLYUKy95d/", + "spXSJVDoetv6o47v1QU9BMoQ5xMcoWIXGMxqLejuo7ULX86vCBr1eU2b/m1nFy3d7/0LxQNkr5KQIIRh", + "54s5q7TaihwNs5i5EoyFgJUgYS3kmkEvFpA500ha4NZOfO+0MOGDmN/uxDqxnC/mfAc6Pp0kk8SiqSqU", + "UAme8tNJMjm17wmgjasx3tOxfaaFnhX2v3EPii7BQZO7uaLZT7UvdJ73+7ztJdp5n0tkliReQpJQkh3C", + "QHX8pe15b+YPWX34veko2q/sY51laMyqLqwRIzG12q3SYnf2P+blPTOQxzlbQs5WIIpao5djXZag73jK", + "r1xiB1mtkcY8XfUkuOhaa+uq7t45KMxxdoSpwe2ekqaApz7EkSsQisNy/N36HNh613HVoj/KsFImQN28", + "MwzPnW+swGXQcnuEuh3zekruQh55nLzeE59jk/X472fWRDzesaJHdB2M/cnx533PIJGQaxPsuE+9TT0Z", + "a4cO/h/abVyMdTvr33bdc2q9oxT4TQ3qrXusfj5k83eVQcFy3GKhKicIH8sjXuuidfs0jgsbt1GG0rMk", + "SWLrrs1N828AAAD//8AWYmcQEgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/app/internal/base/handler.go b/app/internal/base/handler.go new file mode 100644 index 0000000..0f78cc6 --- /dev/null +++ b/app/internal/base/handler.go @@ -0,0 +1,145 @@ +package base + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/kumahq/kuma-counter-demo/app/internal/api" + "github.com/redis/go-redis/v9" + "go.opentelemetry.io/otel/trace" + "log/slog" + "net/http" + "strconv" +) + +const ( + COUNTER_KEY = "counter" + ZONE_KEY = "zone" +) + +type ServerImpl struct { + redisClient *redis.Client + color string + version string +} + +var ( + DemoAppErrorType = "https://github.com/kumahq/kuma-counter-demo/blob/master/ERRORS.md#DEMOAPP-FAILURE" +) + +func redisError(ctx context.Context, statusCode int, title string) api.Error { + redisErrorType := "https://github.com/kumahq/kuma-counter-demo/blob/master/ERRORS.md#REDIS-FAILURE" + span := trace.SpanFromContext(ctx) + return api.Error{Type: &redisErrorType, Status: statusCode, Instance: span.SpanContext().TraceID().String(), Title: title} +} + +func NewServerImpl(client *redis.Client, version string, color string) api.ServerInterface { + return &ServerImpl{ + redisClient: client, + version: version, + color: color, + } + +} + +func (s *ServerImpl) DeleteCounter(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if err := s.redisClient.Del(ctx, COUNTER_KEY).Err(); err != nil { + writeResponse(w, r, http.StatusInternalServerError, redisError(ctx, http.StatusInternalServerError, "failed to delete counter"), err) + return + } + + zone, err := s.redisClient.Get(ctx, ZONE_KEY).Result() + if errors.Is(err, redis.Nil) { + zone = "" + } else if err != nil { + writeResponse(w, r, http.StatusInternalServerError, redisError(ctx, http.StatusInternalServerError, "failed to retrieve zone"), err) + return + } + + response := api.DeleteCounterResponse{ + Counter: 0, + Zone: zone, + } + + writeResponse(w, r, http.StatusOK, response, nil) +} + +func (s *ServerImpl) GetCounter(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + counter, err := s.redisClient.Get(ctx, COUNTER_KEY).Result() + if errors.Is(err, redis.Nil) { + counter = "0" + } else if err != nil { + writeResponse(w, r, http.StatusInternalServerError, redisError(ctx, http.StatusInternalServerError, "failed to retrieve counter"), err) + return + } + + zone, err := s.redisClient.Get(ctx, ZONE_KEY).Result() + if errors.Is(err, redis.Nil) { + zone = "" + } else if err != nil { + writeResponse(w, r, http.StatusInternalServerError, redisError(ctx, http.StatusInternalServerError, "failed to retrieve zone"), err) + return + } + c, _ := strconv.Atoi(counter) + + response := api.GetCounterResponse{ + Counter: c, + Zone: zone, + } + + writeResponse(w, r, http.StatusOK, response, nil) +} + +func (s *ServerImpl) PostCounter(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + counter, err := s.redisClient.Incr(ctx, COUNTER_KEY).Result() + if err != nil { + writeResponse(w, r, http.StatusInternalServerError, redisError(ctx, http.StatusInternalServerError, "failed to increment counter"), err) + return + } + + zone, err := s.redisClient.Get(ctx, ZONE_KEY).Result() + if errors.Is(redis.Nil, err) { + zone = "" + } else if err != nil { + writeResponse(w, r, http.StatusInternalServerError, redisError(ctx, http.StatusInternalServerError, "failed to retrieve zone"), err) + return + } + + response := api.PostCounterResponse{ + Counter: int(counter), + Zone: zone, + } + + writeResponse(w, r, http.StatusOK, response, nil) +} + +func (s *ServerImpl) GetVersion(w http.ResponseWriter, r *http.Request) { + writeResponse(w, r, http.StatusOK, api.VersionResponse{ + Version: s.version, + Color: s.color, + }, nil) +} + +func writeResponse(w http.ResponseWriter, r *http.Request, originalStatusCode int, response interface{}, err error) { + statusCode := originalStatusCode + if originalStatusCode/100 < 5 { // If the original status is 5xx ignore our override + statusStr := r.Header.Get("x-set-response-status-code") + if st, _ := strconv.Atoi(statusStr); st != 0 { + originalStatusCode = st + } + } + w.WriteHeader(originalStatusCode) + if err != nil { + slog.ErrorContext(r.Context(), "failed request with error", "err", err, "status", statusCode, "response", response) + } else { + slog.DebugContext(r.Context(), "successful request", "statusCode", statusCode, "originalStatusCode", originalStatusCode, "response", response) + } + err = json.NewEncoder(w).Encode(response) + if err != nil { + fmt.Printf("failed to write response: %v\n", err) + } +} diff --git a/app/main.go b/app/main.go new file mode 100644 index 0000000..f1c2ced --- /dev/null +++ b/app/main.go @@ -0,0 +1,271 @@ +package main + +import ( + "context" + "errors" + "fmt" + "github.com/getkin/kin-openapi/openapi3" + "github.com/kumahq/kuma-counter-demo/app/internal/api" + "github.com/kumahq/kuma-counter-demo/app/internal/base" + "github.com/kumahq/kuma-counter-demo/app/public" + middleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/trace" + "log/slog" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/redis/go-redis/v9" +) + +var ( + version = "1.0" + color = "#efefef" + redisHost = "127.0.0.1" + redisPort = 6379 + appAddress = "" + appPort = "5050" + redisClient *redis.Client +) + +// setupOTelSDK bootstraps the OpenTelemetry pipeline. +// If it does not return an error, make sure to call shutdown for proper cleanup. +func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { + var shutdownFuncs []func(context.Context) error + + // shutdown calls cleanup functions registered via shutdownFuncs. + // The errors from the calls are joined. + // Each registered cleanup will be invoked once. + shutdown = func(ctx context.Context) error { + var err error + for _, fn := range shutdownFuncs { + err = errors.Join(err, fn(ctx)) + } + shutdownFuncs = nil + return err + } + + // handleErr calls shutdown for cleanup and makes sure that all errors are returned. + handleErr := func(inErr error) { + err = errors.Join(inErr, shutdown(ctx)) + } + + // Set up propagator. + prop := newPropagator() + otel.SetTextMapPropagator(prop) + + // Set up trace provider. + tracerProvider, err := newTraceProvider(ctx) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + otel.SetTracerProvider(tracerProvider) + + // Set up meter provider. + meterProvider, err := newMeterProvider(ctx) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) + otel.SetMeterProvider(meterProvider) + + // Set up logger provider. + //loggerProvider, err := newLoggerProvider(ctx) + //if err != nil { + // handleErr(err) + // return + //} + //slog.SetDefault(otelslog.NewLogger("base")) + //shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) + //global.SetLoggerProvider(loggerProvider) + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) + + return +} + +func newPropagator() propagation.TextMapPropagator { + return propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) +} + +func newTraceProvider(ctx context.Context) (*trace.TracerProvider, error) { + traceClient := otlptracehttp.NewClient(otlptracehttp.WithInsecure(), otlptracehttp.WithCompression(otlptracehttp.NoCompression)) + + traceExporter, err := otlptrace.New(ctx, traceClient) + if err != nil { + return nil, err + } + + traceProvider := trace.NewTracerProvider( + trace.WithBatcher(traceExporter), + ) + return traceProvider, nil +} + +func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) { + prometheusExporter, err := prometheus.New() + if err != nil { + return nil, err + } + otlpExporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithInsecure(), otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression)) + if err != nil { + return nil, err + } + + meterProvider := metric.NewMeterProvider( + metric.WithReader(prometheusExporter), + metric.WithReader(metric.NewPeriodicReader(otlpExporter)), + ) + return meterProvider, nil +} + +// nolint:unused +func newLoggerProvider(ctx context.Context) (*log.LoggerProvider, error) { // + otlpExporter, err := otlploghttp.New(ctx, otlploghttp.WithInsecure(), otlploghttp.WithCompression(otlploghttp.NoCompression)) + if err != nil { + return nil, err + } + + loggerProvider := log.NewLoggerProvider( + log.WithProcessor(log.NewBatchProcessor(otlpExporter)), + ) + return loggerProvider, nil +} + +// Middleware to introduce delay based on the header value +func delayMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + delayStr := r.Header.Get("x-set-response-delay-ms") + delay, _ := strconv.Atoi(delayStr) + if delay > 0 { + slog.DebugContext(r.Context(), "simulating slow response", "time", delay) + time.Sleep(time.Duration(delay) * time.Millisecond) + } + next.ServeHTTP(w, r) + }) +} +func main() { + if v := os.Getenv("APP_VERSION"); v != "" { + version = v + } + if c := os.Getenv("APP_COLOR"); c != "" { + color = c + } + if c := os.Getenv("ADDRESS"); c != "" { + appAddress = c + } + if h := os.Getenv("REDIS_HOST"); h != "" { + redisHost = h + } + if p, err := strconv.Atoi(os.Getenv("REDIS_PORT")); err == nil { + redisPort = p + } + if p := os.Getenv("PORT"); p != "" { + appPort = p + } + + if err := run(); err != nil { + slog.Error("server failed", "error", err) + os.Exit(1) + } +} + +func run() (err error) { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + // Set up OpenTelemetry. + otelShutdown, err := setupOTelSDK(ctx) + if err != nil { + return + } + defer func() { + err = errors.Join(err, otelShutdown(context.Background())) + }() + slog.Info("Connecting to Redis", "host", redisHost, "port", redisPort) + + redisClient = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", redisHost, redisPort), + DialTimeout: 5 * time.Second, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + PoolTimeout: 5 * time.Second, + }) + defer func() { + err = errors.Join(err, redisClient.Close()) + }() + r := mux.NewRouter() + + swagger, err := api.GetSwagger() + if err != nil { + return err + } + + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = []*openapi3.Server{ + { + URL: "/api", + }, + } + + // Create an instance of our handler which satisfies the generated interface + apiHandler := base.NewServerImpl(redisClient, version, color) + + apiRouter := r.PathPrefix("/api").Subrouter() + apiRouter.Use(otelmux.Middleware("api-server")) + // Serve static files from the "public" directory + // Use our validation middleware to check all requests against the OpenAPI schema. + apiRouter.Use(middleware.OapiRequestValidatorWithOptions(swagger, &middleware.Options{SilenceServersWarning: true})) + apiRouter.Use(mux.CORSMethodMiddleware(r)) + apiRouter.Use(delayMiddleware) + api.HandlerFromMux(apiHandler, apiRouter) + r.PathPrefix("/metrics").Handler(promhttp.Handler()) + r.PathPrefix("/").Methods("GET").Handler(http.FileServer(http.FS(public.Files))) + + // Apply middlewares + + addr := net.JoinHostPort(appAddress, appPort) + srv := &http.Server{ + Handler: r, + Addr: addr, + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + // Graceful shutdown handling + srvErr := make(chan error, 1) + go func() { + srvErr <- srv.ListenAndServe() + }() + slog.Info("Server running", "addr", addr) + select { + case err = <-srvErr: + return + case <-ctx.Done(): + // Wait for first CTRL+C. + // Stop receiving signal notifications as soon as possible. + stop() + } + err = srv.Shutdown(context.Background()) + return +} diff --git a/app/package-lock.json b/app/package-lock.json deleted file mode 100644 index 447bbee..0000000 --- a/app/package-lock.json +++ /dev/null @@ -1,574 +0,0 @@ -{ - "name": "kuma-demo", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, - "cluster-key-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", - "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - }, - "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "requires": { - "get-intrinsic": "^1.2.4" - } - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "requires": { - "function-bind": "^1.1.2" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ioredis": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.17.3.tgz", - "integrity": "sha512-iRvq4BOYzNFkDnSyhx7cmJNOi1x/HWYe+A4VXHBu4qpwJaGT1Mp+D2bVGJntH9K/Z/GeOM/Nprb8gB3bmitz1Q==", - "requires": { - "cluster-key-slot": "^1.1.0", - "debug": "^4.1.1", - "denque": "^1.1.0", - "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", - "redis-commands": "1.5.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.0.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { - "side-channel": "^1.0.6" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "redis-commands": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", - "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==" - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "requires": { - "redis-errors": "^1.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "requires": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - } - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "requires": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - } - }, - "standard-as-callback": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz", - "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - } - } -} diff --git a/app/package.json b/app/package.json deleted file mode 100644 index b8b288e..0000000 --- a/app/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "kuma-demo", - "version": "0.1.0", - "description": "A simple counter application to demonstrate Kuma", - "author": "Marco Palladino ", - "homepage": "https://kuma.io", - "dependencies": { - "express": "~4.21.0", - "ioredis": "~4.17.3" - }, - "engines": { - "node": ">= 13.12.0" - }, - "devDependencies": {}, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/Kong/kuma-demo" - }, - "scripts": { - "start": "node server.js" - }, - "directories": { - "public": "./public" - }, - "bugs": { - "url": "https://github.com/Kong/kuma-demo/issues", - "email": "marco@konghq.com" - } -} diff --git a/app/public/embed.go b/app/public/embed.go new file mode 100644 index 0000000..f1823fe --- /dev/null +++ b/app/public/embed.go @@ -0,0 +1,6 @@ +package public + +import "embed" + +//go:embed *.html *.svg *.png +var Files embed.FS diff --git a/app/public/index.html b/app/public/index.html index e717a3a..8977d71 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -1,518 +1,481 @@ - Kuma Demo - - - function increment() { - var status = $("#status"); - var zone = $("#zone"); - var counter = $("#counter"); - var redisErrors = $("#redis-error-count"); - var appErrors = $("#app-error-count"); - - if (running) { - return; - } - running = true; - - $.post('/increment', function (data, textStatus, jqXHR) { - if (data.err == null) { - status.removeClass("error"); - status.text("Everything is working") - counter.text(data.counter); - var zoneVal = data.zone; - if (zoneVal == null) { - zoneVal = "unknown"; - } - zone.text(zoneVal); - } else { - status.addClass("error"); - status.text("Cannot connect to: redis"); - redisErrCount++; - redisErrors.text(redisErrCount); - counter.text("-"); - zone.text("-"); + + -
+
Loading... -
-
+
+
-
-

Kuma Counter Demo

-

- Welcome to a simple demo application to demonstrate how the Kuma service mesh works. This application allows us - to increment a counter, and it is made of two different services: -

-
    -
  • demo-app: To serve the backend API and this frontend
  • -
  • redis: To store the counter value
  • -
-

- Kuma Counter Demo App diagram -

-
-
- -
-
- -
-
- -
+
+

Kuma Counter Demo

+

+ Welcome to a simple demo application to demonstrate how the Kuma service mesh works. This application + allows us + to increment a counter, and it is made of two different services: +

+
    +
  • demo-app: To serve the backend API and this frontend
  • +
  • redis: To store the counter value
  • +
+

+ Kuma Counter Demo App diagram +

- -
- - +
+ +
+
+ +
+
+ +
+
+ +
+ + +
+ +
+ + +
+

Counter

+

+ +

+
+ +
+

Zone

+

+ +

+
+
+ redis errors: 0 +
+
+ demo-app errors: 0 +
+ +
+
-
- - -
-

Counter

+
-
-
- - -
+
+ - diff --git a/app/server.js b/app/server.js deleted file mode 100644 index 1648264..0000000 --- a/app/server.js +++ /dev/null @@ -1,142 +0,0 @@ -const express = require('express') -const Redis = require("ioredis"); -const timers = require("timers/promises"); -const util = require("util"); - -const COUNTER_KEY = "counter" -const ZONE_KEY = "zone" -const PORT = 5000; - -const app = express(); - -const version = process.env.APP_VERSION || "1.0"; -const color = process.env.APP_COLOR || "#efefef"; - -function getClient() { - const host = process.env.REDIS_HOST || "127.0.0.1"; - const port = parseInt(process.env.REDIS_PORT) || 6379; - console.log("Connecting to Redis at %s:%d", host, port); - const client = new Redis({ - port: port, - host: host, - family: 4, - autoResendUnfulfilledCommands: false, - autoResubscribe: false, - enableOfflineQueue: true, - maxRetriesPerRequest: null, - reconnectOnError: function (err) { - return false; - }, - retryStrategy: function(times) { - return false; - } - }); - client.on("error", function() { - // Ignore - }); - return client; -} - -const sleep = util.promisify(setTimeout); -const delayMiddleware = function (req, res, next) { - const delay = parseInt(req.header("x-set-response-delay-ms") || 0, 10) - sleep(delay).then(() => { - next() - }) -} - -const statusCodeMiddleware = function (req, res, next) { - const statusCode = parseInt(req.header("x-set-response-status-code") || 200, 10) - res.status(statusCode) - next() -} - -app.use(delayMiddleware) -app.use(statusCodeMiddleware) -app.use('/', express.static('public')); - -app.post('/increment', function(req, res){ - const client = getClient(); - client.incr(COUNTER_KEY, function (err, counter_result) { - if (err) { - console.log(err); - res.send({err:true}); - } else { - client.get(ZONE_KEY, function(err, zone_result) { - client.quit(); - if (err) { - console.log(err); - res.send({err:true}); - } else { - if (counter_result == null) { - counter_result = 0; - } - res.send({counter: counter_result, zone: zone_result, err: err}); - } - }); - } - }); -}); - -app.delete('/counter', function(req, res){ - const client = getClient(); - client.del(COUNTER_KEY, function(err) { - if (err) { - console.log(err); - res.send({err:true}); - } else { - client.get(ZONE_KEY, function(err, zone_result) { - client.quit(); - if (err) { - console.log(err); - res.send({err:true}); - } else { - res.send({counter: 0, zone: zone_result, err: err}); - } - }); - } - }); -}); - -app.get('/counter', function(req, res){ - const client = getClient(); - client.get(COUNTER_KEY, function(err, counter_result) { - if (err) { - console.log(err); - res.send({err:true}); - } else { - client.get(ZONE_KEY, function(err, zone_result) { - client.quit(); - if (err) { - console.log(err); - res.send({err:true}); - } else { - if (counter_result == null) { - counter_result = 0; - } - res.send({counter: counter_result, zone: zone_result, err: err}); - } - }); - } - }); -}); - -app.get('/version', function(req, res) { - res.send({ - version: version, - color: color - }); -}); - -const server = app.listen(PORT, function(){ - console.log("Server running on port %s", PORT); -}); - -const shutdown = async (event) => { - console.log('%s signal received: wait 1s to ensure this endpoint is dropped and shutting down', event) - await timers.setTimeout(1000); - await util.promisify(server.close.bind(server))(); -}; - -process.on('SIGTERM', shutdown) -process.on('SIGINT', shutdown) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fffd5c8 --- /dev/null +++ b/go.mod @@ -0,0 +1,57 @@ +module github.com/kumahq/kuma-counter-demo + +go 1.23.2 + +require ( + github.com/getkin/kin-openapi v0.127.0 + github.com/gorilla/mux v1.8.1 + github.com/oapi-codegen/nethttp-middleware v1.0.2 + github.com/prometheus/client_golang v1.20.3 + github.com/redis/go-redis/v9 v9.6.1 + go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.55.0 + go.opentelemetry.io/otel v1.30.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.6.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 + go.opentelemetry.io/otel/exporters/prometheus v0.52.0 + go.opentelemetry.io/otel/sdk v1.30.0 + go.opentelemetry.io/otel/sdk/log v0.6.0 + go.opentelemetry.io/otel/sdk/metric v1.30.0 + go.opentelemetry.io/otel/trace v1.30.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/invopop/yaml v0.3.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + go.opentelemetry.io/otel/log v0.6.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4fa6fc4 --- /dev/null +++ b/go.sum @@ -0,0 +1,124 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= +github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oapi-codegen/nethttp-middleware v1.0.2 h1:A5tfAcKJhWIbIPnlQH+l/DtfVE1i5TFgPlQAiW+l1vQ= +github.com/oapi-codegen/nethttp-middleware v1.0.2/go.mod h1:DfDalonSO+eRQ3RTb8kYoWZByCCPFRxm9WKq1UbY0E4= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.55.0 h1:lRMfRoJnAaDWadgR58z6xyMIaH5UQ5SzFOhA+LfmUkA= +go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.55.0/go.mod h1:7kJADjE+s91WUhMkzN7qTnDf2aUJZPzeD7RKw/uB5Xg= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.6.0 h1:QSKmLBzbFULSyHzOdO9JsN9lpE4zkrz1byYGmJecdVE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.6.0/go.mod h1:sTQ/NH8Yrirf0sJ5rWqVu+oT82i4zL9FaF6rWcqnptM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 h1:VrMAbeJz4gnVDg2zEzjHG4dEH86j4jO6VYB+NgtGD8s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0/go.mod h1:qqN/uFdpeitTvm+JDqqnjm517pmQRYxTORbETHq5tOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= +go.opentelemetry.io/otel/exporters/prometheus v0.52.0 h1:kmU3H0b9ufFSi8IQCcxack+sWUblKkFbqWYs6YiACGQ= +go.opentelemetry.io/otel/exporters/prometheus v0.52.0/go.mod h1:+wsAp2+JhuGXX7YRkjlkx6hyWY3ogFPfNA4x3nyiAh0= +go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= +go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= +go.opentelemetry.io/otel/sdk/log v0.6.0 h1:4J8BwXY4EeDE9Mowg+CyhWVBhTSLXVXodiXxS/+PGqI= +go.opentelemetry.io/otel/sdk/log v0.6.0/go.mod h1:L1DN8RMAduKkrwRAFDEX3E3TLOq46+XMGSbUfHU/+vE= +go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= +go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= +google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/openapi-config.yaml b/openapi-config.yaml new file mode 100644 index 0000000..9e06690 --- /dev/null +++ b/openapi-config.yaml @@ -0,0 +1,6 @@ +package: api +generate: + gorilla-server: true + models: true + embedded-spec: true +output: app/internal/api/gen.go diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..c62dd9f --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,171 @@ +openapi: 3.0.3 +info: + title: Counter and Zone API + description: This API provides operations for managing a counter and retrieving zone data. + version: 1.0.0 +servers: + - url: http://localhost:5000/api + description: Local development server +paths: + /counter: + post: + operationId: post-counter + summary: Increment the counter + description: Increments the Redis counter and retrieves the zone data. + responses: + '200': + description: Successful increment of the counter + content: + application/json: + schema: + $ref: "#/components/schemas/PostCounterResponse" + '500': + description: A bad failure + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + get: + operationId: get-counter + summary: Get the current counter value + description: Retrieves the current value of the counter and zone data. + responses: + '200': + description: Successful retrieval of the counter value + content: + application/json: + schema: + $ref: "#/components/schemas/GetCounterResponse" + '500': + description: A bad failure + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + operationId: delete-counter + summary: Reset the counter + description: Deletes the counter from Redis and retrieves zone data. + responses: + '200': + description: Successful reset of the counter + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteCounterResponse" + '500': + description: A bad failure + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /version: + get: + operationId: get-version + summary: Get the application version and color + description: Retrieves the application version and color settings. + responses: + '200': + description: Successful retrieval of version and color information + content: + application/json: + schema: + $ref: "#/components/schemas/VersionResponse" + '500': + description: A bad failure + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + +components: + schemas: + InvalidParameters: + type: object + title: Invalid Parameters + properties: + field: + type: string + reason: + type: string + rule: + type: string + choices: + type: array + items: + type: string + Error: + type: object + title: Error + description: standard error + x-examples: + Example 1: + status: 404 + title: Not Found + type: https://kongapi.info/kuma-counter-demo/not-found + instance: 'portal:trace:2287285207635123011' + detail: The requested document was not found + required: + - status + - title + - instance + properties: + status: + type: integer + description: The HTTP status code. + example: 404 + title: + type: string + description: The error response code. + example: Not Found + type: + type: string + description: The error type. + example: Not Found + instance: + type: string + example: 'portal:trace:2287285207635123011' + description: The portal traceback code + detail: + type: string + example: The requested team was not found + description: Details about the error. + invalid_parameters: + type: array + description: TODO + items: + $ref: "#/components/schemas/InvalidParameters" + Counter: + type: object + required: [counter, zone] + properties: + counter: + type: integer + description: The incremented counter value + zone: + type: string + description: Zone data from Redis + x-examples: + Example1: + counter: 23 + zone: us-west + PostCounterResponse: + $ref: "#/components/schemas/Counter" + GetCounterResponse: + $ref: "#/components/schemas/Counter" + DeleteCounterResponse: + $ref: "#/components/schemas/Counter" + VersionResponse: + type: object + required: [version, color] + properties: + version: + type: string + description: Application version + color: + type: string + description: Application color in HEX format + securitySchemes: + BearerAuth: + type: http + scheme: bearer diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - -