Skip to content

Commit 08f0e01

Browse files
refactor: drop gocloud-kit logic, local implementation
1 parent dae9f94 commit 08f0e01

6 files changed

Lines changed: 250 additions & 82 deletions

File tree

config/secret_aws.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
"github.com/aws/aws-sdk-go-v2/aws"
9+
awsconfig "github.com/aws/aws-sdk-go-v2/config"
10+
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
11+
)
12+
13+
type awsSecretsManagerProvider struct{}
14+
15+
// getDSN fetches the secret value from AWS Secrets Manager.
16+
// URL format: awssecretsmanager://secret-name?region=us-east-1
17+
func (p awsSecretsManagerProvider) getDSN(ctx context.Context, ref *url.URL) (string, error) {
18+
opts := []func(*awsconfig.LoadOptions) error{}
19+
20+
if region := ref.Query().Get("region"); region != "" {
21+
opts = append(opts, awsconfig.WithRegion(region))
22+
}
23+
24+
cfg, err := awsconfig.LoadDefaultConfig(ctx, opts...)
25+
if err != nil {
26+
return "", fmt.Errorf("unable to load AWS config: %w", err)
27+
}
28+
29+
svc := secretsmanager.NewFromConfig(cfg)
30+
31+
secretName := ref.Host
32+
if ref.Path != "" {
33+
secretName += ref.Path
34+
}
35+
36+
result, err := svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
37+
SecretId: aws.String(secretName),
38+
VersionStage: aws.String("AWSCURRENT"),
39+
})
40+
if err != nil {
41+
return "", fmt.Errorf("unable to get secret value: %w", err)
42+
}
43+
44+
return *result.SecretString, nil
45+
}

config/secret_gcp.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
secretmanager "cloud.google.com/go/secretmanager/apiv1"
9+
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
10+
)
11+
12+
type gcpSecretManagerProvider struct{}
13+
14+
// getDSN fetches the secret value from GCP Secret Manager.
15+
// URL format: gcpsecretmanager://projects/my-project/secrets/my-secret
16+
func (p gcpSecretManagerProvider) getDSN(ctx context.Context, ref *url.URL) (string, error) {
17+
client, err := secretmanager.NewClient(ctx)
18+
if err != nil {
19+
return "", fmt.Errorf("unable to create GCP Secret Manager client: %w", err)
20+
}
21+
defer client.Close()
22+
23+
// Reconstruct the full resource name from host+path and append /versions/latest.
24+
secretName := ref.Host + ref.Path + "/versions/latest"
25+
26+
result, err := client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
27+
Name: secretName,
28+
})
29+
if err != nil {
30+
return "", fmt.Errorf("unable to access secret version: %w", err)
31+
}
32+
33+
return string(result.Payload.Data), nil
34+
}

config/secret_resolver.go

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,54 @@ import (
66
"fmt"
77
"log/slog"
88
"net/url"
9-
10-
"gocloud.dev/runtimevar"
11-
12-
// Register awssecretsmanager and gcpsecretmanager URL openers.
13-
_ "gocloud.dev/runtimevar/awssecretsmanager"
14-
_ "gocloud.dev/runtimevar/gcpsecretmanager"
159
)
1610

