feat: optional S3-compatible storage for uploaded images, instead of the local upload directory
This commit is contained in:
1 parent
d752a61e52
commit
eb9bfabed5
23 files changed
+1962
-7
No files matched your search
@@ -0,0 +1,114 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
StorageDriverLocal = "local"
|
||||
StorageDriverS3 = "s3"
|
||||
|
||||
DefaultStorageDriver = StorageDriverLocal
|
||||
DefaultS3Region = "us-east-1"
|
||||
DefaultS3PathStyle = "false"
|
||||
DefaultS3TLS = "true"
|
||||
EnvStorageDriver = "STORAGE_DRIVER"
|
||||
envS3Bucket = "S3_BUCKET"
|
||||
envS3Region = "S3_REGION"
|
||||
envS3Endpoint = "S3_ENDPOINT"
|
||||
envS3AccessKeyID = "S3_ACCESS_KEY_ID"
|
||||
envS3SecretKey = "S3_SECRET_ACCESS_KEY"
|
||||
envS3KeyPrefix = "S3_KEY_PREFIX"
|
||||
envS3PathStyle = "S3_PATH_STYLE"
|
||||
envS3TLS = "S3_TLS"
|
||||
)
|
||||
|
||||
var s3RequiredEnvVars = []string{envS3Bucket}
|
||||
|
||||
type S3Settings struct {
|
||||
Bucket string
|
||||
Region string
|
||||
Endpoint string
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
KeyPrefix string
|
||||
PathStyle bool
|
||||
TLS bool
|
||||
}
|
||||
|
||||
type StorageSettings struct {
|
||||
Driver string
|
||||
S3 S3Settings
|
||||
}
|
||||
|
||||
func StorageRequiredEnvVars(driver string) []string {
|
||||
if driver == StorageDriverS3 {
|
||||
return slices.Clone(s3RequiredEnvVars)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storageSettingsFromEnv() (StorageSettings, error) {
|
||||
driver := envOrDefault(EnvStorageDriver, DefaultStorageDriver)
|
||||
switch driver {
|
||||
case StorageDriverLocal:
|
||||
return StorageSettings{Driver: driver}, nil
|
||||
case StorageDriverS3:
|
||||
s3, err := s3SettingsFromEnv()
|
||||
if err != nil {
|
||||
return StorageSettings{}, err
|
||||
}
|
||||
return StorageSettings{Driver: driver, S3: s3}, nil
|
||||
default:
|
||||
return StorageSettings{}, fmt.Errorf("unsupported storage driver %q", driver)
|
||||
}
|
||||
}
|
||||
|
||||
func s3SettingsFromEnv() (S3Settings, error) {
|
||||
if err := MissingEnvVarError(s3RequiredEnvVars, nonEmptyEnvLookup); err != nil {
|
||||
return S3Settings{}, err
|
||||
}
|
||||
accessKeyID := os.Getenv(envS3AccessKeyID)
|
||||
secretKey := os.Getenv(envS3SecretKey)
|
||||
if accessKeyID != "" && secretKey == "" {
|
||||
return S3Settings{}, fmt.Errorf("Environment variable %s is not set (it is required when %s is set)", envS3SecretKey, envS3AccessKeyID)
|
||||
}
|
||||
if secretKey != "" && accessKeyID == "" {
|
||||
return S3Settings{}, fmt.Errorf("Environment variable %s is not set (it is required when %s is set)", envS3AccessKeyID, envS3SecretKey)
|
||||
}
|
||||
pathStyle, err := boolEnv(envS3PathStyle, DefaultS3PathStyle)
|
||||
if err != nil {
|
||||
return S3Settings{}, err
|
||||
}
|
||||
tls, err := boolEnv(envS3TLS, DefaultS3TLS)
|
||||
if err != nil {
|
||||
return S3Settings{}, err
|
||||
}
|
||||
return S3Settings{
|
||||
Bucket: os.Getenv(envS3Bucket),
|
||||
Region: envOrDefault(envS3Region, DefaultS3Region),
|
||||
Endpoint: os.Getenv(envS3Endpoint),
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretKey,
|
||||
KeyPrefix: os.Getenv(envS3KeyPrefix),
|
||||
PathStyle: pathStyle,
|
||||
TLS: tls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func nonEmptyEnvLookup(name string) (string, bool) {
|
||||
value := os.Getenv(name)
|
||||
return value, value != ""
|
||||
}
|
||||
|
||||
func boolEnv(name string, fallback string) (bool, error) {
|
||||
raw := envOrDefault(name, fallback)
|
||||
value, err := strconv.ParseBool(raw)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s must be true or false, got %q", name, raw)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
Reference in new issue
Block a user