-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfactory.go
More file actions
147 lines (132 loc) · 6.08 KB
/
Copy pathfactory.go
File metadata and controls
147 lines (132 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package storageprovider
import (
"fmt"
"strings"
)
// Canonical backend identifiers. These are the strings every layer (api,
// worker, provisioner, OpenAPI docs, k8s ConfigMaps) should compare against.
//
// BackendDOSpaces is the one canonical identifier for the DO Spaces backend.
// BackendSharedKey is kept as a deprecated alias because earlier `api/config.go`
// emitted "shared-key" / "shared-master-key" — those strings appear in some
// k8s manifests + audit logs. NormalizeBackend collapses both aliases to
// BackendDOSpaces, so they reach the same implementation; new code should
// use BackendDOSpaces and tooling should migrate operator configs over time.
const (
BackendDOSpaces = "do-spaces"
BackendR2 = "r2"
BackendS3 = "s3"
BackendMinIO = "minio"
// Deprecated: use [BackendDOSpaces]. "shared-key" is the legacy alias
// emitted by older api/config.go revisions and survives only so existing
// OBJECT_STORE_BACKEND=shared-key manifests keep working. NormalizeBackend
// collapses it to BackendDOSpaces. Will be removed once all operator
// manifests have migrated.
BackendSharedKey = "shared-key"
)
// Config is the operator-facing configuration for the storage backend. The
// api wires this from env vars (OBJECT_STORE_* + R2_* + AWS_*) and passes it
// to Factory() at boot. Each provider documents which fields it requires.
type Config struct {
// Backend selects the implementation. One of: "do-spaces", "r2", "s3",
// "minio". Aliases ("digitalocean", "spaces", "shared-key",
// "shared-master-key") collapse to "do-spaces"; "cloudflare" → "r2";
// "aws" → "s3"; "admin" / "iam" → "minio".
//
// REQUIRED. Empty or unrecognised values cause Factory to return
// ErrUnknownBackend so callers fail loudly at boot instead of silently
// degrading to a less-secure backend. This is intentional — defaulting
// empty to a real provider (e.g. minio) has historically masked operator
// misconfiguration (OBJECT_STORE_BACKEND unset in a production manifest)
// until a tenant's data was leaked across the master key.
Backend string
// Shared S3-compatible knobs (all backends).
Endpoint string // host or host:port (no scheme)
PublicURL string // customer-facing URL (with scheme), falls back to Endpoint
Region string // "nyc3", "auto", "us-east-1"
Bucket string // shared bucket name; default "instant-shared"
MasterKey string // OBJECT_STORE_ACCESS_KEY (root credential)
MasterSecret string // OBJECT_STORE_SECRET_KEY (root credential)
UseTLS bool // true for DO Spaces / R2 / S3; false for in-cluster MinIO
// R2-specific.
R2AccountID string // CF_ACCOUNT_ID / R2_ACCOUNT_ID
R2APIToken string // R2_API_TOKEN — required for IssueTenantCredentials
// S3-specific.
AWSRoleARN string // IAM role to AssumeRole into for per-tenant sessions
// MinIO-specific (alias of MasterKey/MasterSecret but operators sometimes
// supply MINIO_ROOT_USER / MINIO_ROOT_PASSWORD instead).
MinIORootUser string
MinIORootPassword string
}
// NormalizeBackend maps the operator-facing value (with all the historical
// aliases) onto one of the four canonical backend strings.
//
// Alias notes (2026-05-20 DOC-REALITY-DELTA-2026-05-20 close-out):
// "shared-key" collapses to "do-spaces" because prod was deployed with
// OBJECT_STORE_BACKEND=shared-key (the older `api/internal/config.go`
// legacy mode-resolution naming). The platform's shipped DO Spaces backend
// uses a single master Spaces access key + per-tenant key-prefix isolation
// — the "shared key" describes the underlying credential model, while
// "do-spaces" names the cloud provider. Operators can use either string;
// they resolve to the same implementation. Same applies to "shared-master-key"
// (the storage-mode label surfaced in /storage/new responses).
func NormalizeBackend(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "do-spaces", "do_spaces", "dospaces", "do", "digitalocean", "spaces",
"shared-key", "shared_key", "sharedkey",
"shared-master-key", "shared_master_key":
return "do-spaces"
case "r2", "cloudflare", "cf-r2", "cloudflare-r2":
return "r2"
case "s3", "aws", "aws-s3":
return "s3"
case "minio", "minio-admin", "admin", "iam":
return "minio"
default:
return ""
}
}
// Factory selects and constructs the right StorageCredentialProvider for cfg.
// Returns ErrUnknownBackend when cfg.Backend is unrecognised, so the caller
// can fail loudly instead of silently degrading to a less-secure backend.
//
// To keep `common` zero-dep on cloud SDKs (so import-graph stays cheap for
// every consumer), the actual provider implementations live in subpackages
// that register themselves via init(). Factory consults the global registry
// populated by those inits.
func Factory(cfg Config) (StorageCredentialProvider, error) {
name := NormalizeBackend(cfg.Backend)
if name == "" {
return nil, fmt.Errorf("%w: %q", ErrUnknownBackend, cfg.Backend)
}
ctor, ok := lookupBuilder(name)
if !ok {
return nil, fmt.Errorf("%w: %q (no implementation registered — did you import the impl package?)", ErrUnknownBackend, name)
}
return ctor(cfg)
}
// Builder is the constructor signature every backend implementation
// registers with the global registry via Register. The api / worker import
// the impl subpackages they want available — that way `common` stays free of
// cloud-SDK transitive deps for tooling that doesn't need them.
type Builder func(cfg Config) (StorageCredentialProvider, error)
var builders = map[string]Builder{}
// Register adds a Builder under name. Called from each provider package's
// init(). Idempotent — a second registration with the same name silently
// overwrites the first (used in tests to inject a fake).
func Register(name string, b Builder) {
builders[NormalizeBackend(name)] = b
}
func lookupBuilder(name string) (Builder, bool) {
b, ok := builders[name]
return b, ok
}
// ListRegistered returns the names of every backend currently registered.
// Used by the registry-iterating contract test.
func ListRegistered() []string {
out := make([]string, 0, len(builders))
for k := range builders {
out = append(out, k)
}
return out
}