17-
// secretPayload is the expected JSON structure of the secret value in the store.
18-
type secretPayload struct {
19-
DSN string `json:"data_source_name"`
11+
// secretProvider is the interface that all secret store backends must implement.
12+
type secretProvider interface {
13+
getDSN(ctx context.Context, ref *url.URL) (string, error)
14+
}
15+
16+
// secretProviders is the registry of supported secret store URL schemes.
17+
var secretProviders = map[string]secretProvider{
18+
"awssecretsmanager": awsSecretsManagerProvider{},
19+
"gcpsecretmanager": gcpSecretManagerProvider{},
20+
"hashivault": vaultProvider{},
2021
}
2122

22-
// resolveSecretDSN checks if the given value is a secret store URL
23-
// (awssecretsmanager:// or gcpsecretmanager://) and if so fetches and
24-
// returns the DSN from the secret. Otherwise it returns the value unchanged.
23+
// resolveSecretDSN checks if the given value is a secret store URL and if so fetches and returns the DSN. Otherwise it
24+
// returns the value unchanged.
2525
func resolveSecretDSN(ctx context.Context, value string) (string, error) {
2626
u, err := url.Parse(value)
27-
if err != nil || (u.Scheme != "awssecretsmanager" && u.Scheme != "gcpsecretmanager") {
27+
if err != nil {
2828
return value, nil
2929
}
3030

31-
slog.Debug("resolving DSN from secret store", "url", value)
32-
33-
v, err := runtimevar.OpenVariable(ctx, value)
34-
if err != nil {
35-
return "", fmt.Errorf("failed to open secret variable %q: %w", value, err)
31+
provider, ok := secretProviders[u.Scheme]
32+
if !ok {
33+
return value, nil
3634
}
37-
defer v.Close()
3835

39-
snapshot, err := v.Latest(ctx)
36+
slog.Debug("resolving DSN from secret store", "scheme", u.Scheme, "ref", u.Host+u.Path)
37+
38+
raw, err := provider.getDSN(ctx, u)
4039
if err != nil {
41-
return "", fmt.Errorf("failed to fetch secret %q: %w", value, err)
40+
return "", fmt.Errorf("failed to resolve secret %q: %w", value, err)
4241
}
4342

44-
// The secret value may be either a plain DSN string or a JSON object
45-
// with a "data_source_name" key (backward compatible with old AWS SM format).
46-
switch val := snapshot.Value.(type) {
47-
case string:
48-
// Try JSON first, fall back to treating it as a plain DSN string.
49-
var payload secretPayload
50-
if jsonErr := json.Unmarshal([]byte(val), &payload); jsonErr == nil && payload.DSN != "" {
51-
return payload.DSN, nil
43+
// If the raw value is a JSON object, extract the key specified by the "key" query param, falling back to
44+
// "data_source_name" for backward compatibility. If it's not JSON, return the raw value as-is.
45+
var payload map[string]string
46+
if jsonErr := json.Unmarshal([]byte(raw), &payload); jsonErr == nil {
47+
key := u.Query().Get("key")
48+
if key == "" {
49+
key = "data_source_name"
50+
}
51+
val, ok := payload[key]
52+
if !ok {
53+
return "", fmt.Errorf("key %q not found in secret %q", key, value)
5254
}
5355
return val, nil
54-
default:
55-
return "", fmt.Errorf("unexpected secret value type %T for %q", snapshot.Value, value)
5656
}
57+
58+
return raw, nil
5759
}

config/secret_vault.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
vault "github.com/hashicorp/vault/api"
9+
)
10+
11+
type vaultProvider struct{}
12+
13+
// getDSN fetches the secret value from HashiCorp Vault KV engine.
14+
// URL format: hashivault://mount/path?key=data_source_name&engine_version=2
15+
func (p vaultProvider) getDSN(ctx context.Context, ref *url.URL) (string, error) {
16+
cfg := vault.DefaultConfig()
17+
if err := cfg.ReadEnvironment(); err != nil {
18+
return "", fmt.Errorf("unable to read Vault environment: %w", err)
19+
}
20+
21+
client, err := vault.NewClient(cfg)
22+
if err != nil {
23+
return "", fmt.Errorf("unable to create Vault client: %w", err)
24+
}
25+
26+
q := ref.Query()
27+
28+
engineVersion := "2"
29+
if v := q.Get("engine_version"); v != "" {
30+
engineVersion = v
31+
}
32+
33+
secretPath := ref.Host + ref.Path
34+
35+
var secret *vault.KVSecret
36+
switch engineVersion {
37+
case "1":
38+
secret, err = client.KVv1(ref.Host).Get(ctx, ref.Path)
39+
default:
40+
secret, err = client.KVv2(ref.Host).Get(ctx, ref.Path)
41+
}
42+
if err != nil {
43+
return "", fmt.Errorf("unable to read Vault secret at %q: %w", secretPath, err)
44+
}
45+
46+
// key query param specifies which field to extract, defaults to "data_source_name".
47+
key := q.Get("key")
48+
if key == "" {
49+
key = "data_source_name"
50+
}
51+
52+
val, ok := secret.Data[key]
53+
if !ok {
54+
return "", fmt.Errorf("key %q not found in Vault secret at %q", key, secretPath)
55+
}
56+
57+
str, ok := val.(string)
58+
if !ok {
59+
return "", fmt.Errorf("key %q in Vault secret at %q is not a string", key, secretPath)
60+
}
61+
62+
return str, nil
63+
}

go.mod

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ module github.com/burningalchemist/sql_exporter
33
go 1.24.0
44

55
require (
6+
cloud.google.com/go/secretmanager v1.16.0
67
github.com/ClickHouse/clickhouse-go/v2 v2.43.0
78
github.com/aws/aws-sdk-go-v2 v1.41.2
89
github.com/aws/aws-sdk-go-v2/config v1.32.10
910
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.2
1011
github.com/go-sql-driver/mysql v1.9.3
12+
github.com/hashicorp/vault/api v1.22.0
1113
github.com/jackc/pgx/v5 v5.8.0
1214
github.com/kardianos/minwinsvc v1.0.2
1315
github.com/lib/pq v1.11.2
@@ -26,11 +28,11 @@ require (
2628
)
2729

2830
require (
31+
cloud.google.com/go v0.123.0 // indirect
2932
cloud.google.com/go/auth v0.17.0 // indirect
3033
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
3134
cloud.google.com/go/compute/metadata v0.9.0 // indirect
3235
cloud.google.com/go/iam v1.5.3 // indirect
33-
cloud.google.com/go/secretmanager v1.16.0 // indirect
3436
filippo.io/edwards25519 v1.1.1 // indirect
3537
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
3638
github.com/99designs/keyring v1.2.2 // indirect
@@ -63,16 +65,20 @@ require (
6365
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
6466
github.com/aws/smithy-go v1.24.1 // indirect
6567
github.com/beorn7/perks v1.0.1 // indirect
68+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
6669
github.com/cespare/xxhash/v2 v2.3.0 // indirect
70+
github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e // indirect
6771
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
6872
github.com/danieljoos/wincred v1.2.2 // indirect
6973
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
7074
github.com/elastic/go-sysinfo v1.8.1 // indirect
7175
github.com/elastic/go-windows v1.0.0 // indirect
76+
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
7277
github.com/felixge/httpsnoop v1.0.4 // indirect
7378
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
7479
github.com/go-faster/city v1.0.1 // indirect
7580
github.com/go-faster/errors v0.7.1 // indirect
81+
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
7682
github.com/go-logr/logr v1.4.3 // indirect
7783
github.com/go-logr/stdr v1.2.2 // indirect
7884
github.com/goccy/go-json v0.10.5 // indirect
@@ -84,10 +90,18 @@ require (
8490
github.com/google/flatbuffers v25.2.10+incompatible // indirect
8591
github.com/google/s2a-go v0.1.9 // indirect
8692
github.com/google/uuid v1.6.0 // indirect
87-
github.com/google/wire v0.7.0 // indirect
8893
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
8994
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
9095
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
96+
github.com/hashicorp/errwrap v1.1.0 // indirect
97+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
98+
github.com/hashicorp/go-multierror v1.1.1 // indirect
99+
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
100+
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
101+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
102+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
103+
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
104+
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
91105
github.com/jackc/pgpassfile v1.0.0 // indirect
92106
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
93107
github.com/jackc/puddle/v2 v2.2.2 // indirect
@@ -101,6 +115,8 @@ require (
101115
github.com/mdlayher/vsock v1.2.1 // indirect
102116
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
103117
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
118+
github.com/mitchellh/go-homedir v1.1.0 // indirect
119+
github.com/mitchellh/mapstructure v1.5.0 // indirect
104120
github.com/mtibben/percent v0.2.1 // indirect
105121
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
106122
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
@@ -109,6 +125,7 @@ require (
109125
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
110126
github.com/pkg/errors v0.9.1 // indirect
111127
github.com/prometheus/procfs v0.16.1 // indirect
128+
github.com/ryanuber/go-glob v1.0.0 // indirect
112129
github.com/segmentio/asm v1.2.1 // indirect
113130
github.com/shopspring/decimal v1.4.0 // indirect
114131
github.com/sirupsen/logrus v1.9.3 // indirect
@@ -123,7 +140,6 @@ require (
123140
go.opentelemetry.io/otel/trace v1.40.0 // indirect
124141
go.yaml.in/yaml/v2 v2.4.3 // indirect
125142
go.yaml.in/yaml/v3 v3.0.4 // indirect
126-
gocloud.dev v0.45.0 // indirect
127143
golang.org/x/crypto v0.46.0 // indirect
128144
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
129145
golang.org/x/mod v0.30.0 // indirect

0 commit comments

Comments
 (0)