From 4a27ab6d8ae4cb3f905ec2de77f294041814e69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Baldeweg?= <56736413+abaldeweg@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:51:30 +0000 Subject: [PATCH] Init --- .devcontainer/devcontainer.json | 23 ++ .editorconfig | 10 + .github/workflows/unit.yaml | 62 +++++ .gitignore | 1 + LICENSE | 7 + README.md | 87 ++++++- admincli/.goreleaser.yaml | 37 +++ admincli/admincli.yaml | 2 + admincli/cmd/dump.go | 58 +++++ admincli/cmd/pull.go | 32 +++ admincli/cmd/reboot.go | 28 +++ admincli/cmd/refresh.go | 41 ++++ admincli/cmd/root.go | 33 +++ admincli/command/command.go | 14 ++ admincli/go.mod | 31 +++ admincli/main.go | 9 + blog/Dockerfile | 13 + blog/content/article/article.go | 26 ++ blog/content/content.go | 20 ++ blog/content/home/home.go | 82 +++++++ blog/data/auth/api_keys.json | 8 + blog/data/content/home.yaml | 3 + blog/data/content/test/test.md | 1 + blog/go.mod | 55 +++++ blog/main.go | 25 ++ blog/openapi.yaml | 131 ++++++++++ blog/router/router.go | 59 +++++ framework/apikey/apikey.go | 93 ++++++++ framework/config/config.go | 46 ++++ framework/cors/cors.go | 28 +++ framework/cors/cors_test.go | 30 +++ framework/go.mod | 100 ++++++++ framework/router/router.go | 46 ++++ framework/storage/cloud.go | 83 +++++++ framework/storage/filesystem.go | 40 ++++ framework/storage/storage.go | 68 ++++++ framework/storage/storage_test.go | 56 +++++ gateway/.env | 2 + gateway/Dockerfile | 13 + gateway/go.mod | 58 +++++ gateway/main.go | 28 +++ gateway/openapi.yaml | 87 +++++++ gateway/proxy/proxy.go | 78 ++++++ gateway/proxy/proxy_test.go | 48 ++++ gateway/router/router.go | 28 +++ go.work | 8 + go.work.sum | 384 ++++++++++++++++++++++++++++++ renovate.json | 6 + 48 files changed, 2227 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .editorconfig create mode 100644 .github/workflows/unit.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 admincli/.goreleaser.yaml create mode 100644 admincli/admincli.yaml create mode 100644 admincli/cmd/dump.go create mode 100644 admincli/cmd/pull.go create mode 100644 admincli/cmd/reboot.go create mode 100644 admincli/cmd/refresh.go create mode 100644 admincli/cmd/root.go create mode 100644 admincli/command/command.go create mode 100644 admincli/go.mod create mode 100644 admincli/main.go create mode 100644 blog/Dockerfile create mode 100644 blog/content/article/article.go create mode 100644 blog/content/content.go create mode 100644 blog/content/home/home.go create mode 100644 blog/data/auth/api_keys.json create mode 100644 blog/data/content/home.yaml create mode 100644 blog/data/content/test/test.md create mode 100644 blog/go.mod create mode 100644 blog/main.go create mode 100644 blog/openapi.yaml create mode 100644 blog/router/router.go create mode 100644 framework/apikey/apikey.go create mode 100644 framework/config/config.go create mode 100644 framework/cors/cors.go create mode 100644 framework/cors/cors_test.go create mode 100644 framework/go.mod create mode 100644 framework/router/router.go create mode 100644 framework/storage/cloud.go create mode 100644 framework/storage/filesystem.go create mode 100644 framework/storage/storage.go create mode 100644 framework/storage/storage_test.go create mode 100644 gateway/.env create mode 100644 gateway/Dockerfile create mode 100644 gateway/go.mod create mode 100644 gateway/main.go create mode 100644 gateway/openapi.yaml create mode 100644 gateway/proxy/proxy.go create mode 100644 gateway/proxy/proxy_test.go create mode 100644 gateway/router/router.go create mode 100644 go.work create mode 100644 go.work.sum create mode 100644 renovate.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..073507e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +{ + "name": "Go", + "image": "mcr.microsoft.com/devcontainers/go:1-1.23-bullseye", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "ghcr.io/guiyomh/features/goreleaser:0": {}, + "ghcr.io/abaldeweg/devcontainer_features/bash:latest": {} + }, + "forwardPorts": [ + 5984 + ], + "customizations": { + "vscode": { + "extensions": [ + "EditorConfig.EditorConfig", + "golang.go", + "DavidAnson.vscode-markdownlint", + "redhat.vscode-yaml", + "42Crunch.vscode-openapi" + ] + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..694709f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml new file mode 100644 index 0000000..ba68051 --- /dev/null +++ b/.github/workflows/unit.yaml @@ -0,0 +1,62 @@ +name: Unit-Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test-admincli: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + - name: Run admincli tests + working-directory: ./admincli + run: | + go mod tidy + go test ./... + + test-blog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + - name: Run blog tests + working-directory: ./blog + run: | + go mod tidy + go test ./... + + test-framework: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + - name: Run framework tests + working-directory: ./framework + run: | + go mod tidy + go test ./... + + test-gateway: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.23" + - name: Run gateway tests + working-directory: ./gateway + run: | + go mod tidy + go test ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b613f3d --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2022 - 2024 André Baldeweg + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 2f8ee16..ccba237 100644 --- a/README.md +++ b/README.md @@ -1 +1,86 @@ -# warehouse-server +# monorepo-go + +monorepo-go is a database to manage your warehouse. + +## Getting Started + +## Blog + +Mount auth volume to `/usr/src/app/data/auth/` and data volume to `/usr/src/app/data/content/`. + +The routes needs the API-Key to contain the `articles` permission. + +## Framework + +```go +package main + +import ( + "log" + + "github.com/abaldeweg/warehouse-server/blog/router" + "github.com/abaldeweg/warehouse-server/framework/config" +) + +func main() { + config.LoadAppConfig() + + r := router.Routes() + log.Fatal(r.Run(":8080")) +} +``` + +### Config + +```go +import "github.com/abaldeweg/warehouse-server/framework/config" + +config.LoadAppConfig(config.WithName("myconfig"), config.WithFormat("json"), config.WithPaths("./config", ".")) + +viper.SetDefault("CORS_ALLOW_ORIGIN", "http://127.0.0.1") +``` + +### ApiKey + +```go +import "github.com/abaldeweg/warehouse-server/framework/apikey" + +apikey.IsValidAPIKey("key") +apikey.HasPermission("key", "permission") +``` + +### Cors + +```go +import "github.com/abaldeweg/warehouse-server/framework/cors" + +viper.SetDefault("CORS_ALLOW_ORIGIN", "*") + +corsConfig := cors.NewCors() +corsConfig.AllowOrigins = []string{viper.GetString("CORS_ALLOW_ORIGIN"), "http://127.0.0.1"} +corsConfig.SetCorsHeaders() + +r := gin.Default() +r.Use(corsConfig.SetCorsHeaders()) +``` + +## Settings + +|Var |Description |Used by +|-----------------------|-------------------------------------------|-------------------------------- +|CORS_ALLOW_ORIGIN |Allowed origins |gateway, blog +|API_CORE |API endpoint for the core |gateway +|project_dir |Path to docker compose |admincli +|database |Database name to dump |admincli + +admincli will read a config file from following paths: + +- /etc/admincli/admincli.yaml + +Example + +```yaml +// admincli.yaml +project_dir: . +database: db-1 +``` diff --git a/admincli/.goreleaser.yaml b/admincli/.goreleaser.yaml new file mode 100644 index 0000000..ce08a12 --- /dev/null +++ b/admincli/.goreleaser.yaml @@ -0,0 +1,37 @@ +before: + hooks: + - go mod tidy + - go generate ./... + +release: + disable: true + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + id: admincli + binary: admincli + +archives: + - format: tar.gz + name_template: >- + {{ .Binary }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + +nfpms: + - + package_name: admincli + section: misc + description: "Offers tools for maintenance" + maintainer: "André Baldeweg" + formats: + - deb + +checksum: + name_template: "checksums.txt" diff --git a/admincli/admincli.yaml b/admincli/admincli.yaml new file mode 100644 index 0000000..2b04df7 --- /dev/null +++ b/admincli/admincli.yaml @@ -0,0 +1,2 @@ +project_dir: . +database: db-1 diff --git a/admincli/cmd/dump.go b/admincli/cmd/dump.go new file mode 100644 index 0000000..0b18f50 --- /dev/null +++ b/admincli/cmd/dump.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var dumpCmd = &cobra.Command{ + Use: "dump", + Short: "Dump database", + Long: `Dumps the database to the file system`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Dumping database") + + // command + c := exec.Command("docker", "exec", viper.GetString("database"), "sh", "-c", fmt.Sprintf("exec mysqldump %s -uroot -p\"%s\"", "$MYSQL_DATABASE", "$MYSQL_ROOT_PASSWORD")) + + // create directory + dirPath := fmt.Sprintf("%s/dump/", viper.GetString("project_dir")) + + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + err := os.MkdirAll(dirPath, 0755) + if err != nil { + fmt.Println(err) + return + } + } + + // create dump + weekday := strings.ToLower(time.Now().Weekday().String()) + + file, err := os.Create(fmt.Sprintf("%sdump_%s.sql", dirPath, weekday)) + if err != nil { + fmt.Println(err) + return + } + defer file.Close() + + // run command + c.Stdout = file + err = c.Run() + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Done") + } + }, +} + +func init() { + rootCmd.AddCommand(dumpCmd) +} diff --git a/admincli/cmd/pull.go b/admincli/cmd/pull.go new file mode 100644 index 0000000..1e0bd60 --- /dev/null +++ b/admincli/cmd/pull.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/abaldeweg/warehouse-server/admincli/command" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var pullCmd = &cobra.Command{ + Use: "pull", + Short: "Refresh container images", + Long: `Fetch the latest images from the registry. After restarting the container the new image will be used.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Pulling new images...") + + out, err := command.Command([]string{"/usr/bin/docker", "compose", "--project-directory", viper.GetString("project_dir"), "pull"}) + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(out)) + fmt.Println("Done") + }, +} + + +func init() { + rootCmd.AddCommand(pullCmd) +} diff --git a/admincli/cmd/reboot.go b/admincli/cmd/reboot.go new file mode 100644 index 0000000..e2d79ef --- /dev/null +++ b/admincli/cmd/reboot.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/abaldeweg/warehouse-server/admincli/command" + "github.com/spf13/cobra" +) + +var rebootCmd = &cobra.Command{ + Use: "reboot", + Short: "Reboot the OS.", + Long: `Reboot the OS. The connection will be terminated.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Reboot system") + fmt.Println("The connection will be terminated") + + _, err := command.Command([]string{"reboot"}) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + rootCmd.AddCommand(rebootCmd) +} diff --git a/admincli/cmd/refresh.go b/admincli/cmd/refresh.go new file mode 100644 index 0000000..69986d0 --- /dev/null +++ b/admincli/cmd/refresh.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/abaldeweg/warehouse-server/admincli/command" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var refreshCmd = &cobra.Command{ + Use: "refresh", + Short: "Reload container images", + Long: `Restart all containers.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Stopping all containers...") + + stopContainers, err := command.Command([]string{"/usr/bin/docker", "compose", "--project-directory", viper.GetString("project_dir"), "down"}) + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(stopContainers)) + fmt.Println("Done") + + fmt.Println("Starting all containers...") + + startContainers, err := command.Command([]string{"/usr/bin/docker", "compose", "--project-directory", viper.GetString("project_dir"), "up", "-d"}) + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(startContainers)) + fmt.Println("Done") + }, +} + +func init() { + rootCmd.AddCommand(refreshCmd) +} diff --git a/admincli/cmd/root.go b/admincli/cmd/root.go new file mode 100644 index 0000000..414876d --- /dev/null +++ b/admincli/cmd/root.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "log" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var rootCmd = &cobra.Command{ + Use: "admincli", + Short: "Maintenance tools", + Long: `The app gives you simple access to maintenance tools.`, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func init() { + viper.SetDefault("project_dir", ".") + + viper.SetConfigName("admincli") + viper.SetConfigType("yaml") + viper.AddConfigPath("/etc/admincli/") + + if err := viper.ReadInConfig(); err != nil { + log.Fatal(err) + } +} diff --git a/admincli/command/command.go b/admincli/command/command.go new file mode 100644 index 0000000..feebce6 --- /dev/null +++ b/admincli/command/command.go @@ -0,0 +1,14 @@ +package command + +import ( + "os/exec" +) + +func Command(cmd []string) ([]byte, error) { + out, err := exec.Command(cmd[0], cmd[1:]...).Output() + if err != nil { + return nil, err + } + + return out, nil +} diff --git a/admincli/go.mod b/admincli/go.mod new file mode 100644 index 0000000..306b9a6 --- /dev/null +++ b/admincli/go.mod @@ -0,0 +1,31 @@ +module github.com/abaldeweg/warehouse-server/admincli + +go 1.23 + +require github.com/spf13/cobra v1.8.1 + +require ( + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.19.0 +) diff --git a/admincli/main.go b/admincli/main.go new file mode 100644 index 0000000..aa5b2fc --- /dev/null +++ b/admincli/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/abaldeweg/warehouse-server/admincli/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/blog/Dockerfile b/blog/Dockerfile new file mode 100644 index 0000000..6448da1 --- /dev/null +++ b/blog/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.23 + +WORKDIR /usr/src/app + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . +RUN go build -v -o /usr/local/bin ./... + +EXPOSE 8080 + +CMD ["blog"] diff --git a/blog/content/article/article.go b/blog/content/article/article.go new file mode 100644 index 0000000..77b6360 --- /dev/null +++ b/blog/content/article/article.go @@ -0,0 +1,26 @@ +package article + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/abaldeweg/warehouse-server/blog/content" +) + +// GetArticle retrieves an article from the filesystem safely. +func GetArticle(path string) (string, error) { + fullPath := filepath.Join(content.GetContentRoot(), filepath.Clean("/"+path)) + + if !strings.HasPrefix(fullPath, content.GetContentRoot()) { + return "", fmt.Errorf("error: path traversal attempt detected") + } + + data, err := os.ReadFile(fullPath) + if err != nil { + return "", fmt.Errorf("error reading article: %w", err) + } + + return string(data), nil +} diff --git a/blog/content/content.go b/blog/content/content.go new file mode 100644 index 0000000..9f918b6 --- /dev/null +++ b/blog/content/content.go @@ -0,0 +1,20 @@ +package content + +import ( + "time" +) + +// Home represents an entry in the content index. +type Home struct { + Slug string `json:"slug"` + Date time.Time `json:"date"` + Summary string `json:"summary"` +} + +// contentRoot is the root directory for content files. +const contentRoot = "data/content" + +// GetContentRoot returns the root directory for content files. +func GetContentRoot() string { + return contentRoot +} diff --git a/blog/content/home/home.go b/blog/content/home/home.go new file mode 100644 index 0000000..f8aa458 --- /dev/null +++ b/blog/content/home/home.go @@ -0,0 +1,82 @@ +package home + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/abaldeweg/warehouse-server/blog/content" + "gopkg.in/yaml.v3" +) + +// GetHome returns the content index as a JSON string. +func GetHome() (string, error) { + indexPath := filepath.Join(content.GetContentRoot(), "home.yaml") + + entries, err := loadHome(indexPath) + if err != nil { + return "", fmt.Errorf("error getting index: %w", err) + } + + jsonData, err := json.Marshal(entries) + if err != nil { + return "", fmt.Errorf("error marshalling index to JSON: %w", err) + } + + return string(jsonData), nil +} + +// GetNewArticles returns the number of articles newer than the specified number of days. +func GetNewArticles(days int) (int, error) { + indexPath := filepath.Join(content.GetContentRoot(), "home.yaml") + + entries, err := loadHome(indexPath) + if err != nil { + return 0, fmt.Errorf("error getting index: %w", err) + } + + cutoffDate := time.Now().AddDate(0, 0, -days) + + newArticleCount := 0 + for _, entry := range entries { + if entry.Date.After(cutoffDate) { + newArticleCount++ + } + } + + return newArticleCount, nil +} + +// loadHome reads the content index file and returns its content as []IndexEntry. +// If the file doesn't exist, it creates a default one. +func loadHome(indexPath string) ([]content.Home, error) { + if err := createHomeFileIfNotExists(indexPath); err != nil { + return nil, err + } + + data, err := os.ReadFile(indexPath) + if err != nil { + return nil, fmt.Errorf("error reading index file: %w", err) + } + + var entries []content.Home + if err := yaml.Unmarshal(data, &entries); err != nil { + return nil, fmt.Errorf("error unmarshalling index file: %w", err) + } + + return entries, nil +} + +// createHomeFileIfNotExists creates the index file if it doesn't exist. +func createHomeFileIfNotExists(indexPath string) error { + if _, err := os.Stat(indexPath); os.IsNotExist(err) { + exampleContent := []byte("- slug: example\n date: 2024-09-24\n summary: This is an example article.\n") + if err := os.WriteFile(indexPath, exampleContent, 0644); err != nil { + return fmt.Errorf("error creating index file: %w", err) + } + } + + return nil +} diff --git a/blog/data/auth/api_keys.json b/blog/data/auth/api_keys.json new file mode 100644 index 0000000..ea0ec5a --- /dev/null +++ b/blog/data/auth/api_keys.json @@ -0,0 +1,8 @@ +[ + { + "key": "test", + "permissions": [ + "articles" + ] + } +] diff --git a/blog/data/content/home.yaml b/blog/data/content/home.yaml new file mode 100644 index 0000000..168be20 --- /dev/null +++ b/blog/data/content/home.yaml @@ -0,0 +1,3 @@ +- slug: example + date: 2024-09-24 + summary: This is an example article. diff --git a/blog/data/content/test/test.md b/blog/data/content/test/test.md new file mode 100644 index 0000000..83c831f --- /dev/null +++ b/blog/data/content/test/test.md @@ -0,0 +1 @@ +# test diff --git a/blog/go.mod b/blog/go.mod new file mode 100644 index 0000000..4c5be1a --- /dev/null +++ b/blog/go.mod @@ -0,0 +1,55 @@ +module github.com/abaldeweg/warehouse-server/blog + +go 1.23 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/abaldeweg/warehouse-server/framework v0.0.0-20241015163101-08b93c4b15a5 + github.com/spf13/viper v1.19.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/cors v1.7.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // 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/protobuf v1.34.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) diff --git a/blog/main.go b/blog/main.go new file mode 100644 index 0000000..c074a64 --- /dev/null +++ b/blog/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + + "github.com/abaldeweg/warehouse-server/blog/router" + "github.com/abaldeweg/warehouse-server/framework/config" + "github.com/abaldeweg/warehouse-server/framework/cors" + "github.com/spf13/viper" +) + +func main() { + config.LoadAppConfig() + + viper.SetDefault("CORS_ALLOW_ORIGIN", "*") + + corsConfig := cors.NewCors() + corsConfig.AllowOrigins = []string{viper.GetString("CORS_ALLOW_ORIGIN")} + corsConfig.SetCorsHeaders() + + r := router.Routes() + r.Use(corsConfig.SetCorsHeaders()) + + log.Fatal(r.Run(":8080")) +} diff --git a/blog/openapi.yaml b/blog/openapi.yaml new file mode 100644 index 0000000..85a68be --- /dev/null +++ b/blog/openapi.yaml @@ -0,0 +1,131 @@ +openapi: 3.0.0 +info: + title: Blog API + version: v1 + description: Save and load content from the file system. + +externalDocs: + description: Github + url: https://github.com/abaldeweg/warehouse-server + +servers: + - url: http://localhost:8080/ + +paths: + /home: + get: + summary: Get a list of all articles + description: Retrieves a list of all articles with their slug, date and a short summary. + responses: + "200": + description: Index retrieved successfully. + content: + application/json: + schema: + type: array + items: + type: object + properties: + slug: + type: string + description: The slug of the article. + date: + type: string + format: date-time + description: The date of the article. + summary: + type: string + description: A short summary of the article. + "500": + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + security: + - APIKeyAuth: [] + + /home/new/{days}: + get: + summary: Get the number of new articles + description: Retrieves the number of articles newer than the specified number of days. + parameters: + - in: path + name: days + schema: + type: integer + required: true + description: The number of days to look back for new articles. + example: 7 + responses: + "200": + description: Number of new articles retrieved successfully. + content: + application/json: + schema: + type: object + properties: + new_articles: + type: integer + description: The number of new articles. + "400": + description: Bad Request - Invalid 'days' parameter. Days must be an integer. + content: + application/json: + schema: + type: object + properties: + error: + type: string + "500": + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + security: + - APIKeyAuth: [] + + /article/{path}: + get: + summary: Get content by path + description: Retrieves content from the filesystem based on the provided path. + parameters: + - in: path + name: path + schema: + type: string + required: true + description: The path to the content file. + example: test/test.md + responses: + "200": + description: Content retrieved successfully. + content: + text/plain: + schema: + type: string + "500": + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + security: + - APIKeyAuth: [] + +components: + securitySchemes: + APIKeyAuth: + type: apiKey + in: header + name: X-API-Key diff --git a/blog/router/router.go b/blog/router/router.go new file mode 100644 index 0000000..39ef409 --- /dev/null +++ b/blog/router/router.go @@ -0,0 +1,59 @@ +package router + +import ( + "net/http" + "strconv" + + "github.com/abaldeweg/warehouse-server/blog/content/article" + "github.com/abaldeweg/warehouse-server/blog/content/home" + "github.com/abaldeweg/warehouse-server/framework/router" + "github.com/gin-gonic/gin" +) + +// Routes sets up the Gin router. +func Routes() *gin.Engine { + r := router.Engine() + + api := r.Group("/", router.ApiKeyMiddleware) + { + api.GET("/home", router.PermissionsMiddleware("articles"), func(c *gin.Context) { + index, err := home.GetHome() + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.String(http.StatusOK, index) + }) + api.GET("/home/new/:days", router.PermissionsMiddleware("articles"), func(c *gin.Context) { + daysStr := c.Param("days") + + days, err := strconv.Atoi(daysStr) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid 'days' parameter"}) + return + } + + newCount, err := home.GetNewArticles(days) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"new_articles": newCount}) + }) + api.GET("/article/*path", router.PermissionsMiddleware("articles"), func(c *gin.Context) { + path := c.Param("path") + + cnt, err := article.GetArticle(path) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.String(http.StatusOK, cnt) + }) + } + + return r +} diff --git a/framework/apikey/apikey.go b/framework/apikey/apikey.go new file mode 100644 index 0000000..75565ad --- /dev/null +++ b/framework/apikey/apikey.go @@ -0,0 +1,93 @@ +package apikey + +import ( + "encoding/json" + "errors" + "fmt" + "os" + + "github.com/google/uuid" +) + +// APIKey represents a single API key. +type APIKey struct { + Key string `json:"key"` + Permissions []string `json:"permissions"` +} + +// keysFilePath is the path to the JSON file storing API keys. +const keysFilePath = "data/auth/api_keys.json" + +// loadAPIKeys loads API keys from the JSON file. +func loadAPIKeys() ([]APIKey, error) { + data, err := os.ReadFile(keysFilePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return []APIKey{}, createAPIKeysFile(nil) + } + + return nil, fmt.Errorf("error reading API keys file: %w", err) + } + + var keys []APIKey + if err := json.Unmarshal(data, &keys); err != nil { + return nil, fmt.Errorf("error unmarshalling API keys: %w", err) + } + + return keys, nil +} + +// createAPIKeysFile saves API keys to the JSON file. +// If no keys are provided, it generates a new UUID as a default key. +func createAPIKeysFile(keys []APIKey) error { + if len(keys) == 0 { + newKey := APIKey{Key: uuid.New().String(), Permissions: []string{}} + keys = append(keys, newKey) + } + + data, err := json.MarshalIndent(keys, "", " ") + if err != nil { + return fmt.Errorf("error marshalling API keys: %w", err) + } + + return os.WriteFile(keysFilePath, data, 0644) +} + +// IsValidAPIKey checks if the given key is valid. +func IsValidAPIKey(key string) bool { + keys, err := loadAPIKeys() + if err != nil { + fmt.Println("Error loading API keys:", err) + + return false + } + + for _, k := range keys { + if k.Key == key { + return true + } + } + + return false +} + +// HasPermission checks if the given API key has the specified permission. +func HasPermission(key string, permission string) bool { + keys, err := loadAPIKeys() + if err != nil { + fmt.Println("Error loading API keys:", err) + return false + } + + for _, k := range keys { + if k.Key == key { + for _, p := range k.Permissions { + if p == permission { + return true + } + } + } + } + + return false +} diff --git a/framework/config/config.go b/framework/config/config.go new file mode 100644 index 0000000..e5fc92a --- /dev/null +++ b/framework/config/config.go @@ -0,0 +1,46 @@ +package config + +import ( + "fmt" + "log" + + "github.com/spf13/viper" +) + +func LoadAppConfig(options ...Option) { + for _, option := range options { + option() + } + + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + fmt.Println("The Config file was not found, using defaults.") + } else { + log.Fatalf("Error loading config file: %s", err) + } + } +} + +type Option func() + +func WithName(name string) Option { + return func() { + viper.SetConfigName(name) + } +} + +func WithFormat(format string) Option { + return func() { + viper.SetConfigType(format) + } +} + +func WithPaths(paths ...string) Option { + return func() { + for _, path := range paths { + viper.AddConfigPath(path) + } + } +} diff --git a/framework/cors/cors.go b/framework/cors/cors.go new file mode 100644 index 0000000..0d516f0 --- /dev/null +++ b/framework/cors/cors.go @@ -0,0 +1,28 @@ +package cors + +import ( + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +// Config represents CORS configuration options. +type Config struct { + cors.Config +} + +// NewCorsConfig creates a new CORS configuration with default values. +func NewCors() *Config { + return &Config{ + Config: cors.Config{ + AllowOrigins: []string{"http://127.0.0.1"}, + AllowMethods: []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"}, + AllowHeaders: []string{"Origin", "Authorization", "Content-Type"}, + ExposeHeaders: []string{"Content-Length"}, + }, + } +} + +// SetCorsHeaders sets up CORS middleware with the provided configuration. +func (c *Config) SetCorsHeaders() gin.HandlerFunc { + return cors.New(c.Config) +} diff --git a/framework/cors/cors_test.go b/framework/cors/cors_test.go new file mode 100644 index 0000000..82e600b --- /dev/null +++ b/framework/cors/cors_test.go @@ -0,0 +1,30 @@ +package cors + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewCorsConfig(t *testing.T) { + corsConfig := NewCors() + + assert.Equal(t, []string{"http://127.0.0.1"}, corsConfig.AllowOrigins) + assert.Equal(t, []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"}, corsConfig.AllowMethods) + assert.Equal(t, []string{"Origin", "Authorization", "Content-Type"}, corsConfig.AllowHeaders) + assert.Equal(t, []string{"Content-Length"}, corsConfig.ExposeHeaders) +} + +func TestNewCorsConfigEdit(t *testing.T) { + corsConfig := NewCors() + + corsConfig.AllowOrigins = []string{"http://test.localhost"} + corsConfig.AllowMethods = []string{"POST", "GET", "OPTIONS", "PUT"} + corsConfig.AllowHeaders = []string{"Authorization", "Content-Type"} + corsConfig.ExposeHeaders = []string{"Content-Type"} + + assert.Equal(t, []string{"http://test.localhost"}, corsConfig.AllowOrigins) + assert.Equal(t, []string{"POST", "GET", "OPTIONS", "PUT"}, corsConfig.AllowMethods) + assert.Equal(t, []string{"Authorization", "Content-Type"}, corsConfig.AllowHeaders) + assert.Equal(t, []string{"Content-Type"}, corsConfig.ExposeHeaders) +} diff --git a/framework/go.mod b/framework/go.mod new file mode 100644 index 0000000..fe28329 --- /dev/null +++ b/framework/go.mod @@ -0,0 +1,100 @@ +module github.com/abaldeweg/warehouse-server/framework + +go 1.23 + +require ( + cloud.google.com/go/storage v1.44.0 + github.com/gin-contrib/cors v1.7.2 + github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + cel.dev/expr v0.16.1 // indirect + cloud.google.com/go v0.115.1 // indirect + cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.1 // indirect + cloud.google.com/go/iam v1.2.1 // indirect + cloud.google.com/go/monitoring v1.21.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/envoyproxy/go-control-plane v0.13.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/api v0.197.0 // indirect + google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // 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.2 // indirect + google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/framework/router/router.go b/framework/router/router.go new file mode 100644 index 0000000..d2362e4 --- /dev/null +++ b/framework/router/router.go @@ -0,0 +1,46 @@ +package router + +import ( + "net/http" + + "github.com/abaldeweg/warehouse-server/framework/apikey" + "github.com/gin-gonic/gin" +) + +// ApiKeyMiddleware is a middleware to check for API key authentication. +func ApiKeyMiddleware(c *gin.Context) { + key := c.GetHeader("X-API-Key") + + if !apikey.IsValidAPIKey(key) { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"}) + return + } + + c.Next() +} + +// permissionsMiddleware is a middleware to check for API key permissions. +func PermissionsMiddleware(permissions ...string) gin.HandlerFunc { + return func(c *gin.Context) { + key := c.GetHeader("X-API-Key") + + for _, permission := range permissions { + if !apikey.HasPermission(key, permission) { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + return + + } + } + + c.Next() + } +} + +// Engine creates a gin engine with CORS and sets it to release mode. +func Engine() *gin.Engine { + gin.SetMode(gin.ReleaseMode) + + r := gin.Default() + + return r +} diff --git a/framework/storage/cloud.go b/framework/storage/cloud.go new file mode 100644 index 0000000..245bd71 --- /dev/null +++ b/framework/storage/cloud.go @@ -0,0 +1,83 @@ +package storage + +import ( + "context" + "fmt" + "io" + "path/filepath" + + "cloud.google.com/go/storage" +) + +// cloudStorage implements storage for Google Cloud Storage. +type cloudStorage struct { + bucketName string + directory string + name string +} + +// save uploads data to Google Cloud Storage. +func (s *cloudStorage) save(data []byte) error { + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + return fmt.Errorf("creating cloud storage client: %w", err) + } + defer client.Close() + + bucket := client.Bucket(s.bucketName) + object := bucket.Object(filepath.Join(s.directory, s.name)) + wc := object.NewWriter(ctx) + + if _, err = wc.Write(data); err != nil { + return fmt.Errorf("writing to cloud storage: %w", err) + } + + if err := wc.Close(); err != nil { + return fmt.Errorf("closing cloud storage writer: %w", err) + } + + return nil +} + +// load downloads data from Cloud. +func (s *cloudStorage) load() ([]byte, error) { + ctx := context.Background() + client, err := storage.NewClient(ctx) + + if err != nil { + return nil, fmt.Errorf("creating cloud storage client: %w", err) + } + defer client.Close() + + bucket := client.Bucket(s.bucketName) + object := bucket.Object(filepath.Join(s.directory, s.name)) + reader, err := object.NewReader(ctx) + + if err != nil { + return nil, fmt.Errorf("creating cloud storage reader: %w", err) + } + defer reader.Close() + + data, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("reading from cloud storage: %w", err) + } + + return data, nil +} + +// remove deletes the object from Cloud. +func (s *cloudStorage) remove() error { + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + return fmt.Errorf("creating cloud storage client: %w", err) + } + defer client.Close() + + bucket := client.Bucket(s.bucketName) + object := bucket.Object(filepath.Join(s.directory, s.name)) + + return object.Delete(ctx) +} diff --git a/framework/storage/filesystem.go b/framework/storage/filesystem.go new file mode 100644 index 0000000..eefaf05 --- /dev/null +++ b/framework/storage/filesystem.go @@ -0,0 +1,40 @@ +package storage + +import ( + "os" + "path/filepath" +) + +// filesystemStorage implements storage for the filesystem. +type filesystemStorage struct { + basePath string + name string +} + +// save writes data to a file. +func (s *filesystemStorage) save(data []byte) error { + fullPath := filepath.Join(s.basePath, s.name) + + return os.WriteFile(fullPath, data, 0644) +} + +// load reads data from a file. +func (s *filesystemStorage) load() ([]byte, error) { + fullPath := filepath.Join(s.basePath, s.name) + + if _, err := os.Stat(fullPath); err != nil { + if os.IsNotExist(err) { + return []byte("[]"), nil + } + return nil, err + } + + return os.ReadFile(fullPath) +} + +// remove deletes the file from the filesystem. +func (s *filesystemStorage) remove() error { + fullPath := filepath.Join(s.basePath, s.name) + + return os.Remove(fullPath) +} diff --git a/framework/storage/storage.go b/framework/storage/storage.go new file mode 100644 index 0000000..5b8f554 --- /dev/null +++ b/framework/storage/storage.go @@ -0,0 +1,68 @@ +package storage + +// StorageType represents the type of storage to use. +type StorageType string + +// Constants for different storage types. +const ( + StorageTypeFilesystem StorageType = "filesystem" + StorageTypeCloud StorageType = "cloud" +) + +// persistence defines the interface for saving and loading data. +type persistence interface { + save(data []byte) error + load() ([]byte, error) + remove() error +} + +// Storage represents a storage object with configurable parameters. +type Storage struct { + Type StorageType + Resource string // e.g. BucketName, DB Name + Location string // e.g. Directory + Name string // e.g. Filename, Table Name +} + +// NewStorage creates a new Storage instance with default values. +func NewStorage(resource, location, name string) *Storage { + return &Storage{ + Type: StorageTypeFilesystem, + Resource: resource, + Location: location, + Name: name, + } +} + +// Save stores data using the configured storage mechanism. +func (s *Storage) Save(content []byte) error { + storage := s.getStorage() + + return storage.save(content) +} + +// Load retrieves data using the configured storage mechanism. +func (s *Storage) Load() ([]byte, error) { + storage := s.getStorage() + + return storage.load() +} + +// Remove deletes data using the configured storage mechanism. +func (s *Storage) Remove() error { + storage := s.getStorage() + + return storage.remove() +} + +// getStorage returns the appropriate storage implementation based on the provided type. +func (s *Storage) getStorage() persistence { + switch s.Type { + case StorageTypeCloud: + return &cloudStorage{bucketName: s.Resource, directory: s.Location, name: s.Name} + case StorageTypeFilesystem: + fallthrough + default: + return &filesystemStorage{basePath: s.Location, name: s.Name} + } +} diff --git a/framework/storage/storage_test.go b/framework/storage/storage_test.go new file mode 100644 index 0000000..20ba066 --- /dev/null +++ b/framework/storage/storage_test.go @@ -0,0 +1,56 @@ +package storage + +import ( + "os" + "testing" +) + +func TestFilesystemStorage_Save(t *testing.T) { + file, err := os.CreateTemp("", "export-*.json") + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + defer os.Remove(file.Name()) + + fs := &filesystemStorage{name: file.Name()} + + testData := []byte("{\"test\":\"data\"}") + + err = fs.save(testData) + if err != nil { + t.Errorf("Save() error = %v; want nil", err) + } + + data, err := os.ReadFile(file.Name()) + if err != nil { + t.Fatalf("Failed to read data from file: %v", err) + } + + if string(data) != string(testData) { + t.Errorf("Saved data does not match original data. Got: %s, Want: %s", data, testData) + } +} + +func TestFilesystemStorage_Remove(t *testing.T) { + file, err := os.CreateTemp("", "export-*.json") + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + defer os.Remove(file.Name()) + + fs := &filesystemStorage{name: file.Name()} + + err = fs.save([]byte("test data")) + if err != nil { + t.Errorf("Save() error = %v; want nil", err) + } + + err = fs.remove() + if err != nil { + t.Errorf("Remove() error = %v; want nil", err) + } + + if _, err := os.Stat(file.Name()); !os.IsNotExist(err) { + t.Errorf("File was not removed: %v", err) + } +} diff --git a/gateway/.env b/gateway/.env new file mode 100644 index 0000000..2714ee7 --- /dev/null +++ b/gateway/.env @@ -0,0 +1,2 @@ +CORS_ALLOW_ORIGIN=http://127.0.0.1,http://localhost +API_CORE=https://yesno.wtf/ diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..5ec272e --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.23 + +WORKDIR /usr/src/app + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . +RUN go build -v -o /usr/local/bin ./... + +EXPOSE 8080 + +CMD ["gateway"] diff --git a/gateway/go.mod b/gateway/go.mod new file mode 100644 index 0000000..9b18ae3 --- /dev/null +++ b/gateway/go.mod @@ -0,0 +1,58 @@ +module github.com/abaldeweg/warehouse-server/gateway + +go 1.23 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/abaldeweg/warehouse-server/framework v0.0.0-20241015163101-08b93c4b15a5 + github.com/joho/godotenv v1.5.1 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/cors v1.7.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // 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/protobuf v1.34.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/gateway/main.go b/gateway/main.go new file mode 100644 index 0000000..dce0701 --- /dev/null +++ b/gateway/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "log" + + "github.com/abaldeweg/warehouse-server/framework/config" + "github.com/abaldeweg/warehouse-server/framework/cors" + "github.com/abaldeweg/warehouse-server/gateway/router" + "github.com/joho/godotenv" + "github.com/spf13/viper" +) + +func main() { + godotenv.Load() + + config.LoadAppConfig() + + viper.SetDefault("CORS_ALLOW_ORIGIN", "*") + + corsConfig := cors.NewCors() + corsConfig.AllowOrigins = []string{viper.GetString("CORS_ALLOW_ORIGIN")} + corsConfig.SetCorsHeaders() + + r := router.Routes() + r.Use(corsConfig.SetCorsHeaders()) + + log.Fatal(r.Run(":8080")) +} diff --git a/gateway/openapi.yaml b/gateway/openapi.yaml new file mode 100644 index 0000000..70fc171 --- /dev/null +++ b/gateway/openapi.yaml @@ -0,0 +1,87 @@ +openapi: 3.0.0 + +info: + title: Proxy + version: v1 + +servers: + - url: http://localhost:8080 + +tags: + - name: proxy + description: Proxy + +paths: + /apis/core/1/{path}: + get: + summary: Proxy GET request + tags: + - proxy + parameters: + - in: path + name: path + schema: + type: string + required: true + description: Path to proxy + responses: + 200: + description: Successful response + 500: + description: Internal server error + 504: + description: Gateway timeout + post: + summary: Proxy POST request + tags: + - proxy + parameters: + - in: path + name: path + schema: + type: string + required: true + description: Path to proxy + responses: + 200: + description: Successful response from API Core + 500: + description: Internal server error + 504: + description: Gateway timeout + put: + summary: Proxy PUT request + tags: + - proxy + parameters: + - in: path + name: path + schema: + type: string + required: true + description: Path to proxy + responses: + 200: + description: Successful response + 500: + description: Internal server error + 504: + description: Gateway timeout + delete: + summary: Proxy DELETE request + tags: + - proxy + parameters: + - in: path + name: path + schema: + type: string + required: true + description: Path to proxy + responses: + 200: + description: Successful response from API Core + 500: + description: Internal server error + 504: + description: Gateway timeout diff --git a/gateway/proxy/proxy.go b/gateway/proxy/proxy.go new file mode 100644 index 0000000..dced4ba --- /dev/null +++ b/gateway/proxy/proxy.go @@ -0,0 +1,78 @@ +package proxy + +import ( + "context" + "io" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +const duration = 20 * time.Second + +func Proxy(c *gin.Context, serviceURL string, path string) error { + ctx, cancel := context.WithTimeout(c.Request.Context(), duration) + defer cancel() + + req, err := request(ctx, c, serviceURL, path) + if err != nil { + return err + } + + return response(req, c) +} + +func request(ctx context.Context, c *gin.Context, serviceURL string, path string) (*http.Request, error) { + fullURL := serviceURL + path + "?" + c.Request.URL.RawQuery + + req, err := http.NewRequestWithContext(ctx, c.Request.Method, fullURL, c.Request.Body) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": "Internal Error"}) + return nil, err + } + + for key, values := range c.Request.Header { + // Forward all headers EXCEPT Content-Type + if key != "Content-Type" { + for _, value := range values { + req.Header.Add(key, value) + } + } + } + + return req, nil +} + + +func response(req *http.Request, c *gin.Context) error { + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + if err == context.DeadlineExceeded { + c.JSON(http.StatusGatewayTimeout, gin.H{"error": "Request timed out"}) + } else { + c.JSON(http.StatusBadGateway, gin.H{"msg": "Bad Gateway"}) + } + return err + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + c.JSON(http.StatusBadGateway, gin.H{"msg": "Bad Gateway"}) + return err + } + + for key, value := range resp.Header { + c.Header(key, value[0]) + } + + c.Header("Content-Length", resp.Header.Get("Content-Length")) + + c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), body) + + return nil +} diff --git a/gateway/proxy/proxy_test.go b/gateway/proxy/proxy_test.go new file mode 100644 index 0000000..36020a3 --- /dev/null +++ b/gateway/proxy/proxy_test.go @@ -0,0 +1,48 @@ +package proxy + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestProxy(t *testing.T) { + mockService := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("TEST")) + }), + ) + defer mockService.Close() + + gin.SetMode(gin.TestMode) + + router := gin.New() + router.GET("/test", func(c *gin.Context) { + err := Proxy(c, mockService.URL, "/test") + if err != nil { + t.Fatal(err) + } + }) + + req, err := http.NewRequest(http.MethodGet, "/test", nil) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + body, err := io.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "TEST", string(body)) +} diff --git a/gateway/router/router.go b/gateway/router/router.go new file mode 100644 index 0000000..46e57ba --- /dev/null +++ b/gateway/router/router.go @@ -0,0 +1,28 @@ +package router + +import ( + "net/http" + "path/filepath" + + "github.com/abaldeweg/warehouse-server/gateway/proxy" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" +) + +func Routes() *gin.Engine { + gin.SetMode(gin.ReleaseMode) + r := gin.Default() + + r.Any(`/apis/core/1/*path`, func(c *gin.Context) { + path := c.Param("path") + + safePath := filepath.Join("/", path) + + if err := proxy.Proxy(c, viper.GetString("API_CORE"), safePath); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"msg": "Internal Error"}) + return + } + }) + + return r +} diff --git a/go.work b/go.work new file mode 100644 index 0000000..fc8d5ec --- /dev/null +++ b/go.work @@ -0,0 +1,8 @@ +go 1.23 + +use ( + ./admincli + ./blog + ./framework + ./gateway +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..57b5410 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,384 @@ +cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= +cloud.google.com/go/accessapproval v1.8.0/go.mod h1:ycc7qSIXOrH6gGOGQsuBwpRZw3QhZLi0OWeej3rA5Mg= +cloud.google.com/go/accesscontextmanager v1.9.0/go.mod h1:EmdQRGq5FHLrjGjGTp2X2tlRBvU3LDCUqfnysFYooxQ= +cloud.google.com/go/aiplatform v1.68.0/go.mod h1:105MFA3svHjC3Oazl7yjXAmIR89LKhRAeNdnDKJczME= +cloud.google.com/go/analytics v0.25.0/go.mod h1:LZMfjJnKU1GDkvJV16dKnXm7KJJaMZfvUXx58ujgVLg= +cloud.google.com/go/apigateway v1.7.0/go.mod h1:miZGNhmrC+SFhxjA7ayjKHk1cA+7vsSINp9K+JxKwZI= +cloud.google.com/go/apigeeconnect v1.7.0/go.mod h1:fd8NFqzu5aXGEUpxiyeCyb4LBLU7B/xIPztfBQi+1zg= +cloud.google.com/go/apigeeregistry v0.9.0/go.mod h1:4S/btGnijdt9LSIZwBDHgtYfYkFGekzNyWkyYTP8Qzs= +cloud.google.com/go/appengine v1.9.0/go.mod h1:y5oI+JT3/6s77QmxbTnLHyiMKz3NPHYOjuhmVi+FyYU= +cloud.google.com/go/area120 v0.9.0/go.mod h1:ujIhRz2gJXutmFYGAUgz3KZ5IRJ6vOwL4CYlNy/jDo4= +cloud.google.com/go/artifactregistry v1.15.0/go.mod h1:4xrfigx32/3N7Pp7YSPOZZGs4VPhyYeRyJ67ZfVdOX4= +cloud.google.com/go/asset v1.20.0/go.mod h1:CT3ME6xNZKsPSvi0lMBPgW3azvRhiurJTFSnNl6ahw8= +cloud.google.com/go/assuredworkloads v1.12.0/go.mod h1:jX84R+0iANggmSbzvVgrGWaqdhRsQihAv4fF7IQ4r7Q= +cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/automl v1.14.0/go.mod h1:Kr7rN9ANSjlHyBLGvwhrnt35/vVZy3n/CP4Xmyj0shM= +cloud.google.com/go/baremetalsolution v1.3.0/go.mod h1:E+n44UaDVO5EeSa4SUsDFxQLt6dD1CoE2h+mtxxaJKo= +cloud.google.com/go/batch v1.10.0/go.mod h1:JlktZqyKbcUJWdHOV8juvAiQNH8xXHXTqLp6bD9qreE= +cloud.google.com/go/beyondcorp v1.1.0/go.mod h1:F6Rl20QbayaloWIsMhuz+DICcJxckdFKc7R2HCe6iNA= +cloud.google.com/go/bigquery v1.62.0/go.mod h1:5ee+ZkF1x/ntgCsFQJAQTM3QkAZOecfCmvxhkJsWRSA= +cloud.google.com/go/bigtable v1.31.0/go.mod h1:N/mwZO+4TSHOeyiE1JxO+sRPnW4bnR7WLn9AEaiJqew= +cloud.google.com/go/billing v1.19.0/go.mod h1:bGvChbZguyaWRGmu5pQHfFN1VxTDPFmabnCVA/dNdRM= +cloud.google.com/go/binaryauthorization v1.9.0/go.mod h1:fssQuxfI9D6dPPqfvDmObof+ZBKsxA9iSigd8aSA1ik= +cloud.google.com/go/certificatemanager v1.9.0/go.mod h1:hQBpwtKNjUq+er6Rdg675N7lSsNGqMgt7Bt7Dbcm7d0= +cloud.google.com/go/channel v1.18.0/go.mod h1:gQr50HxC/FGvufmqXD631ldL1Ee7CNMU5F4pDyJWlt0= +cloud.google.com/go/cloudbuild v1.17.0/go.mod h1:/RbwgDlbQEwIKoWLIYnW72W3cWs+e83z7nU45xRKnj8= +cloud.google.com/go/clouddms v1.8.0/go.mod h1:JUgTgqd1M9iPa7p3jodjLTuecdkGTcikrg7nz++XB5E= +cloud.google.com/go/cloudtasks v1.13.0/go.mod h1:O1jFRGb1Vm3sN2u/tBdPiVGVTWIsrsbEs3K3N3nNlEU= +cloud.google.com/go/compute v1.28.0/go.mod h1:DEqZBtYrDnD5PvjsKwb3onnhX+qjdCVM7eshj1XdjV4= +cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/contactcenterinsights v1.14.0/go.mod h1:APmWYHDN4sASnUBnXs4o68t1EUfnqadA53//CzXZ1xE= +cloud.google.com/go/container v1.39.0/go.mod h1:gNgnvs1cRHXjYxrotVm+0nxDfZkqzBbXCffh5WtqieI= +cloud.google.com/go/containeranalysis v0.13.0/go.mod h1:OpufGxsNzMOZb6w5yqwUgHr5GHivsAD18KEI06yGkQs= +cloud.google.com/go/datacatalog v1.22.0/go.mod h1:4Wff6GphTY6guF5WphrD76jOdfBiflDiRGFAxq7t//I= +cloud.google.com/go/dataflow v0.10.0/go.mod h1:zAv3YUNe/2pXWKDSPvbf31mCIUuJa+IHtKmhfzaeGww= +cloud.google.com/go/dataform v0.10.0/go.mod h1:0NKefI6v1ppBEDnwrp6gOMEA3s/RH3ypLUM0+YWqh6A= +cloud.google.com/go/datafusion v1.8.0/go.mod h1:zHZ5dJYHhMP1P8SZDZm+6yRY9BCCcfm7Xg7YmP+iA6E= +cloud.google.com/go/datalabeling v0.9.0/go.mod h1:GVX4sW4cY5OPKu/9v6dv20AU9xmGr4DXR6K26qN0mzw= +cloud.google.com/go/dataplex v1.19.0/go.mod h1:5H9ftGuZWMtoEIUpTdGUtGgje36YGmtRXoC8wx6QSUc= +cloud.google.com/go/dataproc/v2 v2.6.0/go.mod h1:amsKInI+TU4GcXnz+gmmApYbiYM4Fw051SIMDoWCWeE= +cloud.google.com/go/dataqna v0.9.0/go.mod h1:WlRhvLLZv7TfpONlb/rEQx5Qrr7b5sxgSuz5NP6amrw= +cloud.google.com/go/datastore v1.19.0/go.mod h1:KGzkszuj87VT8tJe67GuB+qLolfsOt6bZq/KFuWaahc= +cloud.google.com/go/datastream v1.11.0/go.mod h1:vio/5TQ0qNtGcIj7sFb0gucFoqZW19gZ7HztYtkzq9g= +cloud.google.com/go/deploy v1.22.0/go.mod h1:qXJgBcnyetoOe+w/79sCC99c5PpHJsgUXCNhwMjG0e4= +cloud.google.com/go/dialogflow v1.57.0/go.mod h1:wegtnocuYEfue6IGlX96n5mHu3JGZUaZxv1L5HzJUJY= +cloud.google.com/go/dlp v1.18.0/go.mod h1:RVO9zkh+xXgUa7+YOf9IFNHL/2FXt9Vnv/GKNYmc1fE= +cloud.google.com/go/documentai v1.33.0/go.mod h1:lI9Mti9COZ5qVjdpfDZxNjOrTVf6tJ//vaqbtt81214= +cloud.google.com/go/domains v0.10.0/go.mod h1:VpPXnkCNRsxkieDFDfjBIrLv3p1kRjJ03wLoPeL30To= +cloud.google.com/go/edgecontainer v1.3.0/go.mod h1:dV1qTl2KAnQOYG+7plYr53KSq/37aga5/xPgOlYXh3A= +cloud.google.com/go/errorreporting v0.3.1/go.mod h1:6xVQXU1UuntfAf+bVkFk6nld41+CPyF2NSPCyXE3Ztk= +cloud.google.com/go/essentialcontacts v1.7.0/go.mod h1:0JEcNuyjyg43H/RJynZzv2eo6MkmnvRPUouBpOh6akY= +cloud.google.com/go/eventarc v1.14.0/go.mod h1:60ZzZfOekvsc/keHc7uGHcoEOMVa+p+ZgRmTjpdamnA= +cloud.google.com/go/filestore v1.9.0/go.mod h1:GlQK+VBaAGb19HqprnOMqYYpn7Gev5ZA9SSHpxFKD7Q= +cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= +cloud.google.com/go/functions v1.19.0/go.mod h1:WDreEDZoUVoOkXKDejFWGnprrGYn2cY2KHx73UQERC0= +cloud.google.com/go/gkebackup v1.6.0/go.mod h1:1rskt7NgawoMDHTdLASX8caXXYG3MvDsoZ7qF4RMamQ= +cloud.google.com/go/gkeconnect v0.11.0/go.mod h1:l3iPZl1OfT+DUQ+QkmH1PC5RTLqxKQSVnboLiQGAcCA= +cloud.google.com/go/gkehub v0.15.0/go.mod h1:obpeROly2mjxZJbRkFfHEflcH54XhJI+g2QgfHphL0I= +cloud.google.com/go/gkemulticloud v1.3.0/go.mod h1:XmcOUQ+hJI62fi/klCjEGs6lhQ56Zjs14sGPXsGP0mE= +cloud.google.com/go/gsuiteaddons v1.7.0/go.mod h1:/B1L8ANPbiSvxCgdSwqH9CqHIJBzTt6v50fPr3vJCtg= +cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/iap v1.10.0/go.mod h1:gDT6LZnKnWNCaov/iQbj7NMUpknFDOkhhlH8PwIrpzU= +cloud.google.com/go/ids v1.5.0/go.mod h1:4NOlC1m9hAJL50j2cRV4PS/J6x/f4BBM0Xg54JQLCWw= +cloud.google.com/go/iot v1.8.0/go.mod h1:/NMFENPnQ2t1UByUC1qFvA80fo1KFB920BlyUPn1m3s= +cloud.google.com/go/kms v1.19.0/go.mod h1:e4imokuPJUc17Trz2s6lEXFDt8bgDmvpVynH39bdrHM= +cloud.google.com/go/language v1.14.0/go.mod h1:ldEdlZOFwZREnn/1yWtXdNzfD7hHi9rf87YDkOY9at4= +cloud.google.com/go/lifesciences v0.10.0/go.mod h1:1zMhgXQ7LbMbA5n4AYguFgbulbounfUoYvkV8dtsLcA= +cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= +cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= +cloud.google.com/go/managedidentities v1.7.0/go.mod h1:o4LqQkQvJ9Pt7Q8CyZV39HrzCfzyX8zBzm8KIhRw91E= +cloud.google.com/go/maps v1.12.0/go.mod h1:qjErDNStn3BaGx06vHner5d75MRMgGflbgCuWTuslMc= +cloud.google.com/go/mediatranslation v0.9.0/go.mod h1:udnxo0i4YJ5mZfkwvvQQrQ6ra47vcX8jeGV+6I5x+iU= +cloud.google.com/go/memcache v1.11.0/go.mod h1:99MVF02m5TByT1NKxsoKDnw5kYmMrjbGSeikdyfCYZk= +cloud.google.com/go/metastore v1.14.0/go.mod h1:vtPt5oVF/+ocXO4rv4GUzC8Si5s8gfmo5OIt6bACDuE= +cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4= +cloud.google.com/go/networkconnectivity v1.15.0/go.mod h1:uBQqx/YHI6gzqfV5J/7fkKwTGlXvQhHevUuzMpos9WY= +cloud.google.com/go/networkmanagement v1.14.0/go.mod h1:4myfd4A0uULCOCGHL1npZN0U+kr1Z2ENlbHdCCX4cE8= +cloud.google.com/go/networksecurity v0.10.0/go.mod h1:IcpI5pyzlZyYG8cNRCJmY1AYKajsd9Uz575HoeyYoII= +cloud.google.com/go/notebooks v1.12.0/go.mod h1:euIZBbGY6G0J+UHzQ0XflysP0YoAUnDPZU7Fq0KXNw8= +cloud.google.com/go/optimization v1.7.0/go.mod h1:6KvAB1HtlsMMblT/lsQRIlLjUhKjmMWNqV1AJUctbWs= +cloud.google.com/go/orchestration v1.10.0/go.mod h1:pGiFgTTU6c/nXHTPpfsGT8N4Dax8awccCe6kjhVdWjI= +cloud.google.com/go/orgpolicy v1.13.0/go.mod h1:oKtT56zEFSsYORUunkN2mWVQBc9WGP7yBAPOZW1XCXc= +cloud.google.com/go/osconfig v1.14.0/go.mod h1:GhZzWYVrnQ42r+K5pA/hJCsnWVW2lB6bmVg+GnZ6JkM= +cloud.google.com/go/oslogin v1.14.0/go.mod h1:VtMzdQPRP3T+w5OSFiYhaT/xOm7H1wo1HZUD2NAoVK4= +cloud.google.com/go/phishingprotection v0.9.0/go.mod h1:CzttceTk9UskH9a8BycYmHL64zakEt3EXaM53r4i0Iw= +cloud.google.com/go/policytroubleshooter v1.11.0/go.mod h1:yTqY8n60lPLdU5bRbImn9IazrmF1o5b0VBshVxPzblQ= +cloud.google.com/go/privatecatalog v0.10.0/go.mod h1:/Lci3oPTxJpixjiTBoiVv3PmUZg/IdhPvKHcLEgObuc= +cloud.google.com/go/pubsub v1.42.0/go.mod h1:KADJ6s4MbTwhXmse/50SebEhE4SmUwHi48z3/dHar1Y= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= +cloud.google.com/go/recaptchaenterprise/v2 v2.17.0/go.mod h1:SS4QDdlmJ3NvbOMCXQxaFhVGRjvNMfoKCoCdxqXadqs= +cloud.google.com/go/recommendationengine v0.9.0/go.mod h1:59ydKXFyXO4Y8S0Bk224sKfj6YvIyzgcpG6w8kXIMm4= +cloud.google.com/go/recommender v1.13.0/go.mod h1:+XkXkeB9k6zG222ZH70U6DBkmvEL0na+pSjZRmlWcrk= +cloud.google.com/go/redis v1.17.0/go.mod h1:pzTdaIhriMLiXu8nn2CgiS52SYko0tO1Du4d3MPOG5I= +cloud.google.com/go/resourcemanager v1.10.0/go.mod h1:kIx3TWDCjLnUQUdjQ/e8EXsS9GJEzvcY+YMOHpADxrk= +cloud.google.com/go/resourcesettings v1.8.0/go.mod h1:/hleuSOq8E6mF1sRYZrSzib8BxFHprQXrPluWTuZ6Ys= +cloud.google.com/go/retail v1.18.0/go.mod h1:vaCabihbSrq88mKGKcKc4/FDHvVcPP0sQDAt0INM+v8= +cloud.google.com/go/run v1.5.0/go.mod h1:Z4Tv/XNC/veO6rEpF0waVhR7vEu5RN1uJQ8dD1PeMtI= +cloud.google.com/go/scheduler v1.11.0/go.mod h1:RBSu5/rIsF5mDbQUiruvIE6FnfKpLd3HlTDu8aWk0jw= +cloud.google.com/go/secretmanager v1.14.0/go.mod h1:q0hSFHzoW7eRgyYFH8trqEFavgrMeiJI4FETNN78vhM= +cloud.google.com/go/security v1.18.0/go.mod h1:oS/kRVUNmkwEqzCgSmK2EaGd8SbDUvliEiADjSb/8Mo= +cloud.google.com/go/securitycenter v1.35.0/go.mod h1:gotw8mBfCxX0CGrRK917CP/l+Z+QoDchJ9HDpSR8eDc= +cloud.google.com/go/servicedirectory v1.12.0/go.mod h1:lKKBoVStJa+8S+iH7h/YRBMUkkqFjfPirkOTEyYAIUk= +cloud.google.com/go/shell v1.8.0/go.mod h1:EoQR8uXuEWHUAMoB4+ijXqRVYatDCdKYOLAaay1R/yw= +cloud.google.com/go/spanner v1.67.0/go.mod h1:Um+TNmxfcCHqNCKid4rmAMvoe/Iu1vdz6UfxJ9GPxRQ= +cloud.google.com/go/speech v1.25.0/go.mod h1:2IUTYClcJhqPgee5Ko+qJqq29/bglVizgIap0c5MvYs= +cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= +cloud.google.com/go/storagetransfer v1.11.0/go.mod h1:arcvgzVC4HPcSikqV8D4h4PwrvGQHfKtbL4OwKPirjs= +cloud.google.com/go/talent v1.7.0/go.mod h1:8zfRPWWV4GNZuUmBwQub0gWAe2KaKhsthyGtV8fV1bY= +cloud.google.com/go/texttospeech v1.8.0/go.mod h1:hAgeA01K5QNfLy2sPUAVETE0L4WdEpaCMfwKH1qjCQU= +cloud.google.com/go/tpu v1.7.0/go.mod h1:/J6Co458YHMD60nM3cCjA0msvFU/miCGMfx/nYyxv/o= +cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= +cloud.google.com/go/translate v1.12.0/go.mod h1:4/C4shFIY5hSZ3b3g+xXWM5xhBLqcUqksSMrQ7tyFtc= +cloud.google.com/go/video v1.23.0/go.mod h1:EGLQv3Ce/VNqcl/+Amq7jlrnpg+KMgQcr6YOOBfE9oc= +cloud.google.com/go/videointelligence v1.12.0/go.mod h1:3rjmafNpCEqAb1CElGTA7dsg8dFDsx7RQNHS7o088D0= +cloud.google.com/go/vision/v2 v2.9.0/go.mod h1:sejxShqNOEucObbGNV5Gk85hPCgiVPP4sWv0GrgKuNw= +cloud.google.com/go/vmmigration v1.8.0/go.mod h1:+AQnGUabjpYKnkfdXJZ5nteUfzNDCmwbj/HSLGPFG5E= +cloud.google.com/go/vmwareengine v1.3.0/go.mod h1:7W/C/YFpelGyZzRUfOYkbgUfbN1CK5ME3++doIkh1Vk= +cloud.google.com/go/vpcaccess v1.8.0/go.mod h1:7fz79sxE9DbGm9dbbIdir3tsJhwCxiNAs8aFG8MEhR8= +cloud.google.com/go/webrisk v1.10.0/go.mod h1:ztRr0MCLtksoeSOQCEERZXdzwJGoH+RGYQ2qodGOy2U= +cloud.google.com/go/websecurityscanner v1.7.0/go.mod h1:d5OGdHnbky9MAZ8SGzdWIm3/c9p0r7t+5BerY5JYdZc= +cloud.google.com/go/workflows v1.13.0/go.mod h1:StCuY3jhBj1HYMjCPqZs7J0deQLHPhF6hDtzWJaVF+Y= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/incwadi-warehouse/monorepo-go/framework v0.0.0-20241015163101-08b93c4b15a5/go.mod h1:xX9FQyY8/hBQ+7RMI9rlLu1eozuu7amRiZfA7f5H8y8= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +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/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:q0eWNnCW04EJlyrmLT+ZHsjuoUiZ36/eAEdCCezZoco= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..13a89f0 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "config:base", + ":disableDependencyDashboard" + ] +}