diff --git a/.copywrite.hcl b/.copywrite.hcl new file mode 100644 index 0000000..02d990d --- /dev/null +++ b/.copywrite.hcl @@ -0,0 +1,15 @@ +schema_version = 1 + +project { + copyright_holder = "Spectro Cloud" + license = "MPL-2.0" + copyright_year = 2024 + + # (OPTIONAL) A list of globs that should not have copyright/license headers. + # Supports doublestar glob patterns for more flexibility in defining which + # files or folders should be ignored + header_ignore = [ + # "vendors/**", + # "**autogen**", + ] +} \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 51aee88..a7518f3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,7 +16,7 @@ jobs: steps: - id: checkout name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Nodejs uses: actions/setup-node@v3 @@ -86,7 +86,7 @@ jobs: steps: - id: checkout name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Retrieve Credentials id: import-secrets diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e83f24b..bfcfb4b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,9 +2,9 @@ name: Pull Request on: pull_request: - types: ['synchronize', 'opened', 'reopened', 'ready_for_review'] + types: ["synchronize", "opened", "reopened", "ready_for_review"] branches: - - main + - main env: DB_VERSION: 1.0.0 @@ -21,58 +21,117 @@ jobs: shell: bash if: ${{ !github.event.pull_request.draft }} steps: - # If the condition above is not met, aka, the PR is not in draft status, then this step is skipped. - # Because this step is part of the critical path, omission of this step will result in remaining CI steps not gettinge executed. - # As of 8/8/2022 there is now way to enforce this beahvior in GitHub Actions CI. + # If the condition above is not met, aka, the PR is not in draft status, then this step is skipped. + # Because this step is part of the critical path, omission of this step will result in remaining CI steps not gettinge executed. + # As of 8/8/2022 there is now way to enforce this beahvior in GitHub Actions CI. - run: exit 0 Docker: needs: [run-ci] runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 - - uses: actions/setup-go@v3.1.0 - with: - go-version: '1.20' - check-latest: true + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + check-latest: true - - name: Build - run: | - docker system prune -a -f - docker build --no-cache -t api-server:test . + - name: Build + run: | + docker system prune -a -f + docker build --no-cache -t api-server:test . Linting: needs: [run-ci] runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 - - uses: actions/setup-go@v3.1.0 - with: - go-version: '1.20' - check-latest: true + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + check-latest: true - - name: Lint Internal Package - uses: golangci/golangci-lint-action@v3.2.0 - with: - args: --verbose --timeout 5m + - name: Lint Internal Package + uses: golangci/golangci-lint-action@v3.2.0 + with: + args: --verbose --timeout 5m - API-Test: + Tests: runs-on: ubuntu-latest needs: [run-ci] + permissions: + contents: write + pull-requests: write steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Retrieve Credentials + id: import-secrets + uses: hashicorp/vault-action@v2 + with: + url: https://vault.prism.spectrocloud.com + method: approle + roleId: ${{ secrets.VAULT_ROLE_ID }} + secretId: ${{ secrets.VAULT_SECRET_ID }} + secrets: /providers/github/organizations/spectrocloud/token?org_name=spectrocloud token | VAULT_GITHUB_TOKEN + + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + token: ${{ steps.import-secrets.outputs.VAULT_GITHUB_TOKEN }} + + - name: Setup Nodejs + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install Dependencies + run: make tests + + - name: Go Coverage Badge + uses: tj-actions/coverage-badge-go@v2 + with: + filename: coverage.out + + - name: Verify Changed files + uses: tj-actions/verify-changed-files@v18 + id: verify-changed-files + with: + files: README.md - - name: Setup Nodejs - uses: actions/setup-node@v3 - with: - node-version: 18 + - name: Commit changes + if: steps.verify-changed-files.outputs.files_changed == 'true' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add README.md + git commit -m "chore: Updated coverage badge." + + - name: Push changes + if: steps.verify-changed-files.outputs.files_changed == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ steps.import-secrets.outputs.VAULT_GITHUB_TOKEN }} + branch: ${{ github.GITHUB_REF }} # This also worked with GITHUB_BASE_REF + repository: ${{ github.repository }} + force_with_lease: true + + API-Test: + runs-on: ubuntu-latest + needs: [run-ci] + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: API Test - run: | - make ci-tests + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + check-latest: true + - name: API Test + run: | + make ci-tests diff --git a/.releaserc.yaml b/.releaserc.yaml index c90c958..e71d37e 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -1,4 +1,5 @@ -# Copyright (c) HashiCorp, Inc. +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: MPL-2.0 branches: [main] repositoryUrl: https://github.com/spectrocloud/hello-universe-api diff --git a/Dockerfile b/Dockerfile index 95e6bb7..373e7ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ -# Copyright (c) HashiCorp, Inc. +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: MPL-2.0 -FROM golang:1.20.2-alpine3.17 as builder +FROM golang:1.21.7-alpine3.19 as builder WORKDIR /go/src/app COPY . . RUN go build -o /go/bin/app && \ diff --git a/Makefile b/Makefile index ad787f1..2ea0fac 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,11 @@ docker-pull-db: docker-run-db: docker run --detach -p 5432:5432 --rm --name api-db ghcr.io/spectrocloud/hello-universe-db:$(VERSION) + +stop-db: + docker stop api-db + docker rm api-db + ci-tests: build docker-pull-db docker-run-db sleep 3 ./hello-universe-api 2>&1 & @@ -26,6 +31,17 @@ start-server: build docker-pull-db docker-run-db ./hello-universe-api +tests: docker-run-db + sleep 3 + go test ./... -covermode=count -coverprofile=coverage.out + go tool cover -func=coverage.out -o=coverage.out + docker stop api-db + + +view-coverage: ## View the code coverage + @echo "Viewing the code coverage" + go tool cover -html=coverage.out + license: ## Adds a license header to all files. Reference https://github.com/hashicorp/copywrite to learn more. @echo "Applying license headers..." copywrite headers \ No newline at end of file diff --git a/README.md b/README.md index 30451bf..09cc975 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release) +![Coverage](https://img.shields.io/badge/Coverage-56.1%25-yellow) # Hello Universe API @@ -21,8 +22,8 @@ A Postman collection is available to help you explore the API. Review the [Postm The quickest method to start the API server locally is by using the Docker image. ```shell -docker pull ghcr.io/spectrocloud/hello-universe-api:1.0.9 -docker run -p 3000:3000 ghcr.io/spectrocloud/hello-universe-api:1.0.9 +docker pull ghcr.io/spectrocloud/hello-universe-api:1.0.11 +docker run -p 3000:3000 ghcr.io/spectrocloud/hello-universe-api:1.0.11 ``` To start the API server you must have connectivity to a Postgres instance. Use [environment variables](#environment-variables) to customize the API server start parameters. diff --git a/commitlint.config.js b/commitlint.config.js index f4f8ed2..cf43a5e 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,19 +1,18 @@ /** - * Copyright (c) HashiCorp, Inc. + * Copyright (c) Spectro Cloud + * SPDX-License-Identifier: MPL-2.0 */ module.exports = { - extends: [ - '@commitlint/config-conventional' - ], - rules: { - 'body-max-length': [0, 'always'], - 'body-max-line-length': [0, 'always'], - 'footer-max-length': [0, 'always'], - 'footer-max-line-length': [0, 'always'], - 'header-max-length': [0, 'always'], - 'scope-max-length': [0, 'always'], - 'subject-max-length': [0, 'always'], - 'type-max-length': [0, 'always'], - }, -} \ No newline at end of file + extends: ["@commitlint/config-conventional"], + rules: { + "body-max-length": [0, "always"], + "body-max-line-length": [0, "always"], + "footer-max-length": [0, "always"], + "footer-max-line-length": [0, "always"], + "header-max-length": [0, "always"], + "scope-max-length": [0, "always"], + "subject-max-length": [0, "always"], + "type-max-length": [0, "always"], + }, +}; diff --git a/endpoints/counterRoute.go b/endpoints/counterRoute.go index a1e0c75..4c968c4 100644 --- a/endpoints/counterRoute.go +++ b/endpoints/counterRoute.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package endpoints diff --git a/endpoints/counterRoute_test.go b/endpoints/counterRoute_test.go new file mode 100644 index 0000000..c2b6f4a --- /dev/null +++ b/endpoints/counterRoute_test.go @@ -0,0 +1,172 @@ +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 + +package endpoints + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + "github.com/rs/zerolog/log" +) + +// startDB returns a new database connection to the counter database. +// A local database is required to run the tests. +func startDB() (*sqlx.DB, error) { + dbUser := "postgres" + dbPassword := "password" + dbName := "counter" + host := "localhost" + dbEncryption := "disable" + + db, err := sqlx.Open("postgres", fmt.Sprintf( + "host=%s port=%d dbname=%s user=%s password=%s connect_timeout=5 sslmode=%s", + host, + 5432, + dbName, + dbUser, + dbPassword, + dbEncryption, + )) + if err != nil { + return nil, err + } + + err = db.Ping() + if err != nil { + return nil, err + } + + return db, err + +} + +func TestNewCounterHandlerContext(t *testing.T) { + + db, err := startDB() + if err != nil { + t.Errorf("Expected a new database connection, but got %s", err) + } + + ctx := context.Background() + authorization := true + + counter := NewCounterHandlerContext(db, ctx, authorization) + if counter == nil { + t.Errorf("Expected a new CounterRoute, but got nil") + } + + if counter != nil { + + if counter.ctx != ctx { + t.Errorf("Expected context to be %v, but got %v", ctx, counter.ctx) + } + + if counter.authorization != authorization { + t.Errorf("Expected authorization to be %v, but got %v", authorization, counter.authorization) + } + + } + +} + +func TestCounterHTTPHandlerGET(t *testing.T) { + + db, err := startDB() + if err != nil { + t.Errorf("Expected a new database connection, but got %s", err) + } + + sqlQuery := `INSERT INTO counter(date,browser,os) VALUES ($1, $2, $3)` + _, err = db.Exec(sqlQuery, time.Now(), "Chrome", "Windows") + if err != nil { + t.Errorf("Error inserting into counter table: %s", err) + } + + counter := NewCounterHandlerContext(db, context.Background(), false) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "v1/counter", nil) + if err != nil { + t.Fatal(err) + } + + handler := http.HandlerFunc(counter.CounterHTTPHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + var result counterSummary + + err = json.Unmarshal(rr.Body.Bytes(), &result) + if err != nil { + t.Errorf("Error unmarshalling response: %s", err) + } + + fmt.Println(result) + + if result.Total == 0 { + t.Errorf("handler returned unexpected body: got %v want %v", + result.Total, 0) + } +} + +func TestCounterHTTPHandlerPOST(t *testing.T) { + + db, err := startDB() + if err != nil { + t.Errorf("Expected a new database connection, but got %s", err) + } + + counter := NewCounterHandlerContext(db, context.Background(), false) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("POST", "v1/counter", nil) + if err != nil { + t.Fatal(err) + } + + handler := http.HandlerFunc(counter.CounterHTTPHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusCreated { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusCreated) + } + + var result counterSummary + + err = json.Unmarshal(rr.Body.Bytes(), &result) + if err != nil { + t.Errorf("Error unmarshalling response: %s", err) + } + + if result.Total < 0 { + t.Errorf("handler total returned unexpected body: got %v want %s", + result.Total, "larger than zero") + } + + + sqlQuery := `SELECT COUNT(*) AS total FROM counter` + var counterSummary counterSummary + err = db.GetContext(context.Background(), &counterSummary, sqlQuery) + if err != nil { + log.Error().Err(err).Msg("Error getting counter value.") + log.Debug().Msgf("SQL query: %s", sqlQuery) + } + + if counterSummary.Total < 1 { + t.Errorf("handler returned unexpected body: got %v want %s", + counterSummary.Total, "larger than zero") + } +} diff --git a/endpoints/healthRoute.go b/endpoints/healthRoute.go index 4fa6021..6c8e0ff 100644 --- a/endpoints/healthRoute.go +++ b/endpoints/healthRoute.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package endpoints @@ -12,11 +13,11 @@ import ( ) // NewHandlerContext returns a new CounterRoute with a database connection. -func NewHealthHandlerContext(ctx context.Context, authorization bool) *HeatlhRoute { - return &HeatlhRoute{ctx, authorization} +func NewHealthHandlerContext(ctx context.Context, authorization bool) *HealthRoute { + return &HealthRoute{ctx, authorization} } -func (health *HeatlhRoute) HealthHTTPHandler(writer http.ResponseWriter, request *http.Request) { +func (health *HealthRoute) HealthHTTPHandler(writer http.ResponseWriter, request *http.Request) { log.Debug().Msg("Health check request received.") writer.Header().Set("Content-Type", "application/json") writer.Header().Set("Access-Control-Allow-Origin", "*") @@ -51,6 +52,6 @@ func (health *HeatlhRoute) HealthHTTPHandler(writer http.ResponseWriter, request } // getHandler returns a health check response. -func (health *HeatlhRoute) getHandler(r *http.Request) ([]byte, error) { +func (health *HealthRoute) getHandler(r *http.Request) ([]byte, error) { return json.Marshal(map[string]string{"status": "OK"}) } diff --git a/endpoints/healthRoute_test.go b/endpoints/healthRoute_test.go new file mode 100644 index 0000000..5f7c5bb --- /dev/null +++ b/endpoints/healthRoute_test.go @@ -0,0 +1,144 @@ +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 + +package endpoints + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestNewHealthHanderContext(t *testing.T) { + ctx := context.Background() + authorization := true + health := NewHealthHandlerContext(ctx, authorization) + if health == nil { + t.Errorf("Expected a new HealthRoute, but got nil") + } + + if health != nil { + + if health.ctx == nil { + t.Errorf("Expected context to be %v, but got %v", ctx, health.ctx) + } + + if health.authorization != authorization { + t.Errorf("Expected authorization to be %v, but got %v", authorization, health.authorization) + } + + } + +} + +func TestHealthHTTPHandler(t *testing.T) { + + health := NewHealthHandlerContext(context.Background(), false) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "v1/health", nil) + if err != nil { + t.Fatal(err) + } + + handler := http.HandlerFunc(health.HealthHTTPHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := `{"status":"OK"}` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + +} + +func TestHealthHTTPHandlerInvalidMethod(t *testing.T) { + + health := NewHealthHandlerContext(context.Background(), false) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("POST", "v1/health", nil) + if err != nil { + t.Fatal(err) + } + + handler := http.HandlerFunc(health.HealthHTTPHandler) + handler.ServeHTTP(rr, req) + msg := strings.TrimSpace(rr.Body.String()) + + if status := rr.Code; status != http.StatusMethodNotAllowed { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := `Invalid request method.` + if msg != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + msg, expected) + } + +} + +func TestHealthHTTPHandlerInvalidToken(t *testing.T) { + + health := NewHealthHandlerContext(context.Background(), true) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "v1/health", nil) + if err != nil { + t.Fatal(err) + } + + handler := http.HandlerFunc(health.HealthHTTPHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusUnauthorized { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := `Invalid credentials` + msg := strings.TrimSpace(rr.Body.String()) + if msg != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + +} + +func TestHealthHTTPHandlerValidToken(t *testing.T) { + + health := NewHealthHandlerContext(context.Background(), true) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "v1/health", nil) + if err != nil { + t.Fatal(err) + } + req.Header = http.Header{ + "Authorization": []string{"Bearer 931A3B02-8DCC-543F-A1B2-69423D1A0B94"}, + } + + handler := http.HandlerFunc(health.HealthHTTPHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := `{"status":"OK"}` + msg := strings.TrimSpace(rr.Body.String()) + if msg != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + +} diff --git a/endpoints/types.go b/endpoints/types.go index 0bbc6e1..0f72355 100644 --- a/endpoints/types.go +++ b/endpoints/types.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package endpoints @@ -27,7 +28,7 @@ type counterSummary struct { Counts []Counter `json:"counts,omitempty" db:"counts"` } -type HeatlhRoute struct { +type HealthRoute struct { ctx context.Context authorization bool } diff --git a/go.mod b/go.mod index 996b857..8ff2363 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,68 @@ module spectrocloud.com/hello-universe-api -go 1.20 +go 1.21 require ( github.com/jmoiron/sqlx v1.3.5 - github.com/lib/pq v1.10.7 - github.com/mileusna/useragent v1.2.1 - github.com/rs/zerolog v1.29.0 - go.uber.org/automaxprocs v1.5.2 + github.com/lib/pq v1.10.9 + github.com/mileusna/useragent v1.3.4 + github.com/rs/zerolog v1.32.0 + github.com/testcontainers/testcontainers-go v0.28.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.28.0 + go.uber.org/automaxprocs v1.5.3 ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.13 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/docker v25.0.3+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.7 // indirect + github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - golang.org/x/sys v0.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v3 v3.24.1 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect + go.opentelemetry.io/otel v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/otel/trace v1.23.1 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/tools v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect + google.golang.org/grpc v1.62.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect ) diff --git a/go.sum b/go.sum index 913bb60..880c0a3 100644 --- a/go.sum +++ b/go.sum @@ -1,38 +1,234 @@ -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= +github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= +github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= +github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0= +github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mileusna/useragent v1.2.1 h1:p3RJWhi3LfuI6BHdddojREyK3p6qX67vIfOVMnUIVr0= -github.com/mileusna/useragent v1.2.1/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk= +github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= -github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= -github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= -go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= -go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME= -go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= +github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/testcontainers/testcontainers-go v0.28.0 h1:1HLm9qm+J5VikzFDYhOd+Zw12NtOl+8drH2E8nTY1r8= +github.com/testcontainers/testcontainers-go v0.28.0/go.mod h1:COlDpUXbwW3owtpMkEB1zo9gwb1CoKVKlyrVPejF4AU= +github.com/testcontainers/testcontainers-go/modules/postgres v0.28.0 h1:ff0s4JdYIdNAVSi/SrpN2Pdt1f+IjIw3AKjbHau8Un4= +github.com/testcontainers/testcontainers-go/modules/postgres v0.28.0/go.mod h1:fXgcYpbyrduNdiz2qRZuYkmvqLnEqsjbQiBNYH1ystI= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/internal/authorization.go b/internal/authorization.go index 09f3d09..f6b9c38 100644 --- a/internal/authorization.go +++ b/internal/authorization.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package internal diff --git a/internal/authorization_test.go b/internal/authorization_test.go index 34991fa..8cfb589 100644 --- a/internal/authorization_test.go +++ b/internal/authorization_test.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package internal diff --git a/internal/constants.go b/internal/constants.go index 13e323a..24009a8 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package internal diff --git a/internal/database.go b/internal/database.go index b8950d7..41e5dc5 100644 --- a/internal/database.go +++ b/internal/database.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package internal diff --git a/internal/database_test.go b/internal/database_test.go new file mode 100644 index 0000000..d3f111d --- /dev/null +++ b/internal/database_test.go @@ -0,0 +1,106 @@ +package internal + +import ( + "context" + "fmt" + "log" + "testing" + "time" + + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +func TestInitDB(t *testing.T) { + + ctx := context.Background() + + dbName := "counter" + dbUser := "postgres" + dbPassword := "password" + + const ( + image string = "ghcr.io/spectrocloud/hello-universe-db" + image_veresion string = "1.0.0" + ) + + postgresContainer, err := postgres.RunContainer(ctx, + testcontainers.WithImage(image+":"+image_veresion), + postgres.WithDatabase(dbName), + postgres.WithUsername(dbUser), + postgres.WithPassword(dbPassword), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(5*time.Second)), + ) + if err != nil { + log.Fatalf("failed to start container: %s", err) + } + + db, err := startDB(ctx, postgresContainer) + if err != nil { + log.Fatalf("failed to start database: %s", err) + } + + err = InitDB(ctx, db) + if err != nil { + t.Errorf("Expected database initailization, but got %s", err) + } + + // Check if the table was created + query := `SELECT EXISTS ( + SELECT 1 FROM pg_tables + WHERE schemaname = 'public' + AND tablename = 'counter' + ); + ` + + var tableExists bool + err = db.QueryRowContext(ctx, query).Scan(&tableExists) + if err != nil { + t.Errorf("Unable to query the database: %s", err) + } + + + if !tableExists { + t.Errorf("Expected table 'counter' to exist, but it does not.") + } + + + defer func() { + if err := postgresContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + }() + +} + +func startDB(ctx context.Context, container *postgres.PostgresContainer) (*sqlx.DB, error) { + + connection, err := container.ConnectionString(ctx, + "user=posgres", + "password=password", + "dbname=counter", + "sslmode=disable", + ) + if err != nil { + return nil, fmt.Errorf("failed to get container connection string: %s", err) + } + + db, err := sqlx.Open("postgres", connection) + if err != nil { + return nil, err + } + + err = db.Ping() + if err != nil { + return nil, err + } + + return db, err + +} diff --git a/internal/helpers.go b/internal/helpers.go index d7a5cc5..0e8d52c 100644 --- a/internal/helpers.go +++ b/internal/helpers.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package internal diff --git a/internal/log.go b/internal/log.go index 28b9e9a..3cb21a1 100644 --- a/internal/log.go +++ b/internal/log.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package internal diff --git a/main.go b/main.go index b2fe5f8..6698083 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,5 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright (c) Spectro Cloud +// SPDX-License-Identifier: MPL-2.0 package main