132 lines
4.6 KiB
Go
132 lines
4.6 KiB
Go
package releasekit
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"go.maximumdirect.net/releasekit/internal/git"
|
|
"go.maximumdirect.net/releasekit/internal/publish"
|
|
)
|
|
|
|
// PublicationEvidence identifies one attempted publication. Endpoint may
|
|
// contain credentials; treat this value and saved records as sensitive.
|
|
type PublicationEvidence struct {
|
|
RepositoryDir string
|
|
ModuleDir string
|
|
ModulePath string
|
|
Version string
|
|
Tag string
|
|
CandidateCommit string
|
|
TagObject string
|
|
Endpoint string
|
|
Remote string
|
|
RecordedAt string
|
|
}
|
|
|
|
// PublicationReconciliationResult combines evidence with current remote state.
|
|
type PublicationReconciliationResult struct {
|
|
Evidence PublicationEvidence
|
|
State PublicationState
|
|
ObservedTagObject string
|
|
ObservedCommit string
|
|
}
|
|
|
|
// PublicationRecoveryError describes an unresolved recovery operation.
|
|
// Kind includes missing_evidence, invalid_evidence, canceled, endpoint_changed,
|
|
// transport_configuration, remote_observation, inconsistent_observation,
|
|
// record_format, record_read, record_write, and storage_enumeration.
|
|
type PublicationRecoveryError struct {
|
|
Kind string
|
|
RecordPath string
|
|
MissingFields []string
|
|
cause error
|
|
}
|
|
|
|
func (e *PublicationRecoveryError) Error() string {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
message := fmt.Sprintf("publication recovery %s", e.Kind)
|
|
if e.RecordPath != "" {
|
|
message += fmt.Sprintf(" for %q", e.RecordPath)
|
|
}
|
|
if e.cause != nil {
|
|
message += ": " + git.DisplayError(e.cause).Error()
|
|
}
|
|
return message
|
|
}
|
|
func (e *PublicationRecoveryError) Unwrap() error {
|
|
if e == nil {
|
|
return nil
|
|
}
|
|
return e.cause
|
|
}
|
|
|
|
// ReconcilePublication observes the recorded endpoint without running candidate
|
|
// validation or changing local or remote state.
|
|
func ReconcilePublication(ctx context.Context, evidence PublicationEvidence) (PublicationReconciliationResult, error) {
|
|
observation, err := publish.Reconcile(ctx, internalEvidence(evidence))
|
|
return publicObservation(observation), publicRecoveryError(err, "")
|
|
}
|
|
|
|
// LoadPublicationEvidence loads one explicit private attempt record. Relative
|
|
// paths are interpreted from the caller's current directory; no Git lookup is
|
|
// performed.
|
|
func LoadPublicationEvidence(path string) (PublicationEvidence, error) {
|
|
evidence, err := publish.LoadEvidence(path)
|
|
return publicEvidence(evidence), publicRecoveryError(err, path)
|
|
}
|
|
|
|
type PublicationAttemptsRequest struct {
|
|
RepositoryDir string
|
|
RecordDir string
|
|
}
|
|
type PublicationAttempt struct {
|
|
RecordPath string
|
|
Evidence PublicationEvidence
|
|
}
|
|
type PublicationAttemptsResult struct {
|
|
RecordDir string
|
|
Attempts []PublicationAttempt
|
|
Diagnostics []*PublicationRecoveryError
|
|
}
|
|
|
|
// ListPublicationAttempts discovers saved local records without querying a
|
|
// remote or inferring which attempt should be reconciled.
|
|
func ListPublicationAttempts(ctx context.Context, request PublicationAttemptsRequest) (PublicationAttemptsResult, error) {
|
|
listed, err := publish.ListAttempts(ctx, request.RepositoryDir, request.RecordDir)
|
|
result := PublicationAttemptsResult{RecordDir: listed.RecordDir, Attempts: make([]PublicationAttempt, 0, len(listed.Attempts)), Diagnostics: make([]*PublicationRecoveryError, 0, len(listed.Diagnostics))}
|
|
for _, attempt := range listed.Attempts {
|
|
result.Attempts = append(result.Attempts, PublicationAttempt{RecordPath: attempt.RecordPath, Evidence: publicEvidence(attempt.Evidence)})
|
|
}
|
|
for _, diagnostic := range listed.Diagnostics {
|
|
converted := publicRecoveryError(diagnostic, diagnostic.RecordPath)
|
|
if recovery, ok := converted.(*PublicationRecoveryError); ok {
|
|
result.Diagnostics = append(result.Diagnostics, recovery)
|
|
}
|
|
}
|
|
return result, publicRecoveryError(err, "")
|
|
}
|
|
|
|
func internalEvidence(e PublicationEvidence) publish.Evidence { return publish.Evidence(e) }
|
|
func publicEvidence(e publish.Evidence) PublicationEvidence { return PublicationEvidence(e) }
|
|
func publicObservation(o publish.Observation) PublicationReconciliationResult {
|
|
return PublicationReconciliationResult{Evidence: publicEvidence(o.Evidence), State: PublicationState(o.State), ObservedTagObject: o.ObservedTagObject, ObservedCommit: o.ObservedCommit}
|
|
}
|
|
|
|
func publicRecoveryError(err error, path string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
var recovery *publish.RecoveryError
|
|
if errors.As(err, &recovery) {
|
|
recordPath := path
|
|
if recordPath == "" {
|
|
recordPath = recovery.RecordPath
|
|
}
|
|
return &PublicationRecoveryError{Kind: recovery.Kind, RecordPath: recordPath, MissingFields: append([]string(nil), recovery.MissingFields...), cause: recovery}
|
|
}
|
|
return &PublicationRecoveryError{Kind: "remote_observation", RecordPath: path, cause: err}
|
|
}
|