From 7b86f7546b2ae760155cf8349c3b555dacb5277a Mon Sep 17 00:00:00 2001 From: Jason Lernerman Date: Fri, 4 Sep 2026 13:20:37 -0400 Subject: [PATCH] Move config structs into a leaf storage/config package Consumers that only embed S3Config & co. in their own config (e.g. livekit/cloud's pkg/config, and transitively cloud-config's validator) currently pull in every provider SDK: ~390 packages and ~500 MB of module cache for one struct field. The root package keeps the same API via type aliases; New dispatches with a type switch instead of an unexported interface method. --- config.go | 93 +++++++++++++++--------------------------------- config/config.go | 89 +++++++++++++++++++++++++++++++++++++++++++++ storage.go | 2 +- 3 files changed, 119 insertions(+), 65 deletions(-) create mode 100644 config/config.go diff --git a/config.go b/config.go index 4c719c6..9ec3a55 100644 --- a/config.go +++ b/config.go @@ -15,72 +15,37 @@ package storage import ( - "time" + "fmt" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/livekit/storage/config" ) -type Config interface { - newStorage() (Storage, error) -} - -type AliOSSConfig struct { - AccessKey string `yaml:"access_key,omitempty"` - Secret string `yaml:"secret,omitempty"` - Endpoint string `yaml:"endpoint,omitempty"` - Bucket string `yaml:"bucket,omitempty"` -} - -func (c *AliOSSConfig) newStorage() (Storage, error) { return NewAliOSS(c) } - -type AzureConfig struct { - AccountName string `yaml:"account_name,omitempty"` // (env AZURE_STORAGE_ACCOUNT) - AccountKey string `yaml:"account_key,omitempty"` // (env AZURE_STORAGE_KEY) - ContainerName string `yaml:"container_name,omitempty"` - TokenCredential azcore.TokenCredential `yaml:"-"` // required for presigned url generation -} - -func (c *AzureConfig) newStorage() (Storage, error) { return NewAzure(c) } - -type GCPConfig struct { - CredentialsJSON string `yaml:"credentials_json,omitempty"` // (env GOOGLE_APPLICATION_CREDENTIALS) - Bucket string `yaml:"bucket,omitempty"` - ProxyConfig *ProxyConfig `yaml:"proxy_config,omitempty"` -} - -func (c *GCPConfig) newStorage() (Storage, error) { return NewGCP(c) } - -type LocalConfig struct { - StorageDir string `yaml:"storage_dir,omitempty"` -} - -func (c *LocalConfig) newStorage() (Storage, error) { return NewLocal(c) } - -type S3Config struct { - AccessKey string `yaml:"access_key,omitempty"` - Secret string `yaml:"secret,omitempty"` - SessionToken string `yaml:"session_token,omitempty"` - AssumeRoleArn string `yaml:"assume_role_arn,omitempty"` // ARN of the role to assume for file upload. Egress will make an AssumeRole API call using the provided access_key and secret to assume that role - AssumeRoleExternalId string `yaml:"assume_role_external_id,omitempty"` // ExternalID to use when assuming role for upload - Region string `yaml:"region,omitempty"` - Endpoint string `yaml:"endpoint,omitempty"` - Bucket string `yaml:"bucket,omitempty"` - ForcePathStyle bool `yaml:"force_path_style,omitempty"` - ProxyConfig *ProxyConfig `yaml:"proxy_config,omitempty"` - - MaxRetries int `yaml:"max_retries,omitempty"` - MaxRetryDelay time.Duration `yaml:"max_retry_delay,omitempty"` - MinRetryDelay time.Duration `yaml:"min_retry_delay,omitempty"` - - Metadata map[string]string `yaml:"metadata,omitempty"` - Tagging string `yaml:"tagging,omitempty"` - ContentDisposition string `yaml:"content_disposition,omitempty"` -} - -func (c *S3Config) newStorage() (Storage, error) { return NewS3(c) } +// The config structs live in the storage/config package so that consumers which +// only embed them in their own config (and never create a Storage) don't pull +// in every provider SDK. These aliases keep the existing API. +type ( + Config = config.Config + AliOSSConfig = config.AliOSSConfig + AzureConfig = config.AzureConfig + GCPConfig = config.GCPConfig + LocalConfig = config.LocalConfig + S3Config = config.S3Config + ProxyConfig = config.ProxyConfig +) -type ProxyConfig struct { - Url string `yaml:"url,omitempty"` - Username string `yaml:"username,omitempty"` - Password string `yaml:"password,omitempty"` +func newStorage(conf Config) (Storage, error) { + switch c := conf.(type) { + case *AliOSSConfig: + return NewAliOSS(c) + case *AzureConfig: + return NewAzure(c) + case *GCPConfig: + return NewGCP(c) + case *LocalConfig: + return NewLocal(c) + case *S3Config: + return NewS3(c) + default: + return nil, fmt.Errorf("unsupported storage config type %T", conf) + } } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..e04f139 --- /dev/null +++ b/config/config.go @@ -0,0 +1,89 @@ +// Copyright 2024 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// Config is implemented by every provider config in this package. The marker +// method keeps storage.New's argument type-checked while letting these structs +// live in a package that pulls in no provider SDKs. +type Config interface { + storageConfig() +} + +type AliOSSConfig struct { + AccessKey string `yaml:"access_key,omitempty"` + Secret string `yaml:"secret,omitempty"` + Endpoint string `yaml:"endpoint,omitempty"` + Bucket string `yaml:"bucket,omitempty"` +} + +func (*AliOSSConfig) storageConfig() {} + +type AzureConfig struct { + AccountName string `yaml:"account_name,omitempty"` // (env AZURE_STORAGE_ACCOUNT) + AccountKey string `yaml:"account_key,omitempty"` // (env AZURE_STORAGE_KEY) + ContainerName string `yaml:"container_name,omitempty"` + TokenCredential azcore.TokenCredential `yaml:"-"` // required for presigned url generation +} + +func (*AzureConfig) storageConfig() {} + +type GCPConfig struct { + CredentialsJSON string `yaml:"credentials_json,omitempty"` // (env GOOGLE_APPLICATION_CREDENTIALS) + Bucket string `yaml:"bucket,omitempty"` + ProxyConfig *ProxyConfig `yaml:"proxy_config,omitempty"` +} + +func (*GCPConfig) storageConfig() {} + +type LocalConfig struct { + StorageDir string `yaml:"storage_dir,omitempty"` +} + +func (*LocalConfig) storageConfig() {} + +type S3Config struct { + AccessKey string `yaml:"access_key,omitempty"` + Secret string `yaml:"secret,omitempty"` + SessionToken string `yaml:"session_token,omitempty"` + AssumeRoleArn string `yaml:"assume_role_arn,omitempty"` // ARN of the role to assume for file upload. Egress will make an AssumeRole API call using the provided access_key and secret to assume that role + AssumeRoleExternalId string `yaml:"assume_role_external_id,omitempty"` // ExternalID to use when assuming role for upload + Region string `yaml:"region,omitempty"` + Endpoint string `yaml:"endpoint,omitempty"` + Bucket string `yaml:"bucket,omitempty"` + ForcePathStyle bool `yaml:"force_path_style,omitempty"` + ProxyConfig *ProxyConfig `yaml:"proxy_config,omitempty"` + + MaxRetries int `yaml:"max_retries,omitempty"` + MaxRetryDelay time.Duration `yaml:"max_retry_delay,omitempty"` + MinRetryDelay time.Duration `yaml:"min_retry_delay,omitempty"` + + Metadata map[string]string `yaml:"metadata,omitempty"` + Tagging string `yaml:"tagging,omitempty"` + ContentDisposition string `yaml:"content_disposition,omitempty"` +} + +func (*S3Config) storageConfig() {} + +type ProxyConfig struct { + Url string `yaml:"url,omitempty"` + Username string `yaml:"username,omitempty"` + Password string `yaml:"password,omitempty"` +} diff --git a/storage.go b/storage.go index cbc3118..0317964 100644 --- a/storage.go +++ b/storage.go @@ -32,5 +32,5 @@ type Storage interface { } func New(conf Config) (Storage, error) { - return conf.newStorage() + return newStorage(conf) }