193 lines
6.9 KiB
Go
193 lines
6.9 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"gitea.maximumdirect.net/eric/distributor/internal/adapters/local"
|
|
s3adapter "gitea.maximumdirect.net/eric/distributor/internal/adapters/s3"
|
|
sshadapter "gitea.maximumdirect.net/eric/distributor/internal/adapters/ssh"
|
|
"gitea.maximumdirect.net/eric/distributor/internal/config"
|
|
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
|
)
|
|
|
|
const storagePathKey = "path"
|
|
|
|
const (
|
|
sshHostKey = "host"
|
|
sshUserKey = "user"
|
|
sshPortKey = "port"
|
|
sshKeyFileKey = "ssh_key_file"
|
|
sshKnownHostsKey = "known_hosts"
|
|
sshHostKeyPolicyKey = "host_key_policy"
|
|
sshReadOnlyHostsKey = "read_only_known_hosts"
|
|
s3EndpointKey = "endpoint"
|
|
s3BucketKey = "bucket"
|
|
s3PrefixKey = "prefix"
|
|
s3RegionKey = "region"
|
|
s3ForcePathStyleKey = "force_path_style"
|
|
s3AccessKeyIDKey = "access_key_id"
|
|
s3SecretAccessKey = "secret_access_key"
|
|
)
|
|
|
|
type backendFactory struct {
|
|
registry *storage.Registry
|
|
environment config.Environment
|
|
readOnlyKnownHosts bool
|
|
}
|
|
|
|
func newBackendFactory() *backendFactory {
|
|
return newBackendFactoryWithEnvironment(config.ProcessEnvironment())
|
|
}
|
|
|
|
func newBackendFactoryWithEnvironment(environment config.Environment) *backendFactory {
|
|
registry := storage.NewRegistry()
|
|
_ = registry.Register(config.BackendLocal, func(ctx context.Context, cfg storage.OpenConfig) (storage.Backend, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return local.New(cfg[storagePathKey])
|
|
})
|
|
_ = registry.Register(config.BackendSSH, func(ctx context.Context, cfg storage.OpenConfig) (storage.Backend, error) {
|
|
port, err := strconv.Atoi(cfg[sshPortKey])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ssh port: %w", err)
|
|
}
|
|
readOnlyKnownHosts := false
|
|
if raw := cfg[sshReadOnlyHostsKey]; raw != "" {
|
|
readOnlyKnownHosts, err = strconv.ParseBool(raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ssh read_only_known_hosts: %w", err)
|
|
}
|
|
}
|
|
return sshadapter.New(ctx, sshadapter.Options{
|
|
Host: cfg[sshHostKey],
|
|
User: cfg[sshUserKey],
|
|
Port: port,
|
|
Root: cfg[storagePathKey],
|
|
KeyFile: cfg[sshKeyFileKey],
|
|
KnownHosts: cfg[sshKnownHostsKey],
|
|
HostKeyPolicy: sshadapter.HostKeyPolicy(cfg[sshHostKeyPolicyKey]),
|
|
ReadOnlyKnownHosts: readOnlyKnownHosts,
|
|
})
|
|
})
|
|
_ = registry.Register(config.BackendS3, func(ctx context.Context, cfg storage.OpenConfig) (storage.Backend, error) {
|
|
forcePathStyle, err := strconv.ParseBool(cfg[s3ForcePathStyleKey])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("s3 force_path_style: %w", err)
|
|
}
|
|
return s3adapter.New(ctx, s3adapter.Options{
|
|
Endpoint: cfg[s3EndpointKey],
|
|
Bucket: cfg[s3BucketKey],
|
|
Prefix: cfg[s3PrefixKey],
|
|
Region: cfg[s3RegionKey],
|
|
ForcePathStyle: forcePathStyle,
|
|
AccessKeyID: cfg[s3AccessKeyIDKey],
|
|
SecretAccessKey: cfg[s3SecretAccessKey],
|
|
})
|
|
})
|
|
return &backendFactory{registry: registry, environment: environment}
|
|
}
|
|
|
|
func (f *backendFactory) openSource(ctx context.Context, source config.Backend) (storage.Backend, error) {
|
|
if source.Backend != config.BackendLocal && source.Backend != config.BackendSSH && source.Backend != config.BackendS3 {
|
|
return nil, fmt.Errorf("source backend %s is not implemented for execution", source.Backend)
|
|
}
|
|
openConfig, err := f.sourceOpenConfig(source)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f.registry.Open(ctx, source.Backend, openConfig)
|
|
}
|
|
|
|
func (f *backendFactory) openDestination(ctx context.Context, destination config.Destination) (storage.Backend, error) {
|
|
if destination.Backend != config.BackendLocal && destination.Backend != config.BackendSSH && destination.Backend != config.BackendS3 {
|
|
return nil, fmt.Errorf("backend %s is not implemented for execution", destination.Backend)
|
|
}
|
|
openConfig, err := f.destinationOpenConfig(destination)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f.registry.Open(ctx, destination.Backend, openConfig)
|
|
}
|
|
|
|
func (f *backendFactory) openLocalPath(ctx context.Context, path string) (storage.Backend, error) {
|
|
return f.registry.Open(ctx, config.BackendLocal, storage.OpenConfig{storagePathKey: path})
|
|
}
|
|
|
|
func (f *backendFactory) resolveCredentials(creds config.Credentials) (config.ResolvedCredentials, error) {
|
|
return f.environment.ResolveCredentials(creds)
|
|
}
|
|
|
|
func (f *backendFactory) sourceOpenConfig(source config.Backend) (storage.OpenConfig, error) {
|
|
cfg := sourceOpenConfig(source)
|
|
if source.Backend == config.BackendS3 {
|
|
if err := f.addS3Config(cfg, source.Endpoint, source.Bucket, source.Prefix, source.Region, source.ForcePath, source.Creds); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if source.Backend == config.BackendSSH {
|
|
cfg[sshReadOnlyHostsKey] = strconv.FormatBool(f.readOnlyKnownHosts)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (f *backendFactory) destinationOpenConfig(destination config.Destination) (storage.OpenConfig, error) {
|
|
cfg := destinationOpenConfig(destination)
|
|
if destination.Backend == config.BackendS3 {
|
|
if err := f.addS3Config(cfg, destination.Endpoint, destination.Bucket, destination.Prefix, destination.Region, destination.ForcePath, destination.Creds); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if destination.Backend == config.BackendSSH {
|
|
cfg[sshReadOnlyHostsKey] = strconv.FormatBool(f.readOnlyKnownHosts)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (f *backendFactory) addS3Config(cfg storage.OpenConfig, endpoint, bucket, prefix, region string, forcePath *bool, creds config.Credentials) error {
|
|
cfg[s3EndpointKey] = endpoint
|
|
cfg[s3BucketKey] = bucket
|
|
cfg[s3PrefixKey] = prefix
|
|
cfg[s3RegionKey] = region
|
|
cfg[s3ForcePathStyleKey] = strconv.FormatBool(config.ForcePathStyle(forcePath))
|
|
if creds.AccessKeyIDEnv != "" || creds.SecretAccessKeyEnv != "" {
|
|
resolved, err := f.resolveCredentials(creds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg[s3AccessKeyIDKey] = resolved.AccessKeyID
|
|
cfg[s3SecretAccessKey] = resolved.SecretAccessKey
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sourceOpenConfig(source config.Backend) storage.OpenConfig {
|
|
cfg := storage.OpenConfig{storagePathKey: source.Path}
|
|
if source.Backend == config.BackendSSH {
|
|
cfg[sshHostKey] = source.Host
|
|
cfg[sshUserKey] = source.User
|
|
cfg[sshPortKey] = strconv.Itoa(source.Port)
|
|
cfg[sshKeyFileKey] = source.SSH.KeyFile
|
|
cfg[sshKnownHostsKey] = source.SSH.KnownHosts
|
|
cfg[sshHostKeyPolicyKey] = string(source.SSH.HostKeyPolicy)
|
|
cfg[sshReadOnlyHostsKey] = "false"
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func destinationOpenConfig(destination config.Destination) storage.OpenConfig {
|
|
cfg := storage.OpenConfig{storagePathKey: destination.Path}
|
|
if destination.Backend == config.BackendSSH {
|
|
cfg[sshHostKey] = destination.Host
|
|
cfg[sshUserKey] = destination.User
|
|
cfg[sshPortKey] = strconv.Itoa(destination.Port)
|
|
cfg[sshKeyFileKey] = destination.SSH.KeyFile
|
|
cfg[sshKnownHostsKey] = destination.SSH.KnownHosts
|
|
cfg[sshHostKeyPolicyKey] = string(destination.SSH.HostKeyPolicy)
|
|
cfg[sshReadOnlyHostsKey] = "false"
|
|
}
|
|
return cfg
|
|
}
|