Files

307 lines
10 KiB
Go
Raw Permalink Normal View History

2026-05-31 01:47:39 +00:00
package app
import (
"context"
"fmt"
"io"
2026-05-31 02:21:36 +00:00
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
"gitea.maximumdirect.net/eric/distributor/internal/config"
2026-05-31 02:33:09 +00:00
"gitea.maximumdirect.net/eric/distributor/internal/notify"
2026-05-31 03:41:56 +00:00
"gitea.maximumdirect.net/eric/distributor/internal/storage"
2026-05-31 01:47:39 +00:00
)
type RunOptions struct {
ConfigPath string
DryRun bool
Force bool
Stdout io.Writer
OutputFormat OutputFormat
Notifier notify.Notifier
}
2026-06-03 11:46:44 +00:00
type RunPipelineOptions struct {
ConfigPath string
PipelineID string
DryRun bool
Force bool
Notifier notify.Notifier
}
2026-06-03 15:10:07 +00:00
type RunPipelineWithLocalSourceOptions struct {
ConfigPath string
PipelineID string
SourceRoot string
DryRun bool
Force bool
Notifier notify.Notifier
}
func Run(ctx context.Context, options RunOptions) error {
if err := ValidateOutputFormat(options.OutputFormat); err != nil {
return err
}
if err := ctx.Err(); err != nil {
return err
}
2026-06-04 00:22:09 +00:00
setup, err := loadRuntimeSetup(options.ConfigPath)
if err != nil {
return err
}
2026-06-04 00:22:09 +00:00
return runSetup(ctx, setup, options)
}
2026-05-31 01:47:39 +00:00
2026-06-03 11:46:44 +00:00
func RunPipeline(ctx context.Context, options RunPipelineOptions) (RunReport, error) {
if err := ctx.Err(); err != nil {
return RunReport{}, err
}
2026-06-04 00:22:09 +00:00
setup, err := loadRuntimeSetup(options.ConfigPath)
2026-06-03 11:46:44 +00:00
if err != nil {
return RunReport{}, err
}
2026-06-04 00:22:09 +00:00
return runPipelineSetup(ctx, setup, options)
2026-06-03 11:46:44 +00:00
}
2026-06-03 15:10:07 +00:00
func RunPipelineWithLocalSource(ctx context.Context, options RunPipelineWithLocalSourceOptions) (RunReport, error) {
if err := ctx.Err(); err != nil {
return RunReport{}, err
}
if options.SourceRoot == "" {
return RunReport{}, fmt.Errorf("source root is required")
}
2026-06-04 00:22:09 +00:00
setup, err := loadRuntimeSetup(options.ConfigPath)
2026-06-03 15:10:07 +00:00
if err != nil {
return RunReport{}, err
}
2026-06-04 00:22:09 +00:00
return runPipelineSetupWithLocalSource(ctx, setup, options)
2026-06-03 15:10:07 +00:00
}
2026-05-31 02:21:36 +00:00
func runConfig(ctx context.Context, cfg config.Config, options RunOptions) error {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return err
}
return runSetupWithBackendFactory(ctx, setup, options, newBackendFactoryWithEnvironment)
2026-05-31 17:17:49 +00:00
}
type backendFactoryProvider func(config.Environment) *backendFactory
2026-06-03 11:46:44 +00:00
func runPipelineConfig(ctx context.Context, cfg config.Config, options RunPipelineOptions) (RunReport, error) {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return RunReport{}, err
}
return runPipelineSetupWithBackendFactory(ctx, setup, options, newBackendFactoryWithEnvironment)
2026-06-03 11:46:44 +00:00
}
2026-06-03 15:10:07 +00:00
func runPipelineConfigWithLocalSource(ctx context.Context, cfg config.Config, options RunPipelineWithLocalSourceOptions) (RunReport, error) {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return RunReport{}, err
}
return runPipelineSetupWithLocalSourceAndBackendFactory(ctx, setup, options, newBackendFactoryWithEnvironment)
2026-06-03 15:10:07 +00:00
}
2026-06-03 11:46:44 +00:00
func runPipelineConfigWithBackendFactory(ctx context.Context, cfg config.Config, options RunPipelineOptions, provider backendFactoryProvider) (RunReport, error) {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return RunReport{}, err
}
return runPipelineSetupWithBackendFactory(ctx, setup, options, provider)
}
func runPipelineSetup(ctx context.Context, setup runtimeSetup, options RunPipelineOptions) (RunReport, error) {
return runPipelineSetupWithBackendFactory(ctx, setup, options, newBackendFactoryWithEnvironment)
}
func runPipelineSetupWithBackendFactory(ctx context.Context, setup runtimeSetup, options RunPipelineOptions, provider backendFactoryProvider) (RunReport, error) {
pipeline, ok := findPipeline(setup.Config, options.PipelineID)
2026-06-03 11:46:44 +00:00
if !ok {
return RunReport{}, PipelineNotFoundError{ID: options.PipelineID}
}
2026-06-04 00:22:09 +00:00
return buildRunReportWithSetup(ctx, setup.withPipelines([]config.Pipeline{pipeline}), RunOptions{
2026-06-03 11:46:44 +00:00
DryRun: options.DryRun,
Force: options.Force,
Notifier: options.Notifier,
2026-06-04 00:22:09 +00:00
}, provider, nil)
2026-06-03 11:46:44 +00:00
}
2026-06-03 15:10:07 +00:00
func runPipelineConfigWithLocalSourceAndBackendFactory(ctx context.Context, cfg config.Config, options RunPipelineWithLocalSourceOptions, provider backendFactoryProvider) (RunReport, error) {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return RunReport{}, err
}
return runPipelineSetupWithLocalSourceAndBackendFactory(ctx, setup, options, provider)
}
func runPipelineSetupWithLocalSource(ctx context.Context, setup runtimeSetup, options RunPipelineWithLocalSourceOptions) (RunReport, error) {
return runPipelineSetupWithLocalSourceAndBackendFactory(ctx, setup, options, newBackendFactoryWithEnvironment)
}
func runPipelineSetupWithLocalSourceAndBackendFactory(ctx context.Context, setup runtimeSetup, options RunPipelineWithLocalSourceOptions, provider backendFactoryProvider) (RunReport, error) {
pipeline, ok := findPipeline(setup.Config, options.PipelineID)
2026-06-03 15:10:07 +00:00
if !ok {
return RunReport{}, PipelineNotFoundError{ID: options.PipelineID}
}
2026-06-04 00:22:09 +00:00
return buildRunReportWithSetup(ctx, setup.withPipelines([]config.Pipeline{pipeline}), RunOptions{
2026-06-03 15:10:07 +00:00
DryRun: options.DryRun,
Force: options.Force,
Notifier: options.Notifier,
}, provider, &localSourceRoot{
pipelineID: options.PipelineID,
root: options.SourceRoot,
})
}
2026-05-31 17:17:49 +00:00
func runConfigWithBackendFactory(ctx context.Context, cfg config.Config, options RunOptions, provider backendFactoryProvider) error {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return err
}
return runSetupWithBackendFactory(ctx, setup, options, provider)
}
func runSetup(ctx context.Context, setup runtimeSetup, options RunOptions) error {
return runSetupWithBackendFactory(ctx, setup, options, newBackendFactoryWithEnvironment)
}
func runSetupWithBackendFactory(ctx context.Context, setup runtimeSetup, options RunOptions, provider backendFactoryProvider) error {
report, err := buildRunReportWithSetup(ctx, setup, options, provider, nil)
2026-06-03 11:43:50 +00:00
if err != nil && !IsPartialResultError(err) {
return err
}
if outputErr := WriteRunReport(options.Stdout, options.OutputFormat, report); outputErr != nil {
return outputErr
}
return err
}
func buildRunReportWithBackendFactory(ctx context.Context, cfg config.Config, options RunOptions, provider backendFactoryProvider) (RunReport, error) {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return RunReport{}, err
}
return buildRunReportWithSetup(ctx, setup, options, provider, nil)
2026-06-03 15:10:07 +00:00
}
type localSourceRoot struct {
pipelineID string
root string
}
func buildRunReport(ctx context.Context, cfg config.Config, options RunOptions, provider backendFactoryProvider, sourceRoot *localSourceRoot) (RunReport, error) {
2026-06-04 00:22:09 +00:00
setup, err := runtimeSetupFromConfig("", cfg)
if err != nil {
return RunReport{}, err
}
return buildRunReportWithSetup(ctx, setup, options, provider, sourceRoot)
}
func buildRunReportWithSetup(ctx context.Context, setup runtimeSetup, options RunOptions, provider backendFactoryProvider, sourceRoot *localSourceRoot) (RunReport, error) {
2026-05-31 02:33:09 +00:00
notifier := options.Notifier
if notifier == nil {
notifier = notify.Noop{}
}
summary := runSummary{dryRun: options.DryRun}
2026-06-03 11:43:50 +00:00
report := RunReport{
DryRun: options.DryRun,
2026-06-03 11:43:50 +00:00
Pipelines: []RunPipelineSummary{},
Actions: []RunActionRecord{},
}
2026-05-31 02:33:09 +00:00
var failures runFailures
2026-06-04 00:28:23 +00:00
recorder := runReportRecorder{
report: &report,
summary: &summary,
failures: &failures,
}
2026-06-04 00:22:09 +00:00
report.PreambleWarnings = append(report.PreambleWarnings, setup.Warnings...)
report.addWarnings(setup.Warnings)
backends := provider(setup.Environment)
backends.readOnlyKnownHosts = options.DryRun
2026-05-31 03:30:08 +00:00
transforms := newTransformRegistry()
2026-06-04 00:22:09 +00:00
for _, pipeline := range setup.Config.Pipelines {
pipelineWarnings := sshWarnings(pipeline)
2026-06-03 11:43:50 +00:00
report.addWarnings(pipelineWarnings)
2026-06-03 15:10:07 +00:00
sourceBackend, bundles, sourceBackendName, err := openPipelineSource(ctx, backends, pipeline, sourceRoot)
2026-05-31 02:21:36 +00:00
if err != nil {
2026-06-03 15:10:07 +00:00
return report, err
2026-05-31 02:21:36 +00:00
}
2026-06-03 11:43:50 +00:00
report.Pipelines = append(report.Pipelines, RunPipelineSummary{
ID: pipeline.ID,
2026-06-03 15:10:07 +00:00
SourceBackend: sourceBackendName,
BundleCount: len(bundles),
Destinations: destinationIDs(pipeline.Destinations),
2026-06-03 11:43:50 +00:00
Warnings: pipelineWarnings,
})
2026-06-03 11:43:50 +00:00
pipelineIndex := len(report.Pipelines) - 1
2026-06-01 21:33:12 +00:00
for _, destination := range pipeline.Destinations {
2026-06-04 00:28:23 +00:00
processDestination(ctx, runDestinationRequest{
options: options,
notifier: notifier,
backends: backends,
transforms: transforms,
pipeline: pipeline,
pipelineIndex: pipelineIndex,
sourceBackend: sourceBackend,
bundles: bundles,
destination: destination,
recorder: &recorder,
})
2026-05-31 02:21:36 +00:00
}
2026-05-31 16:53:37 +00:00
closeBackend(sourceBackend)
}
2026-06-03 11:43:50 +00:00
report.Summary = summary.Result()
report.OutputErrors = failures.outputErrors()
2026-05-31 02:33:09 +00:00
if len(failures.items) > 0 {
2026-06-03 11:43:50 +00:00
return report, failures
2026-05-31 02:33:09 +00:00
}
2026-06-03 11:43:50 +00:00
return report, nil
2026-05-31 01:47:39 +00:00
}
2026-05-31 02:21:36 +00:00
2026-06-04 00:28:23 +00:00
type runReportRecorder struct {
report *RunReport
summary *runSummary
failures *runFailures
}
2026-06-03 15:10:07 +00:00
func openPipelineSource(ctx context.Context, backends *backendFactory, pipeline config.Pipeline, sourceRoot *localSourceRoot) (storage.Backend, []bundle.Bundle, string, error) {
if sourceRoot != nil && sourceRoot.pipelineID == pipeline.ID {
sourceBackend, err := backends.openLocalPath(ctx, sourceRoot.root)
if err != nil {
return nil, nil, config.BackendLocal, fmt.Errorf("pipeline %s source backend %s: %w", pipeline.ID, config.BackendLocal, err)
}
sourceBundle, err := bundle.Validate(ctx, sourceBackend, "")
if err != nil {
closeBackend(sourceBackend)
return nil, nil, config.BackendLocal, fmt.Errorf("pipeline %s source backend %s validate source bundle: %w", pipeline.ID, config.BackendLocal, err)
}
return sourceBackend, []bundle.Bundle{sourceBundle}, config.BackendLocal, nil
}
sourceBackend, err := backends.openSource(ctx, pipeline.Source)
if err != nil {
return nil, nil, pipeline.Source.Backend, fmt.Errorf("pipeline %s source backend %s: %w", pipeline.ID, pipeline.Source.Backend, err)
}
bundles, err := bundle.Discover(ctx, sourceBackend, "")
if err != nil {
closeBackend(sourceBackend)
return nil, nil, pipeline.Source.Backend, fmt.Errorf("pipeline %s source backend %s discover source bundles: %w", pipeline.ID, pipeline.Source.Backend, err)
}
return sourceBackend, bundles, pipeline.Source.Backend, nil
}
2026-05-31 16:53:37 +00:00
type closeableBackend interface {
Close() error
}
func closeBackend(backend storage.Backend) {
closeable, ok := backend.(closeableBackend)
if !ok {
return
}
_ = closeable.Close()
}