diff --git a/cmd/hauler/cli/store/add.go b/cmd/hauler/cli/store/add.go index a9dceb7..869921b 100644 --- a/cmd/hauler/cli/store/add.go +++ b/cmd/hauler/cli/store/add.go @@ -98,7 +98,8 @@ func storeImage(ctx context.Context, s *store.Layout, i v1alpha1.Image) error { } err = cosign.SaveImage(ctx, s, r.Name()) - //desc, err := s.AddOCI(ctx, img, r.Name()) + // sync with local index + s.CopyAll(ctx, s.OCI, nil) if err != nil { return err } diff --git a/cmd/hauler/cli/store/copy.go b/cmd/hauler/cli/store/copy.go index 158f433..70f7d40 100644 --- a/cmd/hauler/cli/store/copy.go +++ b/cmd/hauler/cli/store/copy.go @@ -55,6 +55,13 @@ func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Layout, targetRef string Insecure: o.Insecure, PlainHTTP: o.PlainHTTP, } + + if ropts.Username != "" { + err := cosign.RegistryLogin(ctx, s, components[1], ropts) + if err != nil { + return err + } + } err := cosign.LoadImage(ctx, s, components[1], ropts) if err != nil { diff --git a/cmd/hauler/cli/store/sync.go b/cmd/hauler/cli/store/sync.go index 065e21e..e02aa1a 100644 --- a/cmd/hauler/cli/store/sync.go +++ b/cmd/hauler/cli/store/sync.go @@ -42,12 +42,6 @@ func (o *SyncOpts) AddFlags(cmd *cobra.Command) { func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error { l := log.FromContext(ctx) - // Start from an empty store (contents are cached elsewhere) - l.Debugf("flushing content store") - if err := s.Flush(ctx); err != nil { - return err - } - // if passed products, check for a remote manifest to retrieve and use. for _, product := range o.Products { l.Infof("processing content file for product: '%s'", product) diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index e7e42bc..1c50836 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -12,6 +12,7 @@ import ( "context" "strings" "encoding/json" + "time" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/content" @@ -24,77 +25,122 @@ import ( "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" ) +const maxRetries = 3 +const retryDelay = time.Second * 5 + // VerifyFileSignature verifies the digital signature of a file using Sigstore/Cosign. func VerifySignature(ctx context.Context, s *store.Layout, keyPath string, ref string) error { + operation := func() error { + cosignBinaryPath, err := ensureCosignBinary(ctx, s) + if err != nil { + return err + } - // Ensure that the cosign binary is installed or download it if needed - cosignBinaryPath, err := ensureCosignBinary(ctx, s) - if err != nil { - return err + cmd := exec.Command(cosignBinaryPath, "verify", "--insecure-ignore-tlog", "--key", keyPath, ref) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error verifying signature: %v, output: %s", err, output) + } + + return nil } - // Command to verify the signature using Cosign. - cmd := exec.Command(cosignBinaryPath, "verify", "--insecure-ignore-tlog", "--key", keyPath, ref) - - // Run the command and capture its output. - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("error verifying signature: %v, output: %s", err, output) - } - - return nil + return RetryOperation(ctx, operation) } // SaveImage saves image and any signatures/attestations to the store. func SaveImage(ctx context.Context, s *store.Layout, ref string) error { + operation := func() error { + cosignBinaryPath, err := ensureCosignBinary(ctx, s) + if err != nil { + return err + } - // Ensure that the cosign binary is installed or download it if needed - cosignBinaryPath, err := ensureCosignBinary(ctx, s) - if err != nil { - return err + cmd := exec.Command(cosignBinaryPath, "save", ref, "--dir", s.Root) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error adding image to store: %v, output: %s", err, output) + } + + return nil } - // Command to save/download an image using Cosign. - cmd := exec.Command(cosignBinaryPath, "save", ref, "--dir", s.Root) - - // Run the command and capture its output. - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("error adding image to store: %v, output: %s", err, output) - } - - return nil + return RetryOperation(ctx, operation) } // LoadImage loads store to a remote registry. func LoadImage(ctx context.Context, s *store.Layout, registry string, ropts content.RegistryOptions) error { + operation := func() error { + cosignBinaryPath, err := ensureCosignBinary(ctx, s) + if err != nil { + return err + } - //Ensure that the cosign binary is installed or download it if needed - cosignBinaryPath, err := ensureCosignBinary(ctx, s) - if err != nil { - return err + cmd := exec.Command(cosignBinaryPath, "load", "--registry", registry, "--dir", s.Root) + + // Conditionally add extra registry flags. + if ropts.Insecure { + cmd.Args = append(cmd.Args, "--allow-insecure-registry=true") + } + if ropts.PlainHTTP { + cmd.Args = append(cmd.Args, "--allow-http-registry=true") + } + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error copying store: %v, output: %s", err, output) + } + + return nil } - // Command to upload index to a remote registry using Cosign. - cmd := exec.Command(cosignBinaryPath, "load", "--registry", registry, "--dir", s.Root) - - // Conditionally add extra registry flags. - if ropts.Insecure { - cmd.Args = append(cmd.Args, "--allow-insecure-registry=true") - } - if ropts.PlainHTTP { - cmd.Args = append(cmd.Args, "--allow-http-registry=true") - } - - // Run the command and capture its output. - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("error adding image to store: %v, output: %s", err, output) - } - - return nil + return RetryOperation(ctx, operation) } +// RegistryLogin - performs cosign login +func RegistryLogin(ctx context.Context, s *store.Layout, registry string, ropts content.RegistryOptions) error { + operation := func() error { + cosignBinaryPath, err := ensureCosignBinary(ctx, s) + if err != nil { + return err + } + + cmd := exec.Command(cosignBinaryPath, "login", registry, "-u", ropts.Username, "-p", ropts.Password) + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error logging into registry: %v, output: %s", err, output) + } + + return nil + } + + return RetryOperation(ctx, operation) +} + +func RetryOperation(ctx context.Context, operation func() error) error { + l := log.FromContext(ctx) + for attempt := 1; attempt <= maxRetries; attempt++ { + err := operation() + if err == nil { + // If the operation succeeds, return nil (no error). + return nil + } + + // Log the error for the current attempt. + l.Errorf("Error (attempt %d/%d): %v", attempt, maxRetries, err) + + // If this is not the last attempt, wait before retrying. + if attempt < maxRetries { + time.Sleep(retryDelay) + } + } + + // If all attempts fail, return an error. + return fmt.Errorf("operation failed after %d attempts", maxRetries) +} + + // ensureCosignBinary checks if the cosign binary exists in the specified directory and installs it if not. func ensureCosignBinary(ctx context.Context, s *store.Layout) (string, error) { l := log.FromContext(ctx)