Add app backend factory wiring
This commit is contained in:
@@ -14,7 +14,7 @@ Entries report a logical path, type, and size when available. Entry types are `f
|
|||||||
|
|
||||||
Core packages should depend on `internal/storage`, not adapter packages. Adapter-specific path handling stays behind backend implementations.
|
Core packages should depend on `internal/storage`, not adapter packages. Adapter-specific path handling stays behind backend implementations.
|
||||||
|
|
||||||
The local adapter lives in `internal/adapters/local`. The fake backend lives in `internal/storage/fake` for tests and is not registered for runtime use.
|
The local adapter lives in `internal/adapters/local`. Runtime backend construction is wired through the app-level backend factory and storage registry. The fake backend lives in `internal/storage/fake` for tests and is not registered for runtime use.
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
|
|
||||||
|
|||||||
45
internal/app/backends.go
Normal file
45
internal/app/backends.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/adapters/local"
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/config"
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
const storagePathKey = "path"
|
||||||
|
|
||||||
|
type backendFactory struct {
|
||||||
|
registry *storage.Registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBackendFactory() *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])
|
||||||
|
})
|
||||||
|
return &backendFactory{registry: registry}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *backendFactory) openSource(ctx context.Context, source config.Backend) (storage.Backend, error) {
|
||||||
|
if source.Backend != config.BackendLocal {
|
||||||
|
return nil, fmt.Errorf("source backend %s is not implemented for execution", source.Backend)
|
||||||
|
}
|
||||||
|
return f.registry.Open(ctx, source.Backend, storage.OpenConfig{storagePathKey: source.Path})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *backendFactory) openDestination(ctx context.Context, destination config.Destination) (storage.Backend, error) {
|
||||||
|
if destination.Backend != config.BackendLocal {
|
||||||
|
return nil, fmt.Errorf("backend %s is not implemented for execution", destination.Backend)
|
||||||
|
}
|
||||||
|
return f.registry.Open(ctx, destination.Backend, storage.OpenConfig{storagePathKey: destination.Path})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *backendFactory) openLocalPath(ctx context.Context, path string) (storage.Backend, error) {
|
||||||
|
return f.registry.Open(ctx, config.BackendLocal, storage.OpenConfig{storagePathKey: path})
|
||||||
|
}
|
||||||
72
internal/app/backends_test.go
Normal file
72
internal/app/backends_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackendFactoryOpensLocalSource(t *testing.T) {
|
||||||
|
factory := newBackendFactory()
|
||||||
|
backend, err := factory.openSource(context.Background(), config.Backend{
|
||||||
|
Backend: config.BackendLocal,
|
||||||
|
Path: t.TempDir(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("openSource() error = %v", err)
|
||||||
|
}
|
||||||
|
if backend == nil {
|
||||||
|
t.Fatal("openSource() backend = nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackendFactoryOpensLocalDestination(t *testing.T) {
|
||||||
|
factory := newBackendFactory()
|
||||||
|
backend, err := factory.openDestination(context.Background(), config.Destination{
|
||||||
|
Backend: config.BackendLocal,
|
||||||
|
Path: t.TempDir(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("openDestination() error = %v", err)
|
||||||
|
}
|
||||||
|
if backend == nil {
|
||||||
|
t.Fatal("openDestination() backend = nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackendFactoryOpensDirectLocalPath(t *testing.T) {
|
||||||
|
factory := newBackendFactory()
|
||||||
|
backend, err := factory.openLocalPath(context.Background(), t.TempDir())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("openLocalPath() error = %v", err)
|
||||||
|
}
|
||||||
|
if backend == nil {
|
||||||
|
t.Fatal("openLocalPath() backend = nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackendFactoryRejectsUnsupportedSource(t *testing.T) {
|
||||||
|
factory := newBackendFactory()
|
||||||
|
_, err := factory.openSource(context.Background(), config.Backend{
|
||||||
|
Backend: config.BackendSSH,
|
||||||
|
URI: "ssh://reports@example.com:22",
|
||||||
|
Path: "/reports",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "source backend ssh is not implemented for execution") {
|
||||||
|
t.Fatalf("openSource() error = %v, want not implemented", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackendFactoryRejectsUnsupportedDestination(t *testing.T) {
|
||||||
|
factory := newBackendFactory()
|
||||||
|
_, err := factory.openDestination(context.Background(), config.Destination{
|
||||||
|
Backend: config.BackendS3,
|
||||||
|
Endpoint: "https://s3.example.com",
|
||||||
|
Bucket: "reports",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "backend s3 is not implemented for execution") {
|
||||||
|
t.Fatalf("openDestination() error = %v, want not implemented", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/adapters/local"
|
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ func Inspect(ctx context.Context, options InspectOptions) error {
|
|||||||
if options.Path == "" {
|
if options.Path == "" {
|
||||||
return fmt.Errorf("inspect command requires a path")
|
return fmt.Errorf("inspect command requires a path")
|
||||||
}
|
}
|
||||||
backend, err := local.New(options.Path)
|
backend, err := newBackendFactory().openLocalPath(ctx, options.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/adapters/local"
|
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/config"
|
"gitea.maximumdirect.net/eric/distributor/internal/config"
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/notify"
|
"gitea.maximumdirect.net/eric/distributor/internal/notify"
|
||||||
@@ -44,18 +43,16 @@ func runConfig(ctx context.Context, cfg config.Config, options RunOptions) error
|
|||||||
}
|
}
|
||||||
summary := runSummary{dryRun: options.DryRun}
|
summary := runSummary{dryRun: options.DryRun}
|
||||||
var failures runFailures
|
var failures runFailures
|
||||||
|
backends := newBackendFactory()
|
||||||
if options.Stdout != nil {
|
if options.Stdout != nil {
|
||||||
if _, err := fmt.Fprintf(options.Stdout, "Configured pipelines: %d\n", len(cfg.Pipelines)); err != nil {
|
if _, err := fmt.Fprintf(options.Stdout, "Configured pipelines: %d\n", len(cfg.Pipelines)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, pipeline := range cfg.Pipelines {
|
for _, pipeline := range cfg.Pipelines {
|
||||||
if pipeline.Source.Backend != config.BackendLocal {
|
sourceBackend, err := backends.openSource(ctx, pipeline.Source)
|
||||||
return fmt.Errorf("pipeline %s source backend %s is not implemented for execution", pipeline.ID, pipeline.Source.Backend)
|
|
||||||
}
|
|
||||||
sourceBackend, err := local.New(pipeline.Source.Path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("pipeline %s: %w", pipeline.ID, err)
|
||||||
}
|
}
|
||||||
bundles, err := bundle.Discover(ctx, sourceBackend, "")
|
bundles, err := bundle.Discover(ctx, sourceBackend, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -68,16 +65,7 @@ func runConfig(ctx context.Context, cfg config.Config, options RunOptions) error
|
|||||||
}
|
}
|
||||||
for _, sourceBundle := range bundles {
|
for _, sourceBundle := range bundles {
|
||||||
for _, destination := range pipeline.Destinations {
|
for _, destination := range pipeline.Destinations {
|
||||||
if destination.Backend != config.BackendLocal {
|
destinationBackend, err := backends.openDestination(ctx, destination)
|
||||||
err := fmt.Errorf("backend %s is not implemented for execution", destination.Backend)
|
|
||||||
failures.add(pipeline.ID, destination.ID, displayBundlePath(sourceBundle.RootRelativePath), err)
|
|
||||||
summary.recordFailure()
|
|
||||||
if options.Stdout != nil {
|
|
||||||
writeErrorLine(options.Stdout, sourceBundle.RootRelativePath, destination.ID, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
destinationBackend, err := local.New(destination.Path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failures.add(pipeline.ID, destination.ID, displayBundlePath(sourceBundle.RootRelativePath), err)
|
failures.add(pipeline.ID, destination.ID, displayBundlePath(sourceBundle.RootRelativePath), err)
|
||||||
summary.recordFailure()
|
summary.recordFailure()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/adapters/local"
|
|
||||||
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ func Validate(ctx context.Context, options ValidateOptions) error {
|
|||||||
if options.Path == "" {
|
if options.Path == "" {
|
||||||
return fmt.Errorf("validate command requires a path")
|
return fmt.Errorf("validate command requires a path")
|
||||||
}
|
}
|
||||||
backend, err := local.New(options.Path)
|
backend, err := newBackendFactory().openLocalPath(ctx, options.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user