Add fixed destination path mapping
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/bundle"
|
||||
@@ -112,33 +113,55 @@ func runConfigWithBackendFactory(ctx context.Context, cfg config.Config, options
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, sourceBundle := range bundles {
|
||||
for _, destination := range pipeline.Destinations {
|
||||
destinationBackend, err := backends.openDestination(ctx, destination)
|
||||
if err != nil {
|
||||
failures.add(pipeline.ID, destination.ID, destination.Backend, storage.DisplayPath(sourceBundle.RootRelativePath), err)
|
||||
for _, destination := range pipeline.Destinations {
|
||||
selections := selectDestinationBundles(destination, bundles)
|
||||
if isFixedPathDestination(destination) {
|
||||
summary.recordFixedPath()
|
||||
if options.DryRun {
|
||||
warning := fixedPathSelectionWarning(pipeline.ID, destination.ID, selections, len(bundles))
|
||||
if jsonOutput {
|
||||
warnings = append(warnings, warning)
|
||||
} else if options.Stdout != nil {
|
||||
if err := writeWarnings(options.Stdout, []OutputWarning{warning}); err != nil {
|
||||
closeBackend(sourceBackend)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(selections) == 0 {
|
||||
continue
|
||||
}
|
||||
destinationBackend, err := backends.openDestination(ctx, destination)
|
||||
if err != nil {
|
||||
for _, selection := range selections {
|
||||
failures.add(pipeline.ID, destination.ID, destination.Backend, storage.DisplayPath(selection.SourceBundle.RootRelativePath), err)
|
||||
summary.recordFailure()
|
||||
if jsonOutput {
|
||||
result.Actions = append(result.Actions, errorAction(pipeline.ID, destination.ID, destination.Backend, sourceBundle.RootRelativePath, err))
|
||||
result.Actions = append(result.Actions, errorAction(pipeline.ID, destination.ID, destination.Backend, selection.SourceBundle.RootRelativePath, err))
|
||||
} else if options.Stdout != nil {
|
||||
writeErrorLine(options.Stdout, sourceBundle.RootRelativePath, destination.ID, destination.Backend, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
closeDestination := true
|
||||
deferCloseDestination := func() {
|
||||
if closeDestination {
|
||||
closeBackend(destinationBackend)
|
||||
closeDestination = false
|
||||
writeErrorLine(options.Stdout, selection.SourceBundle.RootRelativePath, destination.ID, destination.Backend, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
closeDestination := true
|
||||
deferCloseDestination := func() {
|
||||
if closeDestination {
|
||||
closeBackend(destinationBackend)
|
||||
closeDestination = false
|
||||
}
|
||||
}
|
||||
for _, selection := range selections {
|
||||
sourceBundle := selection.SourceBundle
|
||||
req := publish.Request{
|
||||
PipelineID: pipeline.ID,
|
||||
DestinationID: destination.ID,
|
||||
SourceBundle: sourceBundle,
|
||||
SourceBackend: sourceBackend,
|
||||
DestinationBackend: destinationBackend,
|
||||
DestinationBundlePath: sourceBundle.RootRelativePath,
|
||||
DestinationBundlePath: selection.DestinationBundlePath,
|
||||
PathMapping: destination.PathMap.Mode,
|
||||
Publish: *destination.Publish,
|
||||
Transform: destination.Transform,
|
||||
Transformers: transforms,
|
||||
@@ -160,6 +183,24 @@ func runConfigWithBackendFactory(ctx context.Context, cfg config.Config, options
|
||||
if plan.BundlePath == "" {
|
||||
plan.BundlePath = sourceBundle.RootRelativePath
|
||||
}
|
||||
if plan.DestinationBundlePath == "" {
|
||||
plan.DestinationBundlePath = selection.DestinationBundlePath
|
||||
}
|
||||
}
|
||||
if isFixedPathDestination(destination) {
|
||||
plan.PathMapping = config.PathMappingFixed
|
||||
if options.DryRun && isDestructiveFixedPathAction(plan.Action) {
|
||||
warning := fixedPathReplacementWarning(plan)
|
||||
if jsonOutput {
|
||||
warnings = append(warnings, warning)
|
||||
} else if options.Stdout != nil {
|
||||
if err := writeWarnings(options.Stdout, []OutputWarning{warning}); err != nil {
|
||||
deferCloseDestination()
|
||||
closeBackend(sourceBackend)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if jsonOutput {
|
||||
result.Actions = append(result.Actions, runActionFromPlan(destination.Backend, plan, err))
|
||||
@@ -167,7 +208,6 @@ func runConfigWithBackendFactory(ctx context.Context, cfg config.Config, options
|
||||
writePlanLine(options.Stdout, destination.Backend, plan, err)
|
||||
}
|
||||
if err != nil {
|
||||
deferCloseDestination()
|
||||
failures.add(pipeline.ID, destination.ID, destination.Backend, storage.DisplayPath(sourceBundle.RootRelativePath), err)
|
||||
summary.recordFailure()
|
||||
continue
|
||||
@@ -175,22 +215,20 @@ func runConfigWithBackendFactory(ctx context.Context, cfg config.Config, options
|
||||
summary.recordPlan(plan.Action)
|
||||
if !options.DryRun {
|
||||
if err := publish.Execute(ctx, req, plan); err != nil {
|
||||
deferCloseDestination()
|
||||
failures.add(pipeline.ID, destination.ID, destination.Backend, storage.DisplayPath(sourceBundle.RootRelativePath), err)
|
||||
summary.recordFailure()
|
||||
continue
|
||||
}
|
||||
if shouldNotify(plan.Action) {
|
||||
if err := notifier.Notify(ctx, notifyEvent(plan)); err != nil {
|
||||
deferCloseDestination()
|
||||
failures.add(pipeline.ID, destination.ID, destination.Backend, storage.DisplayPath(sourceBundle.RootRelativePath), err)
|
||||
summary.recordFailure()
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
deferCloseDestination()
|
||||
}
|
||||
deferCloseDestination()
|
||||
}
|
||||
closeBackend(sourceBackend)
|
||||
}
|
||||
@@ -231,10 +269,17 @@ func writePlanLine(w io.Writer, backend string, plan publish.Plan, planErr error
|
||||
if destinationID == "" {
|
||||
destinationID = "unknown"
|
||||
}
|
||||
fmt.Fprintf(w, " - bundle=%s destination=%s backend=%s action=error reason=%q\n", storage.DisplayPath(plan.BundlePath), destinationID, backend, planErr.Error())
|
||||
fmt.Fprintf(w, " - bundle=%s destination=%s backend=%s%s action=error reason=%q\n", storage.DisplayPath(plan.BundlePath), destinationID, backend, pathMappingSummary(plan), planErr.Error())
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, " - bundle=%s destination=%s backend=%s action=%s outputs=%s reason=%q\n", storage.DisplayPath(plan.BundlePath), plan.DestinationID, backend, plan.Action, outputSummary(plan.Outputs), plan.Reason)
|
||||
fmt.Fprintf(w, " - bundle=%s destination=%s backend=%s%s action=%s outputs=%s reason=%q\n", storage.DisplayPath(plan.BundlePath), plan.DestinationID, backend, pathMappingSummary(plan), plan.Action, outputSummary(plan.Outputs), plan.Reason)
|
||||
}
|
||||
|
||||
func pathMappingSummary(plan publish.Plan) string {
|
||||
if plan.PathMapping != config.PathMappingFixed {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(" path_mapping=fixed target=%s", storage.DisplayPath(plan.DestinationBundlePath))
|
||||
}
|
||||
|
||||
func writeErrorLine(w io.Writer, bundlePath, destinationID, backend string, err error) {
|
||||
@@ -255,6 +300,66 @@ func outputSummary(outputs []publish.Output) string {
|
||||
return strings.Join(paths, ",")
|
||||
}
|
||||
|
||||
type destinationBundleSelection struct {
|
||||
SourceBundle bundle.Bundle
|
||||
DestinationBundlePath string
|
||||
}
|
||||
|
||||
func selectDestinationBundles(destination config.Destination, bundles []bundle.Bundle) []destinationBundleSelection {
|
||||
if !isFixedPathDestination(destination) {
|
||||
selections := make([]destinationBundleSelection, 0, len(bundles))
|
||||
for _, sourceBundle := range bundles {
|
||||
selections = append(selections, destinationBundleSelection{
|
||||
SourceBundle: sourceBundle,
|
||||
DestinationBundlePath: sourceBundle.RootRelativePath,
|
||||
})
|
||||
}
|
||||
return selections
|
||||
}
|
||||
if len(bundles) == 0 {
|
||||
return nil
|
||||
}
|
||||
sourceBundle := newestBundle(bundles)
|
||||
return []destinationBundleSelection{{
|
||||
SourceBundle: sourceBundle,
|
||||
DestinationBundlePath: "",
|
||||
}}
|
||||
}
|
||||
|
||||
func newestBundle(bundles []bundle.Bundle) bundle.Bundle {
|
||||
if len(bundles) == 0 {
|
||||
return bundle.Bundle{}
|
||||
}
|
||||
sorted := append([]bundle.Bundle(nil), bundles...)
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
if sorted[i].Manifest.Created.Equal(sorted[j].Manifest.Created) {
|
||||
return sorted[i].RootRelativePath < sorted[j].RootRelativePath
|
||||
}
|
||||
return sorted[i].Manifest.Created.After(sorted[j].Manifest.Created)
|
||||
})
|
||||
return sorted[0]
|
||||
}
|
||||
|
||||
func isFixedPathDestination(destination config.Destination) bool {
|
||||
return destination.PathMap.Mode == config.PathMappingFixed
|
||||
}
|
||||
|
||||
func fixedPathSelectionWarning(pipelineID, destinationID string, selections []destinationBundleSelection, candidateCount int) OutputWarning {
|
||||
selected := "none"
|
||||
if len(selections) > 0 {
|
||||
selected = storage.DisplayPath(selections[0].SourceBundle.RootRelativePath)
|
||||
}
|
||||
return OutputWarning{Message: fmt.Sprintf("pipeline=%s destination=%s path_mapping=fixed candidates=%d selected_bundle=%s destination_bundle=.", pipelineID, destinationID, candidateCount, selected)}
|
||||
}
|
||||
|
||||
func isDestructiveFixedPathAction(action publish.Action) bool {
|
||||
return action == publish.ActionReplaceOlder || action == publish.ActionForceReplace
|
||||
}
|
||||
|
||||
func fixedPathReplacementWarning(plan publish.Plan) OutputWarning {
|
||||
return OutputWarning{Message: fmt.Sprintf("pipeline=%s destination=%s path_mapping=fixed action=%s replaces destination root for selected_bundle=%s", plan.PipelineID, plan.DestinationID, plan.Action, storage.DisplayPath(plan.BundlePath))}
|
||||
}
|
||||
|
||||
func destinationIDs(destinations []config.Destination) []string {
|
||||
ids := make([]string, 0, len(destinations))
|
||||
for _, destination := range destinations {
|
||||
@@ -362,14 +467,16 @@ type runPipelineResult struct {
|
||||
}
|
||||
|
||||
type runActionResult struct {
|
||||
PipelineID string `json:"pipeline_id,omitempty"`
|
||||
DestinationID string `json:"destination_id"`
|
||||
Backend string `json:"backend"`
|
||||
BundleID string `json:"bundle_id,omitempty"`
|
||||
BundlePath string `json:"bundle_path"`
|
||||
Action string `json:"action"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Outputs []runOutputResult `json:"outputs"`
|
||||
PipelineID string `json:"pipeline_id,omitempty"`
|
||||
DestinationID string `json:"destination_id"`
|
||||
Backend string `json:"backend"`
|
||||
BundleID string `json:"bundle_id,omitempty"`
|
||||
BundlePath string `json:"bundle_path"`
|
||||
DestinationPath string `json:"destination_path"`
|
||||
PathMapping string `json:"path_mapping,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Outputs []runOutputResult `json:"outputs"`
|
||||
}
|
||||
|
||||
type runOutputResult struct {
|
||||
@@ -388,37 +495,42 @@ func runActionFromPlan(backend string, plan publish.Plan, planErr error) runActi
|
||||
destinationID = "unknown"
|
||||
}
|
||||
return runActionResult{
|
||||
PipelineID: plan.PipelineID,
|
||||
DestinationID: destinationID,
|
||||
Backend: backend,
|
||||
BundleID: plan.BundleID,
|
||||
BundlePath: storage.DisplayPath(plan.BundlePath),
|
||||
Action: "error",
|
||||
Reason: planErr.Error(),
|
||||
Outputs: []runOutputResult{},
|
||||
PipelineID: plan.PipelineID,
|
||||
DestinationID: destinationID,
|
||||
Backend: backend,
|
||||
BundleID: plan.BundleID,
|
||||
BundlePath: storage.DisplayPath(plan.BundlePath),
|
||||
DestinationPath: storage.DisplayPath(plan.DestinationBundlePath),
|
||||
PathMapping: plan.PathMapping,
|
||||
Action: "error",
|
||||
Reason: planErr.Error(),
|
||||
Outputs: []runOutputResult{},
|
||||
}
|
||||
}
|
||||
return runActionResult{
|
||||
PipelineID: plan.PipelineID,
|
||||
DestinationID: plan.DestinationID,
|
||||
Backend: backend,
|
||||
BundleID: plan.BundleID,
|
||||
BundlePath: storage.DisplayPath(plan.BundlePath),
|
||||
Action: string(plan.Action),
|
||||
Reason: plan.Reason,
|
||||
Outputs: runOutputsFromPlan(plan.Outputs),
|
||||
PipelineID: plan.PipelineID,
|
||||
DestinationID: plan.DestinationID,
|
||||
Backend: backend,
|
||||
BundleID: plan.BundleID,
|
||||
BundlePath: storage.DisplayPath(plan.BundlePath),
|
||||
DestinationPath: storage.DisplayPath(plan.DestinationBundlePath),
|
||||
PathMapping: plan.PathMapping,
|
||||
Action: string(plan.Action),
|
||||
Reason: plan.Reason,
|
||||
Outputs: runOutputsFromPlan(plan.Outputs),
|
||||
}
|
||||
}
|
||||
|
||||
func errorAction(pipelineID, destinationID, backend, bundlePath string, err error) runActionResult {
|
||||
return runActionResult{
|
||||
PipelineID: pipelineID,
|
||||
DestinationID: destinationID,
|
||||
Backend: backend,
|
||||
BundlePath: storage.DisplayPath(bundlePath),
|
||||
Action: "error",
|
||||
Reason: err.Error(),
|
||||
Outputs: []runOutputResult{},
|
||||
PipelineID: pipelineID,
|
||||
DestinationID: destinationID,
|
||||
Backend: backend,
|
||||
BundlePath: storage.DisplayPath(bundlePath),
|
||||
DestinationPath: storage.DisplayPath(bundlePath),
|
||||
Action: "error",
|
||||
Reason: err.Error(),
|
||||
Outputs: []runOutputResult{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,6 +557,7 @@ type runSummary struct {
|
||||
forceReplace int
|
||||
skipped int
|
||||
failures int
|
||||
fixedPath int
|
||||
}
|
||||
|
||||
func (s *runSummary) recordPlan(action publish.Action) {
|
||||
@@ -465,12 +578,16 @@ func (s *runSummary) recordFailure() {
|
||||
s.failures++
|
||||
}
|
||||
|
||||
func (s *runSummary) recordFixedPath() {
|
||||
s.fixedPath++
|
||||
}
|
||||
|
||||
func (s runSummary) Line() string {
|
||||
status := "ok"
|
||||
if s.failures > 0 {
|
||||
status = "failed"
|
||||
}
|
||||
return fmt.Sprintf("Final status: %s planned=%d publish_new=%d replace_older=%d force_replace=%d skipped=%d failed=%d dry_run=%t", status, s.planned, s.publishNew, s.replaceOlder, s.forceReplace, s.skipped, s.failures, s.dryRun)
|
||||
return fmt.Sprintf("Final status: %s planned=%d publish_new=%d replace_older=%d force_replace=%d skipped=%d failed=%d dry_run=%t fixed_path=%d", status, s.planned, s.publishNew, s.replaceOlder, s.forceReplace, s.skipped, s.failures, s.dryRun, s.fixedPath)
|
||||
}
|
||||
|
||||
type runSummaryResult struct {
|
||||
@@ -482,6 +599,7 @@ type runSummaryResult struct {
|
||||
Skipped int `json:"skipped"`
|
||||
Failed int `json:"failed"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
FixedPath int `json:"fixed_path"`
|
||||
}
|
||||
|
||||
func (s runSummary) Result() runSummaryResult {
|
||||
@@ -498,6 +616,7 @@ func (s runSummary) Result() runSummaryResult {
|
||||
Skipped: s.skipped,
|
||||
Failed: s.failures,
|
||||
DryRun: s.dryRun,
|
||||
FixedPath: s.fixedPath,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user