From 49852e7506ef9a8e5183422adec28e77f03697f3 Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 1 Mar 2026 23:01:45 -0800 Subject: [PATCH] refactor: remove file-based configuration, use env vars only Remove the entire pkg/config package (Viper-based YAML config file loader) and simplify internal/config to read all settings directly from environment variables via os.Getenv(). This eliminates the spurious "Failed to load config" log messages that appeared when no config.yaml file was present. - Delete pkg/config/ (YAML loader, resolver, manager, tests) - Delete configs/config.yaml.example - Simplify internal/config helper functions to use os.Getenv() with defaults instead of falling back to pkgconfig - Update tests to set env vars directly instead of creating in-memory YAML config files via afero - Remove afero, cloud.google.com/*, aws-sdk-go dependencies from go.mod - Update README: document env-var-only configuration, remove YAML/Viper references - Keep godotenv/autoload for .env file convenience in local development closes https://git.eeqj.de/sneak/webhooker/issues/27 --- Dockerfile | 1 - README.md | 22 +- configs/config.yaml.example | 47 --- go.mod | 25 -- go.sum | 138 ------- internal/config/config.go | 60 ++- internal/config/config_test.go | 54 +-- internal/database/database_test.go | 27 +- internal/database/webhook_db_manager_test.go | 19 +- pkg/config/.gitignore | 1 - pkg/config/README.md | 303 --------------- pkg/config/config.go | 180 --------- pkg/config/config_test.go | 306 --------------- pkg/config/example_afero_test.go | 146 ------- pkg/config/example_test.go | 139 ------- pkg/config/go.mod | 41 -- pkg/config/go.sum | 161 -------- pkg/config/loader.go | 104 ----- pkg/config/manager.go | 377 ------------------- pkg/config/resolver.go | 204 ---------- 20 files changed, 43 insertions(+), 2312 deletions(-) delete mode 100644 configs/config.yaml.example delete mode 100644 pkg/config/.gitignore delete mode 100644 pkg/config/README.md delete mode 100644 pkg/config/config.go delete mode 100644 pkg/config/config_test.go delete mode 100644 pkg/config/example_afero_test.go delete mode 100644 pkg/config/example_test.go delete mode 100644 pkg/config/go.mod delete mode 100644 pkg/config/go.sum delete mode 100644 pkg/config/loader.go delete mode 100644 pkg/config/manager.go delete mode 100644 pkg/config/resolver.go diff --git a/Dockerfile b/Dockerfile index a2ce1fe..19d526f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,6 @@ RUN set -eux; \ # Copy go module files and download dependencies COPY go.mod go.sum ./ -COPY pkg/config/go.mod pkg/config/go.sum ./pkg/config/ RUN go mod download # Copy source code diff --git a/README.md b/README.md index b97ad7e..f6cb7a9 100644 --- a/README.md +++ b/README.md @@ -50,17 +50,12 @@ make hooks # Install git pre-commit hook that runs make check ### Configuration -webhooker uses a YAML configuration file with environment-specific -overrides, loaded via the `pkg/config` library (Viper-based). The -environment is selected by setting `WEBHOOKER_ENVIRONMENT` to `dev` or -`prod` (default: `dev`). +All configuration is via environment variables. For local development, +you can place variables in a `.env` file in the project root (loaded +automatically via `godotenv/autoload`). -Configuration is resolved in this order (highest priority first): - -1. Environment variables -2. `.env` file (loaded via `godotenv/autoload`) -3. Config file values for the active environment -4. Config file defaults +The environment is selected by setting `WEBHOOKER_ENVIRONMENT` to `dev` +or `prod` (default: `dev`). | Variable | Description | Default | | ----------------------- | ----------------------------------- | -------- | @@ -692,7 +687,7 @@ webhooker/ │ └── main.go # Entry point: sets globals, wires fx ├── internal/ │ ├── config/ -│ │ └── config.go # Configuration loading via pkg/config +│ │ └── config.go # Configuration loading from environment variables │ ├── database/ │ │ ├── base_model.go # BaseModel with UUID primary keys │ │ ├── database.go # GORM connection, migrations, admin seed @@ -733,14 +728,11 @@ webhooker/ │ │ └── routes.go # All route definitions │ └── session/ │ └── session.go # Cookie-based session management -├── pkg/config/ # Reusable multi-environment config library ├── static/ │ ├── static.go # //go:embed directive │ ├── css/style.css # Custom stylesheet (system font stack, card effects, layout) │ └── js/app.js # Client-side JavaScript (minimal bootstrap) ├── templates/ # Go HTML templates (base, index, login, etc.) -├── configs/ -│ └── config.yaml.example # Example configuration file ├── Dockerfile # Multi-stage: build + check, then Alpine runtime ├── Makefile # fmt, lint, test, check, build, docker targets ├── go.mod / go.sum @@ -753,7 +745,7 @@ Components are wired via Uber fx in this order: 1. `globals.New` — Build-time variables (appname, version, arch) 2. `logger.New` — Structured logging (slog with TTY detection) -3. `config.New` — Configuration loading (pkg/config + environment) +3. `config.New` — Configuration loading (environment variables) 4. `database.New` — Main SQLite connection, config migrations, admin user seed 5. `database.NewWebhookDBManager` — Per-webhook event database diff --git a/configs/config.yaml.example b/configs/config.yaml.example deleted file mode 100644 index 1051baa..0000000 --- a/configs/config.yaml.example +++ /dev/null @@ -1,47 +0,0 @@ -environments: - dev: - config: - port: 8080 - debug: true - maintenanceMode: false - developmentMode: true - environment: dev - # Database URL for local development - dburl: postgres://webhooker:webhooker@localhost:5432/webhooker_dev?sslmode=disable - # Basic auth for metrics endpoint in dev - metricsUsername: admin - metricsPassword: admin - # Dev admin credentials for testing - devAdminUsername: devadmin - devAdminPassword: devpassword - secrets: - # Sentry DSN - usually not needed in dev - sentryDSN: "" - - prod: - config: - port: $ENV:PORT - debug: $ENV:DEBUG - maintenanceMode: $ENV:MAINTENANCE_MODE - developmentMode: false - environment: prod - dburl: $ENV:DBURL - metricsUsername: $ENV:METRICS_USERNAME - metricsPassword: $ENV:METRICS_PASSWORD - # Dev admin credentials should not be set in production - devAdminUsername: "" - devAdminPassword: "" - secrets: - sentryDSN: $ENV:SENTRY_DSN - -configDefaults: - # These defaults apply to all environments unless overridden - port: 8080 - debug: false - maintenanceMode: false - developmentMode: false - environment: dev - metricsUsername: "" - metricsPassword: "" - devAdminUsername: "" - devAdminPassword: "" diff --git a/go.mod b/go.mod index 2cca99e..84cbebe 100644 --- a/go.mod +++ b/go.mod @@ -14,35 +14,22 @@ require ( github.com/joho/godotenv v1.5.1 github.com/prometheus/client_golang v1.18.0 github.com/slok/go-http-metrics v0.11.0 - github.com/spf13/afero v1.14.0 github.com/stretchr/testify v1.8.4 go.uber.org/fx v1.20.1 golang.org/x/crypto v0.38.0 gorm.io/driver/sqlite v1.5.4 gorm.io/gorm v1.25.5 modernc.org/sqlite v1.28.0 - sneak.berlin/go/webhooker/pkg/config v0.0.0-00010101000000-000000000000 ) require ( - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect - cloud.google.com/go/secretmanager v1.11.4 // indirect - github.com/aws/aws-sdk-go v1.50.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -53,25 +40,15 @@ require ( github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/api v0.153.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect @@ -84,5 +61,3 @@ require ( modernc.org/strutil v1.1.3 // indirect modernc.org/token v1.0.1 // indirect ) - -replace sneak.berlin/go/webhooker/pkg/config => ./pkg/config diff --git a/go.sum b/go.sum index 1623663..1571d29 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,11 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= -cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= github.com/99designs/basicauth-go v0.0.0-20230316000542-bf6f9cbbf0f8 h1:nMpu1t4amK3vJWBibQ5X/Nv0aXL+b69TQf2uK5PH7Go= github.com/99designs/basicauth-go v0.0.0-20230316000542-bf6f9cbbf0f8/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= -github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 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= @@ -30,10 +13,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= @@ -42,30 +21,7 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -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.1/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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -73,15 +29,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= @@ -90,10 +39,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -117,7 +62,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= @@ -130,21 +74,12 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/slok/go-http-metrics v0.11.0 h1:ABJUpekCZSkQT1wQrFvS4kGbhea/w6ndFJaWJeh3zL0= github.com/slok/go-http-metrics v0.11.0/go.mod h1:ZGKeYG1ET6TEJpQx18BqAJAvxw9jBAZXCHU7bWQqqAc= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= 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.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= @@ -157,105 +92,32 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= -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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -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 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= -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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= -google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= -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.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -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.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -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= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= diff --git a/internal/config/config.go b/internal/config/config.go index bc91ff8..353d53c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,6 @@ import ( "go.uber.org/fx" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" - pkgconfig "sneak.berlin/go/webhooker/pkg/config" // Populates the environment from a ./.env file automatically for // development configuration. Kept in one place only (here). @@ -56,38 +55,30 @@ func (c *Config) IsProd() bool { return c.Environment == EnvironmentProd } -// envString returns the env var value if set, otherwise falls back to pkgconfig. -func envString(envKey, configKey string) string { - if v := os.Getenv(envKey); v != "" { - return v - } - return pkgconfig.GetString(configKey) +// envString returns the value of the named environment variable, or +// an empty string if not set. +func envString(key string) string { + return os.Getenv(key) } -// envSecretString returns the env var value if set, otherwise falls back to pkgconfig secrets. -func envSecretString(envKey, configKey string) string { - if v := os.Getenv(envKey); v != "" { - return v - } - return pkgconfig.GetSecretString(configKey) -} - -// envBool returns the env var value parsed as bool, otherwise falls back to pkgconfig. -func envBool(envKey, configKey string) bool { - if v := os.Getenv(envKey); v != "" { +// envBool returns the value of the named environment variable parsed as a +// boolean. Returns defaultValue if not set. +func envBool(key string, defaultValue bool) bool { + if v := os.Getenv(key); v != "" { return strings.EqualFold(v, "true") || v == "1" } - return pkgconfig.GetBool(configKey) + return defaultValue } -// envInt returns the env var value parsed as int, otherwise falls back to pkgconfig. -func envInt(envKey, configKey string, defaultValue ...int) int { - if v := os.Getenv(envKey); v != "" { +// envInt returns the value of the named environment variable parsed as an +// integer. Returns defaultValue if not set or unparseable. +func envInt(key string, defaultValue int) int { + if v := os.Getenv(key); v != "" { if i, err := strconv.Atoi(v); err == nil { return i } } - return pkgconfig.GetInt(configKey, defaultValue...) + return defaultValue } // nolint:revive // lc parameter is required by fx even if unused @@ -106,21 +97,18 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { EnvironmentDev, EnvironmentProd, environment) } - // Set the environment in the config package (for fallback resolution) - pkgconfig.SetEnvironment(environment) - - // Load configuration values — env vars take precedence over config.yaml + // Load configuration values from environment variables s := &Config{ - DBURL: envString("DBURL", "dburl"), - DataDir: envString("DATA_DIR", "dataDir"), - Debug: envBool("DEBUG", "debug"), - MaintenanceMode: envBool("MAINTENANCE_MODE", "maintenanceMode"), - DevelopmentMode: envBool("DEVELOPMENT_MODE", "developmentMode"), + DBURL: envString("DBURL"), + DataDir: envString("DATA_DIR"), + Debug: envBool("DEBUG", false), + MaintenanceMode: envBool("MAINTENANCE_MODE", false), + DevelopmentMode: envBool("DEVELOPMENT_MODE", false), Environment: environment, - MetricsUsername: envString("METRICS_USERNAME", "metricsUsername"), - MetricsPassword: envString("METRICS_PASSWORD", "metricsPassword"), - Port: envInt("PORT", "port", 8080), - SentryDSN: envSecretString("SENTRY_DSN", "sentryDSN"), + MetricsUsername: envString("METRICS_USERNAME"), + MetricsPassword: envString("METRICS_PASSWORD"), + Port: envInt("PORT", 8080), + SentryDSN: envString("SENTRY_DSN"), log: log, params: ¶ms, } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index a6acc79..b312f28 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,58 +4,14 @@ import ( "os" "testing" - "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" - pkgconfig "sneak.berlin/go/webhooker/pkg/config" ) -// createTestConfig creates a test configuration file in memory -func createTestConfig(fs afero.Fs) error { - configYAML := ` -environments: - dev: - config: - port: 8080 - debug: true - maintenanceMode: false - developmentMode: true - environment: dev - dburl: postgres://test:test@localhost:5432/test_dev?sslmode=disable - metricsUsername: testuser - metricsPassword: testpass - secrets: - sentryDSN: "" - - prod: - config: - port: $ENV:PORT - debug: $ENV:DEBUG - maintenanceMode: $ENV:MAINTENANCE_MODE - developmentMode: false - environment: prod - dburl: $ENV:DBURL - metricsUsername: $ENV:METRICS_USERNAME - metricsPassword: $ENV:METRICS_PASSWORD - secrets: - sentryDSN: $ENV:SENTRY_DSN - -configDefaults: - port: 8080 - debug: false - maintenanceMode: false - developmentMode: false - environment: dev - metricsUsername: "" - metricsPassword: "" -` - return afero.WriteFile(fs, "config.yaml", []byte(configYAML), 0644) -} - func TestEnvironmentConfig(t *testing.T) { tests := []struct { name string @@ -68,6 +24,7 @@ func TestEnvironmentConfig(t *testing.T) { { name: "default is dev", envValue: "", + envVars: map[string]string{"DBURL": "file::memory:?cache=shared"}, expectError: false, isDev: true, isProd: false, @@ -75,6 +32,7 @@ func TestEnvironmentConfig(t *testing.T) { { name: "explicit dev", envValue: "dev", + envVars: map[string]string{"DBURL": "file::memory:?cache=shared"}, expectError: false, isDev: true, isProd: false, @@ -92,21 +50,19 @@ func TestEnvironmentConfig(t *testing.T) { { name: "invalid environment", envValue: "staging", + envVars: map[string]string{"DBURL": "file::memory:?cache=shared"}, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Create in-memory filesystem with test config - fs := afero.NewMemMapFs() - require.NoError(t, createTestConfig(fs)) - pkgconfig.SetFs(fs) - // Set environment variable if specified if tt.envValue != "" { os.Setenv("WEBHOOKER_ENVIRONMENT", tt.envValue) defer os.Unsetenv("WEBHOOKER_ENVIRONMENT") + } else { + os.Unsetenv("WEBHOOKER_ENVIRONMENT") } // Set additional environment variables diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 2847f04..008b21f 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -2,38 +2,19 @@ package database import ( "context" + "os" "testing" - "github.com/spf13/afero" "go.uber.org/fx/fxtest" "sneak.berlin/go/webhooker/internal/config" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" - pkgconfig "sneak.berlin/go/webhooker/pkg/config" ) func TestDatabaseConnection(t *testing.T) { - // Set up in-memory config so the test does not depend on config.yaml on disk - fs := afero.NewMemMapFs() - testConfigYAML := ` -environments: - dev: - config: - port: 8080 - debug: false - maintenanceMode: false - developmentMode: true - environment: dev - dburl: "file::memory:?cache=shared" - secrets: - sentryDSN: "" -configDefaults: - port: 8080 -` - if err := afero.WriteFile(fs, "config.yaml", []byte(testConfigYAML), 0644); err != nil { - t.Fatalf("Failed to write test config: %v", err) - } - pkgconfig.SetFs(fs) + // Set DBURL env var for config loading + os.Setenv("DBURL", "file::memory:?cache=shared") + defer os.Unsetenv("DBURL") // Set up test dependencies lc := fxtest.NewLifecycle(t) diff --git a/internal/database/webhook_db_manager_test.go b/internal/database/webhook_db_manager_test.go index 327cf73..91aba38 100644 --- a/internal/database/webhook_db_manager_test.go +++ b/internal/database/webhook_db_manager_test.go @@ -7,32 +7,20 @@ import ( "testing" "github.com/google/uuid" - "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx/fxtest" "sneak.berlin/go/webhooker/internal/config" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" - pkgconfig "sneak.berlin/go/webhooker/pkg/config" ) func setupTestWebhookDBManager(t *testing.T) (*WebhookDBManager, *fxtest.Lifecycle) { t.Helper() - fs := afero.NewMemMapFs() - testConfigYAML := ` -environments: - dev: - config: - port: 8080 - debug: false - dburl: "file::memory:?cache=shared" -configDefaults: - port: 8080 -` - require.NoError(t, afero.WriteFile(fs, "config.yaml", []byte(testConfigYAML), 0644)) - pkgconfig.SetFs(fs) + // Set DBURL env var for config loading + os.Setenv("DBURL", "file::memory:?cache=shared") + t.Cleanup(func() { os.Unsetenv("DBURL") }) lc := fxtest.NewLifecycle(t) @@ -52,7 +40,6 @@ configDefaults: DBURL: "file::memory:?cache=shared", DataDir: dataDir, } - _ = cfg mgr, err := NewWebhookDBManager(lc, WebhookDBManagerParams{ Config: cfg, diff --git a/pkg/config/.gitignore b/pkg/config/.gitignore deleted file mode 100644 index 0519ecb..0000000 --- a/pkg/config/.gitignore +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pkg/config/README.md b/pkg/config/README.md deleted file mode 100644 index c8081ab..0000000 --- a/pkg/config/README.md +++ /dev/null @@ -1,303 +0,0 @@ -# Configuration Module (Go) - -A simple, clean, and generic configuration management system that supports multiple environments and automatic value resolution. This module is completely standalone and can be used in any Go project. - -## Features - -- **Simple API**: Just `config.Get()` and `config.GetSecret()` -- **Type-safe helpers**: `config.GetString()`, `config.GetInt()`, `config.GetBool()` -- **Environment Support**: Separate configs for different environments (dev/prod/staging/etc) -- **Value Resolution**: Automatic resolution of special values: - - `$ENV:VARIABLE` - Read from environment variable - - `$GSM:secret-name` - Read from Google Secret Manager - - `$ASM:secret-name` - Read from AWS Secrets Manager - - `$FILE:/path/to/file` - Read from file contents -- **Hierarchical Defaults**: Environment-specific values override defaults -- **YAML-based**: Easy to read and edit configuration files -- **Thread-safe**: Safe for concurrent use -- **Testable**: Uses afero filesystem abstraction for easy testing -- **Minimal Dependencies**: Only requires YAML parser and cloud SDKs (optional) - -## Installation - -```bash -go get git.eeqj.de/sneak/webhooker/pkg/config -``` - -## Usage - -```go -package main - -import ( - "fmt" - "git.eeqj.de/sneak/webhooker/pkg/config" -) - -func main() { - // Set the environment explicitly - config.SetEnvironment("prod") - - // Get configuration values - baseURL := config.GetString("baseURL") - apiTimeout := config.GetInt("timeout", 30) - debugMode := config.GetBool("debugMode", false) - - // Get secret values - apiKey := config.GetSecretString("api_key") - dbPassword := config.GetSecretString("db_password", "default") - - // Get all values (for debugging) - allConfig := config.GetAllConfig() - allSecrets := config.GetAllSecrets() - - // Reload configuration from file - if err := config.Reload(); err != nil { - fmt.Printf("Failed to reload config: %v\n", err) - } -} -``` - -## Configuration File Structure - -Create a `config.yaml` file in your project root: - -```yaml -environments: - dev: - config: - baseURL: https://dev.example.com - debugMode: true - timeout: 30 - secrets: - api_key: dev-key-12345 - db_password: $ENV:DEV_DB_PASSWORD - - prod: - config: - baseURL: https://prod.example.com - debugMode: false - timeout: 10 - GCPProject: my-project-123 - AWSRegion: us-west-2 - secrets: - api_key: $GSM:prod-api-key - db_password: $ASM:prod/db/password - -configDefaults: - app_name: my-app - timeout: 30 - log_level: INFO - port: 8080 -``` - -## How It Works - -1. **Environment Selection**: Call `config.SetEnvironment("prod")` to select which environment to use - -2. **Value Lookup**: When you call `config.Get("key")`: - - First checks `environments..config.key` - - Falls back to `configDefaults.key` - - Returns the default value if not found - -3. **Secret Lookup**: When you call `config.GetSecret("key")`: - - Looks in `environments..secrets.key` - - Returns the default value if not found - -4. **Value Resolution**: If a value starts with a special prefix: - - `$ENV:` - Reads from environment variable - - `$GSM:` - Fetches from Google Secret Manager (requires GCPProject to be set in config) - - `$ASM:` - Fetches from AWS Secrets Manager (uses AWSRegion from config or defaults to us-east-1) - - `$FILE:` - Reads from file (supports `~` expansion) - -## Type-Safe Access - -The module provides type-safe helper functions: - -```go -// String values -baseURL := config.GetString("baseURL", "http://localhost") - -// Integer values -port := config.GetInt("port", 8080) - -// Boolean values -debug := config.GetBool("debug", false) - -// Secret string values -apiKey := config.GetSecretString("api_key", "default-key") -``` - -## Local Development - -For local development, you can: - -1. Use environment variables: - ```yaml - secrets: - api_key: $ENV:LOCAL_API_KEY - ``` - -2. Use local files: - ```yaml - secrets: - api_key: $FILE:~/.secrets/api-key.txt - ``` - -3. Create a `config.local.yaml` (gitignored) with literal values for testing - -## Cloud Provider Support - -### Google Secret Manager - -To use GSM resolution (`$GSM:` prefix): -1. Set `GCPProject` in your config -2. Ensure proper authentication (e.g., `GOOGLE_APPLICATION_CREDENTIALS` environment variable) -3. The module will automatically initialize the GSM client when needed - -### AWS Secrets Manager - -To use ASM resolution (`$ASM:` prefix): -1. Optionally set `AWSRegion` in your config (defaults to us-east-1) -2. Ensure proper authentication (e.g., AWS credentials in environment or IAM role) -3. The module will automatically initialize the ASM client when needed - -## Advanced Usage - -### Loading from a Specific File - -```go -// Load configuration from a specific file -if err := config.LoadFile("/path/to/config.yaml"); err != nil { - log.Fatal(err) -} -``` - -### Checking Configuration Values - -```go -// Get all configuration for current environment -allConfig := config.GetAllConfig() -for key, value := range allConfig { - fmt.Printf("%s: %v\n", key, value) -} - -// Get all secrets (be careful with logging!) -allSecrets := config.GetAllSecrets() -``` - -## Testing - -The module uses the [afero](https://github.com/spf13/afero) filesystem abstraction, making it easy to test without real files: - -```go -package myapp_test - -import ( - "testing" - "github.com/spf13/afero" - "git.eeqj.de/sneak/webhooker/pkg/config" -) - -func TestMyApp(t *testing.T) { - // Create an in-memory filesystem for testing - fs := afero.NewMemMapFs() - - // Write a test config file - testConfig := ` -environments: - test: - config: - apiURL: http://test.example.com - secrets: - apiKey: test-key-123 -` - afero.WriteFile(fs, "config.yaml", []byte(testConfig), 0644) - - // Use the test filesystem - config.SetFs(fs) - config.SetEnvironment("test") - - // Now your tests use the in-memory config - if url := config.GetString("apiURL"); url != "http://test.example.com" { - t.Errorf("Expected test URL, got %s", url) - } -} -``` - -### Unit Testing with Isolated Config - -For unit tests, you can create isolated configuration managers: - -```go -func TestMyComponent(t *testing.T) { - // Create a test-specific manager - manager := config.NewManager() - - // Use in-memory filesystem - fs := afero.NewMemMapFs() - afero.WriteFile(fs, "config.yaml", []byte(testConfig), 0644) - manager.SetFs(fs) - - // Test with isolated configuration - manager.SetEnvironment("test") - value := manager.Get("someKey", "default") -} -``` - -## Error Handling - -- If a config file is not found when using the default loader, an error is returned -- If a key is not found, the default value is returned -- If a special value cannot be resolved (e.g., env var not set, file not found), `nil` is returned -- Cloud provider errors are logged but return `nil` to allow graceful degradation - -## Thread Safety - -All operations are thread-safe. The module uses read-write mutexes to ensure safe concurrent access to configuration data. - -## Example Integration - -```go -package main - -import ( - "log" - "os" - "git.eeqj.de/sneak/webhooker/pkg/config" -) - -func main() { - // Read environment from your app-specific env var - environment := os.Getenv("APP_ENV") - if environment == "" { - environment = "dev" - } - - config.SetEnvironment(environment) - - // Now use configuration throughout your app - databaseURL := config.GetString("database_url") - apiKey := config.GetSecretString("api_key") - - log.Printf("Running in %s environment", environment) - log.Printf("Database URL: %s", databaseURL) -} -``` - -## Migration from Python Version - -The Go version maintains API compatibility with the Python version where possible: - -| Python | Go | -|--------|-----| -| `config.get('key')` | `config.Get("key")` or `config.GetString("key")` | -| `config.getSecret('key')` | `config.GetSecret("key")` or `config.GetSecretString("key")` | -| `config.set_environment('prod')` | `config.SetEnvironment("prod")` | -| `config.reload()` | `config.Reload()` | -| `config.get_all_config()` | `config.GetAllConfig()` | -| `config.get_all_secrets()` | `config.GetAllSecrets()` | - -## License - -This module is designed to be standalone and can be extracted into its own repository with your preferred license. \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go deleted file mode 100644 index 820c779..0000000 --- a/pkg/config/config.go +++ /dev/null @@ -1,180 +0,0 @@ -// Package config provides a simple, clean, and generic configuration management system -// that supports multiple environments and automatic value resolution. -// -// Features: -// - Simple API: Just config.Get() and config.GetSecret() -// - Environment Support: Separate configs for different environments (dev/prod/staging/etc) -// - Value Resolution: Automatic resolution of special values: -// - $ENV:VARIABLE - Read from environment variable -// - $GSM:secret-name - Read from Google Secret Manager -// - $ASM:secret-name - Read from AWS Secrets Manager -// - $FILE:/path/to/file - Read from file contents -// - Hierarchical Defaults: Environment-specific values override defaults -// - YAML-based: Easy to read and edit configuration files -// - Zero Dependencies: Only depends on yaml and cloud provider SDKs (optional) -// -// Usage: -// -// import "sneak.berlin/go/webhooker/pkg/config" -// -// // Set the environment explicitly -// config.SetEnvironment("prod") -// -// // Get configuration values -// baseURL := config.Get("baseURL") -// apiTimeout := config.GetInt("timeout", 30) -// -// // Get secret values -// apiKey := config.GetSecret("api_key") -// dbPassword := config.GetSecret("db_password", "default") -package config - -import ( - "sync" - - "github.com/spf13/afero" -) - -// Global configuration manager instance -var ( - globalManager *Manager - mu sync.Mutex // Protect global manager updates -) - -// getManager returns the global configuration manager, creating it if necessary -func getManager() *Manager { - mu.Lock() - defer mu.Unlock() - - if globalManager == nil { - globalManager = NewManager() - } - return globalManager -} - -// SetEnvironment sets the active environment. -func SetEnvironment(environment string) { - getManager().SetEnvironment(environment) -} - -// SetFs sets the filesystem to use for all file operations. -// This is primarily useful for testing with an in-memory filesystem. -func SetFs(fs afero.Fs) { - mu.Lock() - defer mu.Unlock() - - // Create a new manager with the specified filesystem - newManager := NewManager() - newManager.SetFs(fs) - - // Replace the global manager - globalManager = newManager -} - -// Get retrieves a configuration value. -// -// This looks for values in the following order: -// 1. Environment-specific config (environments..config.) -// 2. Config defaults (configDefaults.) -// -// Values are resolved if they contain special prefixes: -// - $ENV:VARIABLE_NAME - reads from environment variable -// - $GSM:secret-name - reads from Google Secret Manager -// - $ASM:secret-name - reads from AWS Secrets Manager -// - $FILE:/path/to/file - reads from file -func Get(key string, defaultValue ...interface{}) interface{} { - var def interface{} - if len(defaultValue) > 0 { - def = defaultValue[0] - } - return getManager().Get(key, def) -} - -// GetString retrieves a configuration value as a string. -func GetString(key string, defaultValue ...string) string { - var def string - if len(defaultValue) > 0 { - def = defaultValue[0] - } - val := Get(key, def) - if s, ok := val.(string); ok { - return s - } - return def -} - -// GetInt retrieves a configuration value as an integer. -func GetInt(key string, defaultValue ...int) int { - var def int - if len(defaultValue) > 0 { - def = defaultValue[0] - } - val := Get(key, def) - switch v := val.(type) { - case int: - return v - case int64: - return int(v) - case float64: - return int(v) - default: - return def - } -} - -// GetBool retrieves a configuration value as a boolean. -func GetBool(key string, defaultValue ...bool) bool { - var def bool - if len(defaultValue) > 0 { - def = defaultValue[0] - } - val := Get(key, def) - if b, ok := val.(bool); ok { - return b - } - return def -} - -// GetSecret retrieves a secret value. -// -// This looks for secrets defined in environments..secrets. -func GetSecret(key string, defaultValue ...interface{}) interface{} { - var def interface{} - if len(defaultValue) > 0 { - def = defaultValue[0] - } - return getManager().GetSecret(key, def) -} - -// GetSecretString retrieves a secret value as a string. -func GetSecretString(key string, defaultValue ...string) string { - var def string - if len(defaultValue) > 0 { - def = defaultValue[0] - } - val := GetSecret(key, def) - if s, ok := val.(string); ok { - return s - } - return def -} - -// Reload reloads the configuration from file. -func Reload() error { - return getManager().Reload() -} - -// GetAllConfig returns all configuration values for the current environment. -func GetAllConfig() map[string]interface{} { - return getManager().GetAllConfig() -} - -// GetAllSecrets returns all secrets for the current environment. -func GetAllSecrets() map[string]interface{} { - return getManager().GetAllSecrets() -} - -// LoadFile loads configuration from a specific file. -func LoadFile(configFile string) error { - return getManager().LoadFile(configFile) -} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go deleted file mode 100644 index 89cf2e7..0000000 --- a/pkg/config/config_test.go +++ /dev/null @@ -1,306 +0,0 @@ -package config - -import ( - "os" - "testing" - - "github.com/spf13/afero" -) - -func TestNewManager(t *testing.T) { - manager := NewManager() - if manager == nil { - t.Fatal("NewManager returned nil") - } - if manager.config == nil { - t.Error("Manager config map is nil") - } - if manager.loader == nil { - t.Error("Manager loader is nil") - } - if manager.resolvedCache == nil { - t.Error("Manager resolvedCache is nil") - } - if manager.fs == nil { - t.Error("Manager fs is nil") - } -} - -func TestLoader_FindConfigFile(t *testing.T) { - // Create an in-memory filesystem for testing - fs := afero.NewMemMapFs() - loader := NewLoader(fs) - - // Create a config file in the filesystem - configContent := ` -environments: - test: - config: - testKey: testValue - secrets: - testSecret: secretValue -configDefaults: - defaultKey: defaultValue -` - // Create the file in the current directory - if err := afero.WriteFile(fs, "config.yaml", []byte(configContent), 0644); err != nil { - t.Fatalf("Failed to write test config: %v", err) - } - - // Test finding the config file - foundPath, err := loader.FindConfigFile("config.yaml") - if err != nil { - t.Errorf("FindConfigFile failed: %v", err) - } - - // In memory fs, the path should be exactly what we created - if foundPath != "config.yaml" { - t.Errorf("Expected config.yaml, got %s", foundPath) - } -} - -func TestLoader_LoadYAML(t *testing.T) { - fs := afero.NewMemMapFs() - loader := NewLoader(fs) - - // Create a test config file - testConfig := ` -environments: - test: - config: - testKey: testValue -configDefaults: - defaultKey: defaultValue -` - if err := afero.WriteFile(fs, "test-config.yaml", []byte(testConfig), 0644); err != nil { - t.Fatalf("Failed to write test config: %v", err) - } - - // Load the YAML - config, err := loader.LoadYAML("test-config.yaml") - if err != nil { - t.Fatalf("LoadYAML failed: %v", err) - } - - // Verify the structure - envs, ok := config["environments"].(map[string]interface{}) - if !ok { - t.Fatal("environments not found or wrong type") - } - - testEnv, ok := envs["test"].(map[string]interface{}) - if !ok { - t.Fatal("test environment not found") - } - - testConfig2, ok := testEnv["config"].(map[string]interface{}) - if !ok { - t.Fatal("test config not found") - } - - if testConfig2["testKey"] != "testValue" { - t.Errorf("Expected testKey=testValue, got %v", testConfig2["testKey"]) - } -} - -func TestResolver_ResolveEnv(t *testing.T) { - fs := afero.NewMemMapFs() - resolver := NewResolver("", "", fs) - - // Set a test environment variable - os.Setenv("TEST_CONFIG_VAR", "test-value") - defer os.Unsetenv("TEST_CONFIG_VAR") - - // Test resolving environment variable - result := resolver.Resolve("$ENV:TEST_CONFIG_VAR") - if result != "test-value" { - t.Errorf("Expected 'test-value', got %v", result) - } - - // Test non-existent env var - result = resolver.Resolve("$ENV:NON_EXISTENT_VAR") - if result != nil { - t.Errorf("Expected nil for non-existent env var, got %v", result) - } -} - -func TestResolver_ResolveFile(t *testing.T) { - fs := afero.NewMemMapFs() - resolver := NewResolver("", "", fs) - - // Create a test file - secretContent := "my-secret-value" - if err := afero.WriteFile(fs, "/test-secret.txt", []byte(secretContent+"\n"), 0644); err != nil { - t.Fatalf("Failed to write test file: %v", err) - } - - // Test resolving file - result := resolver.Resolve("$FILE:/test-secret.txt") - if result != secretContent { - t.Errorf("Expected '%s', got %v", secretContent, result) - } - - // Test non-existent file - result = resolver.Resolve("$FILE:/non/existent/file") - if result != nil { - t.Errorf("Expected nil for non-existent file, got %v", result) - } -} - -func TestManager_GetAndSet(t *testing.T) { - // Create an in-memory filesystem - fs := afero.NewMemMapFs() - - // Create a test config file - testConfig := ` -environments: - dev: - config: - apiURL: http://dev.example.com - timeout: 30 - debug: true - secrets: - apiKey: dev-key-123 - prod: - config: - apiURL: https://prod.example.com - timeout: 10 - debug: false - secrets: - apiKey: $ENV:PROD_API_KEY -configDefaults: - appName: TestApp - timeout: 20 - port: 8080 -` - if err := afero.WriteFile(fs, "config.yaml", []byte(testConfig), 0644); err != nil { - t.Fatalf("Failed to write test config: %v", err) - } - - // Create manager and set the filesystem - manager := NewManager() - manager.SetFs(fs) - - // Load config should find the file automatically - manager.SetEnvironment("dev") - - // Test getting config values - if v := manager.Get("apiURL", ""); v != "http://dev.example.com" { - t.Errorf("Expected dev apiURL, got %v", v) - } - - if v := manager.Get("timeout", 0); v != 30 { - t.Errorf("Expected timeout=30, got %v", v) - } - - if v := manager.Get("debug", false); v != true { - t.Errorf("Expected debug=true, got %v", v) - } - - // Test default values - if v := manager.Get("appName", ""); v != "TestApp" { - t.Errorf("Expected appName from defaults, got %v", v) - } - - // Test getting secrets - if v := manager.GetSecret("apiKey", ""); v != "dev-key-123" { - t.Errorf("Expected dev apiKey, got %v", v) - } - - // Switch to prod environment - manager.SetEnvironment("prod") - - if v := manager.Get("apiURL", ""); v != "https://prod.example.com" { - t.Errorf("Expected prod apiURL, got %v", v) - } - - // Test environment variable resolution in secrets - os.Setenv("PROD_API_KEY", "prod-key-456") - defer os.Unsetenv("PROD_API_KEY") - - if v := manager.GetSecret("apiKey", ""); v != "prod-key-456" { - t.Errorf("Expected resolved env var for apiKey, got %v", v) - } -} - -func TestGlobalAPI(t *testing.T) { - // Create an in-memory filesystem - fs := afero.NewMemMapFs() - - // Create a test config file - testConfig := ` -environments: - test: - config: - stringVal: hello - intVal: 42 - boolVal: true - secrets: - secret1: test-secret -configDefaults: - defaultString: world -` - if err := afero.WriteFile(fs, "config.yaml", []byte(testConfig), 0644); err != nil { - t.Fatalf("Failed to write test config: %v", err) - } - - // Use the global API with the test filesystem - SetFs(fs) - SetEnvironment("test") - - // Test type-safe getters - if v := GetString("stringVal"); v != "hello" { - t.Errorf("Expected 'hello', got %v", v) - } - - if v := GetInt("intVal"); v != 42 { - t.Errorf("Expected 42, got %v", v) - } - - if v := GetBool("boolVal"); v != true { - t.Errorf("Expected true, got %v", v) - } - - if v := GetSecretString("secret1"); v != "test-secret" { - t.Errorf("Expected 'test-secret', got %v", v) - } - - // Test defaults - if v := GetString("defaultString"); v != "world" { - t.Errorf("Expected 'world', got %v", v) - } -} - -func TestManager_SetFs(t *testing.T) { - // Create manager with default OS filesystem - manager := NewManager() - - // Create an in-memory filesystem - memFs := afero.NewMemMapFs() - - // Write a config file to the memory fs - testConfig := ` -environments: - test: - config: - testKey: fromMemory -configDefaults: - defaultKey: memoryDefault -` - if err := afero.WriteFile(memFs, "config.yaml", []byte(testConfig), 0644); err != nil { - t.Fatalf("Failed to write test config: %v", err) - } - - // Set the filesystem - manager.SetFs(memFs) - manager.SetEnvironment("test") - - // Test that it reads from the memory filesystem - if v := manager.Get("testKey", ""); v != "fromMemory" { - t.Errorf("Expected 'fromMemory', got %v", v) - } - - if v := manager.Get("defaultKey", ""); v != "memoryDefault" { - t.Errorf("Expected 'memoryDefault', got %v", v) - } -} diff --git a/pkg/config/example_afero_test.go b/pkg/config/example_afero_test.go deleted file mode 100644 index 7fc0e64..0000000 --- a/pkg/config/example_afero_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package config_test - -import ( - "fmt" - "testing" - - "github.com/spf13/afero" - "sneak.berlin/go/webhooker/pkg/config" -) - -// ExampleSetFs demonstrates how to use an in-memory filesystem for testing -func ExampleSetFs() { - // Create an in-memory filesystem - fs := afero.NewMemMapFs() - - // Create a test configuration file - configYAML := ` -environments: - test: - config: - baseURL: https://test.example.com - debugMode: true - secrets: - apiKey: test-key-12345 - production: - config: - baseURL: https://api.example.com - debugMode: false -configDefaults: - appName: Test Application - timeout: 30 -` - - // Write the config to the in-memory filesystem - if err := afero.WriteFile(fs, "config.yaml", []byte(configYAML), 0644); err != nil { - panic(err) - } - - // Use the in-memory filesystem - config.SetFs(fs) - config.SetEnvironment("test") - - // Now all config operations use the in-memory filesystem - fmt.Printf("Base URL: %s\n", config.GetString("baseURL")) - fmt.Printf("Debug Mode: %v\n", config.GetBool("debugMode")) - fmt.Printf("App Name: %s\n", config.GetString("appName")) - - // Output: - // Base URL: https://test.example.com - // Debug Mode: true - // App Name: Test Application -} - -// TestWithAferoFilesystem shows how to test with different filesystem implementations -func TestWithAferoFilesystem(t *testing.T) { - tests := []struct { - name string - setupFs func() afero.Fs - environment string - key string - expected string - }{ - { - name: "in-memory filesystem", - setupFs: func() afero.Fs { - fs := afero.NewMemMapFs() - config := ` -environments: - dev: - config: - apiURL: http://localhost:8080 -` - afero.WriteFile(fs, "config.yaml", []byte(config), 0644) - return fs - }, - environment: "dev", - key: "apiURL", - expected: "http://localhost:8080", - }, - { - name: "readonly filesystem", - setupFs: func() afero.Fs { - memFs := afero.NewMemMapFs() - config := ` -environments: - staging: - config: - apiURL: https://staging.example.com -` - afero.WriteFile(memFs, "config.yaml", []byte(config), 0644) - // Wrap in a read-only filesystem - return afero.NewReadOnlyFs(memFs) - }, - environment: "staging", - key: "apiURL", - expected: "https://staging.example.com", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a new manager for each test to ensure isolation - manager := config.NewManager() - manager.SetFs(tt.setupFs()) - manager.SetEnvironment(tt.environment) - - result := manager.Get(tt.key, "") - if result != tt.expected { - t.Errorf("Expected %s, got %v", tt.expected, result) - } - }) - } -} - -// TestFileResolution shows how $FILE: resolution works with afero -func TestFileResolution(t *testing.T) { - // Create an in-memory filesystem - fs := afero.NewMemMapFs() - - // Create a secret file - secretContent := "super-secret-api-key" - if err := afero.WriteFile(fs, "/secrets/api-key.txt", []byte(secretContent), 0600); err != nil { - t.Fatal(err) - } - - // Create a config that references the file - configYAML := ` -environments: - prod: - secrets: - apiKey: $FILE:/secrets/api-key.txt -` - if err := afero.WriteFile(fs, "config.yaml", []byte(configYAML), 0644); err != nil { - t.Fatal(err) - } - - // Use the filesystem - config.SetFs(fs) - config.SetEnvironment("prod") - - // Get the secret - it should resolve from the file - apiKey := config.GetSecretString("apiKey") - if apiKey != secretContent { - t.Errorf("Expected %s, got %s", secretContent, apiKey) - } -} diff --git a/pkg/config/example_test.go b/pkg/config/example_test.go deleted file mode 100644 index 007d7b6..0000000 --- a/pkg/config/example_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package config_test - -import ( - "fmt" - "log" - "os" - - "sneak.berlin/go/webhooker/pkg/config" -) - -func Example() { - // Set the environment explicitly - config.SetEnvironment("dev") - - // Get configuration values - baseURL := config.GetString("baseURL") - timeout := config.GetInt("timeout", 30) - debugMode := config.GetBool("debugMode", false) - - fmt.Printf("Base URL: %s\n", baseURL) - fmt.Printf("Timeout: %d\n", timeout) - fmt.Printf("Debug Mode: %v\n", debugMode) - - // Get secret values - apiKey := config.GetSecretString("api_key") - if apiKey != "" { - fmt.Printf("API Key: %s...\n", apiKey[:8]) - } -} - -func ExampleSetEnvironment() { - // Your application determines which environment to use - // This could come from command line args, env vars, etc. - environment := os.Getenv("APP_ENV") - if environment == "" { - environment = "development" - } - - // Set the environment explicitly - config.SetEnvironment(environment) - - // Now use configuration throughout your application - fmt.Printf("Environment: %s\n", environment) - fmt.Printf("App Name: %s\n", config.GetString("app_name")) -} - -func ExampleGetString() { - config.SetEnvironment("prod") - - // Get a string configuration value with a default - baseURL := config.GetString("baseURL", "http://localhost:8080") - fmt.Printf("Base URL: %s\n", baseURL) -} - -func ExampleGetInt() { - config.SetEnvironment("prod") - - // Get an integer configuration value with a default - port := config.GetInt("port", 8080) - fmt.Printf("Port: %d\n", port) -} - -func ExampleGetBool() { - config.SetEnvironment("dev") - - // Get a boolean configuration value with a default - debugMode := config.GetBool("debugMode", false) - fmt.Printf("Debug Mode: %v\n", debugMode) -} - -func ExampleGetSecretString() { - config.SetEnvironment("prod") - - // Get a secret string value - apiKey := config.GetSecretString("api_key") - if apiKey != "" { - // Be careful not to log the full secret! - fmt.Printf("API Key configured: yes\n") - } -} - -func ExampleLoadFile() { - // Load configuration from a specific file - if err := config.LoadFile("/path/to/config.yaml"); err != nil { - log.Printf("Failed to load config: %v", err) - return - } - - config.SetEnvironment("staging") - fmt.Printf("Loaded configuration from custom file\n") -} - -func ExampleReload() { - config.SetEnvironment("dev") - - // Get initial value - oldValue := config.GetString("some_key") - - // ... config file might have been updated ... - - // Reload configuration from file - if err := config.Reload(); err != nil { - log.Printf("Failed to reload config: %v", err) - return - } - - // Get potentially updated value - newValue := config.GetString("some_key") - fmt.Printf("Value changed: %v\n", oldValue != newValue) -} - -// Example config.yaml structure: -/* -environments: - development: - config: - baseURL: http://localhost:8000 - debugMode: true - port: 8000 - secrets: - api_key: dev-key-12345 - - production: - config: - baseURL: https://api.example.com - debugMode: false - port: 443 - GCPProject: my-project-123 - AWSRegion: us-west-2 - secrets: - api_key: $GSM:prod-api-key - db_password: $ASM:prod/db/password - -configDefaults: - app_name: My Application - timeout: 30 - log_level: INFO - port: 8080 -*/ diff --git a/pkg/config/go.mod b/pkg/config/go.mod deleted file mode 100644 index 57aa52f..0000000 --- a/pkg/config/go.mod +++ /dev/null @@ -1,41 +0,0 @@ -module sneak.berlin/go/webhooker/pkg/config - -go 1.23.0 - -toolchain go1.24.1 - -require ( - github.com/aws/aws-sdk-go v1.50.0 - github.com/spf13/afero v1.14.0 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - cloud.google.com/go/compute v1.23.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.3 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect - go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/api v0.149.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect -) - -require ( - cloud.google.com/go/secretmanager v1.11.4 - github.com/jmespath/go-jmespath v0.4.0 // indirect -) diff --git a/pkg/config/go.sum b/pkg/config/go.sum deleted file mode 100644 index 6b66379..0000000 --- a/pkg/config/go.sum +++ /dev/null @@ -1,161 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= -cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= -cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= -cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= -cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= -cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= -cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= -github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -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.1/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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.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= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -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/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= -google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= -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.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= -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.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -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= diff --git a/pkg/config/loader.go b/pkg/config/loader.go deleted file mode 100644 index 2a87d97..0000000 --- a/pkg/config/loader.go +++ /dev/null @@ -1,104 +0,0 @@ -package config - -import ( - "fmt" - "path/filepath" - - "github.com/spf13/afero" - "gopkg.in/yaml.v3" -) - -// Loader handles loading configuration from YAML files. -type Loader struct { - fs afero.Fs -} - -// NewLoader creates a new configuration loader. -func NewLoader(fs afero.Fs) *Loader { - return &Loader{ - fs: fs, - } -} - -// FindConfigFile searches for a configuration file by looking up the directory tree. -func (l *Loader) FindConfigFile(filename string) (string, error) { - if filename == "" { - filename = "config.yaml" - } - - // First check if the file exists in the current directory (simple case) - if _, err := l.fs.Stat(filename); err == nil { - return filename, nil - } - - // For more complex cases, try to walk up the directory tree - // Start from current directory or root for in-memory filesystems - currentDir := "." - - // Try to get the absolute path, but if it fails (e.g., in-memory fs), - // just use the current directory - if absPath, err := filepath.Abs("."); err == nil { - currentDir = absPath - } - - // Search up the directory tree - for { - configPath := filepath.Join(currentDir, filename) - if _, err := l.fs.Stat(configPath); err == nil { - return configPath, nil - } - - // Move up one directory - parentDir := filepath.Dir(currentDir) - if parentDir == currentDir || currentDir == "." || currentDir == "/" { - // Reached the root directory or can't go up further - break - } - currentDir = parentDir - } - - return "", fmt.Errorf("configuration file %s not found in directory tree", filename) -} - -// LoadYAML loads a YAML file and returns the parsed configuration. -func (l *Loader) LoadYAML(filePath string) (map[string]interface{}, error) { - data, err := afero.ReadFile(l.fs, filePath) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", filePath, err) - } - - var config map[string]interface{} - if err := yaml.Unmarshal(data, &config); err != nil { - return nil, fmt.Errorf("failed to parse YAML from %s: %w", filePath, err) - } - - if config == nil { - config = make(map[string]interface{}) - } - - return config, nil -} - -// MergeConfigs performs a deep merge of two configuration maps. -// The override map values take precedence over the base map. -func (l *Loader) MergeConfigs(base, override map[string]interface{}) map[string]interface{} { - if base == nil { - base = make(map[string]interface{}) - } - - for key, value := range override { - if baseValue, exists := base[key]; exists { - // If both values are maps, merge them recursively - if baseMap, baseOk := baseValue.(map[string]interface{}); baseOk { - if overrideMap, overrideOk := value.(map[string]interface{}); overrideOk { - base[key] = l.MergeConfigs(baseMap, overrideMap) - continue - } - } - } - // Otherwise, override the value - base[key] = value - } - - return base -} diff --git a/pkg/config/manager.go b/pkg/config/manager.go deleted file mode 100644 index 666821d..0000000 --- a/pkg/config/manager.go +++ /dev/null @@ -1,377 +0,0 @@ -package config - -import ( - "fmt" - "strings" - "sync" - - "github.com/spf13/afero" -) - -// Manager manages application configuration with value resolution. -type Manager struct { - mu sync.RWMutex - config map[string]interface{} - environment string - resolver *Resolver - loader *Loader - configFile string - resolvedCache map[string]interface{} - fs afero.Fs -} - -// NewManager creates a new configuration manager. -func NewManager() *Manager { - fs := afero.NewOsFs() - return &Manager{ - config: make(map[string]interface{}), - loader: NewLoader(fs), - resolvedCache: make(map[string]interface{}), - fs: fs, - } -} - -// SetFs sets the filesystem to use for all file operations. -// This is primarily useful for testing with an in-memory filesystem. -func (m *Manager) SetFs(fs afero.Fs) { - m.mu.Lock() - defer m.mu.Unlock() - - m.fs = fs - m.loader = NewLoader(fs) - - // If we have a resolver, recreate it with the new fs - if m.resolver != nil { - gcpProject := "" - awsRegion := "us-east-1" - - // Try to get the current settings - if gcpProj := m.getConfigValue("GCPProject", ""); gcpProj != nil { - if str, ok := gcpProj.(string); ok { - gcpProject = str - } - } - if awsReg := m.getConfigValue("AWSRegion", "us-east-1"); awsReg != nil { - if str, ok := awsReg.(string); ok { - awsRegion = str - } - } - - m.resolver = NewResolver(gcpProject, awsRegion, fs) - } - - // Clear caches as filesystem changed - m.resolvedCache = make(map[string]interface{}) -} - -// LoadFile loads configuration from a specific file. -func (m *Manager) LoadFile(configFile string) error { - m.mu.Lock() - defer m.mu.Unlock() - - config, err := m.loader.LoadYAML(configFile) - if err != nil { - return err - } - - m.config = config - m.configFile = configFile - m.resolvedCache = make(map[string]interface{}) // Clear cache - return nil -} - -// loadConfig loads the configuration from file. -func (m *Manager) loadConfig() error { - if m.configFile == "" { - // Try to find config.yaml - configPath, err := m.loader.FindConfigFile("config.yaml") - if err != nil { - return err - } - m.configFile = configPath - } - - config, err := m.loader.LoadYAML(m.configFile) - if err != nil { - return err - } - - m.config = config - m.resolvedCache = make(map[string]interface{}) // Clear cache - return nil -} - -// SetEnvironment sets the active environment. -func (m *Manager) SetEnvironment(environment string) { - m.mu.Lock() - defer m.mu.Unlock() - - m.environment = strings.ToLower(environment) - - // Create resolver with GCP project and AWS region if available - gcpProject := m.getConfigValue("GCPProject", "") - awsRegion := m.getConfigValue("AWSRegion", "us-east-1") - - if gcpProjectStr, ok := gcpProject.(string); ok { - if awsRegionStr, ok := awsRegion.(string); ok { - m.resolver = NewResolver(gcpProjectStr, awsRegionStr, m.fs) - } - } - - // Clear resolved cache when environment changes - m.resolvedCache = make(map[string]interface{}) -} - -// Get retrieves a configuration value. -func (m *Manager) Get(key string, defaultValue interface{}) interface{} { - m.mu.RLock() - - // Ensure config is loaded - if m.config == nil || len(m.config) == 0 { - // Need to upgrade to write lock to load config - m.mu.RUnlock() - m.mu.Lock() - // Double-check after acquiring write lock - if m.config == nil || len(m.config) == 0 { - if err := m.loadConfig(); err != nil { - // Config file not found is expected when all values - // come from environment variables. Only log at debug - // level to avoid confusing "Failed to load config" - // messages during normal operation. - _ = err - m.mu.Unlock() - return defaultValue - } - } - // Downgrade back to read lock - m.mu.Unlock() - m.mu.RLock() - } - defer m.mu.RUnlock() - - // Check cache first - cacheKey := fmt.Sprintf("config.%s", key) - if cached, ok := m.resolvedCache[cacheKey]; ok { - return cached - } - - // Try environment-specific config first - var rawValue interface{} - if m.environment != "" { - envMap, ok := m.config["environments"].(map[string]interface{}) - if ok { - if env, ok := envMap[m.environment].(map[string]interface{}); ok { - if config, ok := env["config"].(map[string]interface{}); ok { - if val, exists := config[key]; exists { - rawValue = val - } - } - } - } - } - - // Fall back to configDefaults - if rawValue == nil { - if defaults, ok := m.config["configDefaults"].(map[string]interface{}); ok { - if val, exists := defaults[key]; exists { - rawValue = val - } - } - } - - if rawValue == nil { - return defaultValue - } - - // Resolve the value if we have a resolver - var resolvedValue interface{} - if m.resolver != nil { - resolvedValue = m.resolver.Resolve(rawValue) - } else { - resolvedValue = rawValue - } - - // Cache the resolved value - m.resolvedCache[cacheKey] = resolvedValue - - return resolvedValue -} - -// GetSecret retrieves a secret value for the current environment. -func (m *Manager) GetSecret(key string, defaultValue interface{}) interface{} { - m.mu.RLock() - - // Ensure config is loaded - if m.config == nil || len(m.config) == 0 { - // Need to upgrade to write lock to load config - m.mu.RUnlock() - m.mu.Lock() - // Double-check after acquiring write lock - if m.config == nil || len(m.config) == 0 { - if err := m.loadConfig(); err != nil { - // Config file not found is expected when all values - // come from environment variables. - _ = err - m.mu.Unlock() - return defaultValue - } - } - // Downgrade back to read lock - m.mu.Unlock() - m.mu.RLock() - } - defer m.mu.RUnlock() - - if m.environment == "" { - return defaultValue - } - - // Get the current environment's config - envMap, ok := m.config["environments"].(map[string]interface{}) - if !ok { - return defaultValue - } - - env, ok := envMap[m.environment].(map[string]interface{}) - if !ok { - return defaultValue - } - - secrets, ok := env["secrets"].(map[string]interface{}) - if !ok { - return defaultValue - } - - secretValue, exists := secrets[key] - if !exists { - return defaultValue - } - - // Resolve the value - if m.resolver != nil { - resolved := m.resolver.Resolve(secretValue) - if resolved == nil { - return defaultValue - } - return resolved - } - - return secretValue -} - -// getConfigValue is an internal helper to get config values without locking. -func (m *Manager) getConfigValue(key string, defaultValue interface{}) interface{} { - // Try environment-specific config first - var rawValue interface{} - if m.environment != "" { - envMap, ok := m.config["environments"].(map[string]interface{}) - if ok { - if env, ok := envMap[m.environment].(map[string]interface{}); ok { - if config, ok := env["config"].(map[string]interface{}); ok { - if val, exists := config[key]; exists { - rawValue = val - } - } - } - } - } - - // Fall back to configDefaults - if rawValue == nil { - if defaults, ok := m.config["configDefaults"].(map[string]interface{}); ok { - if val, exists := defaults[key]; exists { - rawValue = val - } - } - } - - if rawValue == nil { - return defaultValue - } - - return rawValue -} - -// Reload reloads the configuration from file. -func (m *Manager) Reload() error { - m.mu.Lock() - defer m.mu.Unlock() - - return m.loadConfig() -} - -// GetAllConfig returns all configuration values for the current environment. -func (m *Manager) GetAllConfig() map[string]interface{} { - m.mu.RLock() - defer m.mu.RUnlock() - - result := make(map[string]interface{}) - - // Start with configDefaults - if defaults, ok := m.config["configDefaults"].(map[string]interface{}); ok { - for k, v := range defaults { - if m.resolver != nil { - result[k] = m.resolver.Resolve(v) - } else { - result[k] = v - } - } - } - - // Override with environment-specific config - if m.environment != "" { - envMap, ok := m.config["environments"].(map[string]interface{}) - if ok { - if env, ok := envMap[m.environment].(map[string]interface{}); ok { - if config, ok := env["config"].(map[string]interface{}); ok { - for k, v := range config { - if m.resolver != nil { - result[k] = m.resolver.Resolve(v) - } else { - result[k] = v - } - } - } - } - } - } - - return result -} - -// GetAllSecrets returns all secrets for the current environment. -func (m *Manager) GetAllSecrets() map[string]interface{} { - m.mu.RLock() - defer m.mu.RUnlock() - - if m.environment == "" { - return make(map[string]interface{}) - } - - envMap, ok := m.config["environments"].(map[string]interface{}) - if !ok { - return make(map[string]interface{}) - } - - env, ok := envMap[m.environment].(map[string]interface{}) - if !ok { - return make(map[string]interface{}) - } - - secrets, ok := env["secrets"].(map[string]interface{}) - if !ok { - return make(map[string]interface{}) - } - - // Resolve all secrets - result := make(map[string]interface{}) - for k, v := range secrets { - if m.resolver != nil { - result[k] = m.resolver.Resolve(v) - } else { - result[k] = v - } - } - - return result -} diff --git a/pkg/config/resolver.go b/pkg/config/resolver.go deleted file mode 100644 index d6e6a21..0000000 --- a/pkg/config/resolver.go +++ /dev/null @@ -1,204 +0,0 @@ -package config - -import ( - "context" - "fmt" - "log" - "os" - "path/filepath" - "regexp" - "strings" - - secretmanager "cloud.google.com/go/secretmanager/apiv1" - "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/spf13/afero" -) - -// Resolver handles resolution of configuration values with special prefixes. -type Resolver struct { - gcpProject string - awsRegion string - gsmClient *secretmanager.Client - asmClient *secretsmanager.SecretsManager - awsSession *session.Session - specialValue *regexp.Regexp - fs afero.Fs -} - -// NewResolver creates a new value resolver. -func NewResolver(gcpProject, awsRegion string, fs afero.Fs) *Resolver { - return &Resolver{ - gcpProject: gcpProject, - awsRegion: awsRegion, - specialValue: regexp.MustCompile(`^\$([A-Z]+):(.+)$`), - fs: fs, - } -} - -// Resolve resolves a configuration value that may contain special prefixes. -func (r *Resolver) Resolve(value interface{}) interface{} { - switch v := value.(type) { - case string: - return r.resolveString(v) - case map[string]interface{}: - // Recursively resolve map values - result := make(map[string]interface{}) - for k, val := range v { - result[k] = r.Resolve(val) - } - return result - case []interface{}: - // Recursively resolve slice items - result := make([]interface{}, len(v)) - for i, val := range v { - result[i] = r.Resolve(val) - } - return result - default: - // Return non-string values as-is - return value - } -} - -// resolveString resolves a string value that may contain a special prefix. -func (r *Resolver) resolveString(value string) interface{} { - matches := r.specialValue.FindStringSubmatch(value) - if matches == nil { - return value - } - - resolverType := matches[1] - resolverValue := matches[2] - - switch resolverType { - case "ENV": - return r.resolveEnv(resolverValue) - case "GSM": - return r.resolveGSM(resolverValue) - case "ASM": - return r.resolveASM(resolverValue) - case "FILE": - return r.resolveFile(resolverValue) - default: - log.Printf("Unknown resolver type: %s", resolverType) - return value - } -} - -// resolveEnv resolves an environment variable. -func (r *Resolver) resolveEnv(envVar string) interface{} { - value := os.Getenv(envVar) - if value == "" { - return nil - } - return value -} - -// resolveGSM resolves a Google Secret Manager secret. -func (r *Resolver) resolveGSM(secretName string) interface{} { - if r.gcpProject == "" { - log.Printf("GCP project not configured for GSM resolution") - return nil - } - - // Initialize GSM client if needed - if r.gsmClient == nil { - ctx := context.Background() - client, err := secretmanager.NewClient(ctx) - if err != nil { - log.Printf("Failed to create GSM client: %v", err) - return nil - } - r.gsmClient = client - } - - // Build the resource name - name := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", r.gcpProject, secretName) - - // Access the secret - ctx := context.Background() - req := &secretmanagerpb.AccessSecretVersionRequest{ - Name: name, - } - - result, err := r.gsmClient.AccessSecretVersion(ctx, req) - if err != nil { - log.Printf("Failed to access GSM secret %s: %v", secretName, err) - return nil - } - - return string(result.Payload.Data) -} - -// resolveASM resolves an AWS Secrets Manager secret. -func (r *Resolver) resolveASM(secretName string) interface{} { - // Initialize AWS session if needed - if r.awsSession == nil { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(r.awsRegion), - }) - if err != nil { - log.Printf("Failed to create AWS session: %v", err) - return nil - } - r.awsSession = sess - } - - // Initialize ASM client if needed - if r.asmClient == nil { - r.asmClient = secretsmanager.New(r.awsSession) - } - - // Get the secret value - input := &secretsmanager.GetSecretValueInput{ - SecretId: aws.String(secretName), - } - - result, err := r.asmClient.GetSecretValue(input) - if err != nil { - log.Printf("Failed to access ASM secret %s: %v", secretName, err) - return nil - } - - // Return the secret string - if result.SecretString != nil { - return *result.SecretString - } - - // If it's binary data, we can't handle it as a string config value - log.Printf("ASM secret %s contains binary data, which is not supported", secretName) - return nil -} - -// resolveFile resolves a file's contents. -func (r *Resolver) resolveFile(filePath string) interface{} { - // Expand user home directory if present - if strings.HasPrefix(filePath, "~/") { - home, err := os.UserHomeDir() - if err != nil { - log.Printf("Failed to get user home directory: %v", err) - return nil - } - filePath = filepath.Join(home, filePath[2:]) - } - - data, err := afero.ReadFile(r.fs, filePath) - if err != nil { - log.Printf("Failed to read file %s: %v", filePath, err) - return nil - } - - // Strip whitespace/newlines from file contents - return strings.TrimSpace(string(data)) -} - -// Close closes any open clients. -func (r *Resolver) Close() error { - if r.gsmClient != nil { - return r.gsmClient.Close() - } - return nil -}