Bugfix for commands that list artifacts in the S3 backend
This commit is contained in:
@@ -219,11 +219,13 @@ func Status(ctx context.Context, args []string, out io.Writer) error {
|
||||
}
|
||||
}
|
||||
remoteState := map[string]string{}
|
||||
promotedRemoteState := map[string]string{}
|
||||
if store != nil {
|
||||
remoteState = remoteArtifactAvailability(ctx, cfg, store, catalog)
|
||||
promotedRemoteState = remotePromotionAvailability(ctx, cfg, store, catalog)
|
||||
}
|
||||
fmt.Fprintln(out, "Remote outputs:")
|
||||
writeArtifactList(out, cfg, catalog, catalogLocks, remoteState)
|
||||
writeArtifactList(out, cfg, catalog, catalogLocks, remoteState, promotedRemoteState)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Archive locks: error: %v\n", err)
|
||||
@@ -389,10 +391,12 @@ func ArtifactsList(ctx context.Context, args []string, out io.Writer) error {
|
||||
return fmt.Errorf("artifacts list: %w", err)
|
||||
}
|
||||
remoteState := map[string]string{}
|
||||
promotedRemoteState := map[string]string{}
|
||||
if remote && store != nil {
|
||||
remoteState = remoteArtifactAvailability(ctx, cfg, store, catalog)
|
||||
promotedRemoteState = remotePromotionAvailability(ctx, cfg, store, catalog)
|
||||
}
|
||||
writeArtifactList(out, cfg, catalog, locks, remoteState)
|
||||
writeArtifactList(out, cfg, catalog, locks, remoteState, promotedRemoteState)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -793,7 +797,7 @@ func buildHelperArtifactCatalog(cfg *config.Config) (*artifacts.ArtifactCatalog,
|
||||
return catalog, nil
|
||||
}
|
||||
|
||||
func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, remoteState map[string]string) {
|
||||
func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.ArtifactCatalog, locks *effectiveLocks, remoteState map[string]string, promotedRemoteState map[string]string) {
|
||||
lockSet := lockSourceSet(locks.All)
|
||||
fmt.Fprintln(out, "Built-in:")
|
||||
for _, id := range []string{
|
||||
@@ -815,7 +819,7 @@ func writeArtifactList(out io.Writer, cfg *config.Config, catalog *artifacts.Art
|
||||
}
|
||||
fmt.Fprintln(out, "Promoted:")
|
||||
for _, rule := range cfg.Pipeline.Archive.PromoteArtifacts {
|
||||
writeArtifactLine(out, rule.Source, lockSet, remoteState)
|
||||
writePromotedArtifactLine(out, rule, catalog, lockSet, promotedRemoteState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,6 +834,27 @@ func writeArtifactLine(out io.Writer, source string, lockSet map[string]config.A
|
||||
fmt.Fprintf(out, "- %s\n", strings.Join(parts, " "))
|
||||
}
|
||||
|
||||
func writePromotedArtifactLine(out io.Writer, rule config.ArchivePromotionRule, catalog *artifacts.ArtifactCatalog, lockSet map[string]config.ArchiveLockRule, remoteState map[string]string) {
|
||||
source := strings.TrimSpace(rule.Source)
|
||||
parts := []string{source}
|
||||
if _, ok := lockSet[source]; ok {
|
||||
parts = append(parts, "locked")
|
||||
}
|
||||
dest, showDest, err := helperPromotionDest(rule, catalog)
|
||||
if err != nil {
|
||||
parts = append(parts, "remote=error")
|
||||
fmt.Fprintf(out, "- %s\n", strings.Join(parts, " "))
|
||||
return
|
||||
}
|
||||
if showDest {
|
||||
parts = append(parts, "dest="+dest)
|
||||
}
|
||||
if state := remoteState[promotionRemoteStateKey(source, dest)]; state != "" {
|
||||
parts = append(parts, state)
|
||||
}
|
||||
fmt.Fprintf(out, "- %s\n", strings.Join(parts, " "))
|
||||
}
|
||||
|
||||
func remoteArtifactAvailability(ctx context.Context, cfg *config.Config, store storage.ObjectStore, catalog *artifacts.ArtifactCatalog) map[string]string {
|
||||
out := map[string]string{}
|
||||
sessionPrefix := artifacts.S3SessionPrefix(cfg.Pipeline.Storage.S3.RootPrefix, cfg.Session.Campaign, cfg.Session.SessionID)
|
||||
@@ -850,6 +875,69 @@ func remoteArtifactAvailability(ctx context.Context, cfg *config.Config, store s
|
||||
return out
|
||||
}
|
||||
|
||||
func remotePromotionAvailability(ctx context.Context, cfg *config.Config, store storage.ObjectStore, catalog *artifacts.ArtifactCatalog) map[string]string {
|
||||
out := map[string]string{}
|
||||
sessionPrefix := artifacts.S3SessionPrefix(cfg.Pipeline.Storage.S3.RootPrefix, cfg.Session.Campaign, cfg.Session.SessionID)
|
||||
for _, rule := range cfg.Pipeline.Archive.PromoteArtifacts {
|
||||
source := strings.TrimSpace(rule.Source)
|
||||
dest, _, err := helperPromotionDest(rule, catalog)
|
||||
if err != nil {
|
||||
out[promotionRemoteStateKey(source, "")] = "remote=error"
|
||||
continue
|
||||
}
|
||||
key := artifacts.S3PromotedArtifactKey(sessionPrefix, dest)
|
||||
if exists, err := store.Exists(ctx, key); err == nil && exists {
|
||||
out[promotionRemoteStateKey(source, dest)] = "remote=promoted"
|
||||
} else if err != nil {
|
||||
out[promotionRemoteStateKey(source, dest)] = "remote=error"
|
||||
} else {
|
||||
out[promotionRemoteStateKey(source, dest)] = "remote=missing"
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func helperPromotionDest(rule config.ArchivePromotionRule, catalog *artifacts.ArtifactCatalog) (string, bool, error) {
|
||||
source := strings.TrimSpace(rule.Source)
|
||||
dest := strings.TrimSpace(rule.Dest)
|
||||
if dest == "" {
|
||||
entry, ok := catalog.Lookup(source)
|
||||
if !ok {
|
||||
return "", false, fmt.Errorf("destination omitted and source is unknown")
|
||||
}
|
||||
dest = strings.TrimSpace(entry.CanonicalRelPath)
|
||||
if dest == "" {
|
||||
return "", false, fmt.Errorf("destination omitted and no canonical destination is available")
|
||||
}
|
||||
}
|
||||
normalized, err := normalizeHelperArchiveRelativePath(dest)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
entry, ok := catalog.Lookup(source)
|
||||
showDest := !ok || strings.TrimSpace(entry.CanonicalRelPath) != normalized
|
||||
return normalized, showDest, nil
|
||||
}
|
||||
|
||||
func normalizeHelperArchiveRelativePath(rel string) (string, error) {
|
||||
trimmed := strings.TrimSpace(rel)
|
||||
if trimmed == "" {
|
||||
return "", fmt.Errorf("relative path is required")
|
||||
}
|
||||
cleaned := filepath.ToSlash(filepath.Clean(filepath.FromSlash(trimmed)))
|
||||
if cleaned == "." || cleaned == "" {
|
||||
return "", fmt.Errorf("relative path is required")
|
||||
}
|
||||
if filepath.IsAbs(trimmed) || strings.HasPrefix(cleaned, "/") || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
|
||||
return "", fmt.Errorf("path must be a clean relative path")
|
||||
}
|
||||
return cleaned, nil
|
||||
}
|
||||
|
||||
func promotionRemoteStateKey(source, dest string) string {
|
||||
return strings.TrimSpace(source) + "\x00" + strings.TrimSpace(dest)
|
||||
}
|
||||
|
||||
func allCatalogSources(catalog *artifacts.ArtifactCatalog) []string {
|
||||
out := []string{
|
||||
artifacts.ArtifactTranscriptMerged,
|
||||
|
||||
Reference in New Issue
Block a user