package publish import ( "context" "fmt" "gitea.maximumdirect.net/eric/distributor/internal/bundle" "gitea.maximumdirect.net/eric/distributor/internal/config" "gitea.maximumdirect.net/eric/distributor/internal/state" "gitea.maximumdirect.net/eric/distributor/internal/storage" "gitea.maximumdirect.net/eric/distributor/internal/transform" markdowntransform "gitea.maximumdirect.net/eric/distributor/internal/transform/markdown" ) type Action string const ( ActionPublishNew Action = "publish_new" ActionReplaceOlder Action = "replace_older" ActionSkipSame Action = "skip_same" ActionSkipDestinationNewer Action = "skip_destination_newer" ActionFailConflict Action = "fail_conflict" ActionFailUnmanaged Action = "fail_unmanaged" ) type Request struct { PipelineID string DestinationID string SourceBundle bundle.Bundle SourceBackend storage.Backend DestinationBackend storage.Backend DestinationBundlePath string Publish config.PublishPolicy Transform config.Transform Transfer config.TransferPolicy DistributorVersion string } type Plan struct { PipelineID string DestinationID string BundleID string BundlePath string DestinationBundlePath string Action Action Reason string Outputs []Output ExistingState *state.DistributorState } type Output struct { SourcePath string DestinationPath string Kind string Transform string Data []byte SHA256 string Size int64 } func Build(ctx context.Context, req Request) (Plan, error) { if err := validateRequest(req); err != nil { return Plan{}, err } outputs, err := PlanOutputs(ctx, req) if err != nil { return Plan{}, err } status, err := inspectDestination(ctx, req.DestinationBackend, req.DestinationBundlePath) if err != nil { return Plan{}, err } comparison := state.Compare(req.SourceBundle.Manifest, req.PipelineID, req.DestinationID, status) action, reason := actionForComparison(comparison, req.Transfer) plan := Plan{ PipelineID: req.PipelineID, DestinationID: req.DestinationID, BundleID: req.SourceBundle.Manifest.ID, BundlePath: req.SourceBundle.RootRelativePath, DestinationBundlePath: req.DestinationBundlePath, Action: action, Reason: reason, Outputs: outputs, ExistingState: status.State, } if action == ActionFailConflict || action == ActionFailUnmanaged { return plan, fmt.Errorf("%s: %s", action, reason) } return plan, nil } func validateRequest(req Request) error { if req.PipelineID == "" { return fmt.Errorf("pipeline id is required") } if req.DestinationID == "" { return fmt.Errorf("destination id is required") } if req.SourceBackend == nil { return fmt.Errorf("source backend is required") } if req.DestinationBackend == nil { return fmt.Errorf("destination backend is required") } if !req.Publish.Source && !req.Publish.HTML { return fmt.Errorf("publish source or html must be enabled") } if req.Publish.HTML { if req.Transform.MarkdownToHTML == nil || !req.Transform.MarkdownToHTML.Enabled || req.Transform.MarkdownToHTML.Mode != config.TransformModeSidecar { return fmt.Errorf("publish html requires markdown_to_html transform enabled with sidecar mode") } } return nil } func markdownTransformer() transform.Transformer { return markdowntransform.New() } func actionForComparison(comparison state.Comparison, transfer config.TransferPolicy) (Action, string) { switch comparison.Outcome { case state.OutcomeDestinationAbsent: return ActionPublishNew, comparison.Reason case state.OutcomeDestinationUnmanaged: return ActionFailUnmanaged, comparison.Reason case state.OutcomeInvalidState, state.OutcomeIdentityMismatch, state.OutcomeSameCreatedConflict, state.OutcomeDifferentSourceConflict: return ActionFailConflict, comparison.Reason case state.OutcomeSameSource: if transfer.OnDestinationSame == config.TransferActionFail { return ActionFailConflict, "destination matches source and transfer policy requires failure" } return ActionSkipSame, comparison.Reason case state.OutcomeDestinationOlder: if transfer.OnDestinationOlder == config.TransferActionFail { return ActionFailConflict, "destination is older and transfer policy requires failure" } return ActionReplaceOlder, comparison.Reason case state.OutcomeDestinationNewer: if transfer.OnDestinationNewer == config.TransferActionFail { return ActionFailConflict, "destination is newer and transfer policy requires failure" } return ActionSkipDestinationNewer, comparison.Reason default: return ActionFailConflict, "unsupported comparison outcome" } } func displayPath(path string) string { if path == "" { return "." } return path }