diff --git a/main.go b/main.go index 503d4599..640fd422 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,9 @@ const ( // Docker Registry authentication defaultDockerUserEnvironmentVariable = "DOCKER_USER" defaultDockerPasswordEnvironmentVariable = "DOCKER_PASSWORD" + // Staging Registry authentication + defaultStagingUserEnvironmentVariable = "STAGING_USER" + defaultStagingPasswordEnvironmentVariable = "STAGING_PASSWORD" // Prime Registry authentication defaultPrimeUserEnvironmentVariable = "PRIME_USER" defaultPrimePasswordEnvironmentVariable = "PRIME_PASSWORD" @@ -120,6 +123,10 @@ var ( DockerUser string // DockerPassword is the password provided by EIO DockerPassword string + // StagingUser is the username provided by EIO + StagingUser string + // StagingPassword is the password provided by EIO + StagingPassword string // PrimeUser is the username provided by EIO PrimeUser string // PrimePassword is the password provided by EIO @@ -305,6 +312,20 @@ func main() { EnvVar: defaultDockerPasswordEnvironmentVariable, Destination: &DockerPassword, } + stagingUserFlag := cli.StringFlag{ + Name: "staging-user", + Usage: "--staging-user=******** || STAGING_USER=*******", + Required: true, + EnvVar: defaultStagingUserEnvironmentVariable, + Destination: &StagingUser, + } + stagingPasswordFlag := cli.StringFlag{ + Name: "staging-password", + Usage: "--staging-password=******** || STAGING_PASSWORD=*******", + Required: true, + EnvVar: defaultStagingPasswordEnvironmentVariable, + Destination: &StagingPassword, + } primeUserFlag := cli.StringFlag{ Name: "prime-user", Usage: "--prime-user=******** || PRIME_USER=*******", @@ -402,7 +423,7 @@ func main() { Name: "sync-registries", Usage: "Fetch, list and compare SUSE's registries and create yaml files with what is supposed to be synced from Docker Hub", Action: syncRegistries, - Flags: []cli.Flag{dockerUserFlag, dockerPasswordFlag, primeUserFlag, primePasswordFlag, primeURLFlag}, + Flags: []cli.Flag{dockerUserFlag, dockerPasswordFlag, stagingUserFlag, stagingPasswordFlag, primeUserFlag, primePasswordFlag, primeURLFlag}, }, { Name: "index", @@ -643,16 +664,20 @@ func syncRegistries(c *cli.Context) { emptyURL := PrimeURL == "" emptyDockerUser := DockerUser == "" emptyDockerPass := DockerPassword == "" - if emptyUser || emptyPass || emptyURL || emptyDockerUser || emptyDockerPass { + emptyStagingUser := StagingUser == "" + emptyStagingPass := StagingPassword == "" + if emptyUser || emptyPass || emptyURL || emptyDockerUser || emptyDockerPass || emptyStagingUser || emptyStagingPass { logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("User Empty", emptyUser)) logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("Password Empty", emptyPass)) logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("URL Empty", emptyURL)) logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("Docker User Empty", emptyDockerUser)) logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("Docker Pass Empty", emptyDockerPass)) + logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("Staging User Empty", emptyStagingUser)) + logger.Log(ctx, slog.LevelError, "missing credential", slog.Bool("Staging Pass Empty", emptyStagingPass)) logger.Fatal(ctx, errors.New("no credentials provided for sync").Error()) } - if err := registries.Sync(ctx, PrimeUser, PrimePassword, PrimeURL, DockerUser, DockerPassword); err != nil { + if err := registries.Sync(ctx, PrimeUser, PrimePassword, PrimeURL, DockerUser, DockerPassword, StagingUser, StagingPassword); err != nil { logger.Fatal(ctx, err.Error()) } } diff --git a/pkg/registries/cosign.go b/pkg/registries/cosign.go index ea0cdbf8..0a2d87ed 100644 --- a/pkg/registries/cosign.go +++ b/pkg/registries/cosign.go @@ -45,7 +45,8 @@ type repoImage struct { // sourceRegistry will be either Staging Registry or Docker Hub type sourceRegistry struct { - ociOpts []ociremote.Option + dockerOpts []ociremote.Option + stagingOpts []ociremote.Option } type primeRegistry struct { @@ -68,8 +69,8 @@ type tagMap func(name.Reference, ...ociremote.Option) (name.Tag, error) // // There is only one destination: // - Prime Registry -func Sync(ctx context.Context, primeUser, primePass, primeURL, dockerUser, dockerPass string) error { - s, err := prepareSync(ctx, primeUser, primePass, dockerUser, dockerPass) +func Sync(ctx context.Context, primeUser, primePass, primeURL, dockerUser, dockerPass, stagingUser, stagingPass string) error { + s, err := prepareSync(ctx, primeUser, primePass, dockerUser, dockerPass, stagingUser, stagingPass) if err != nil { return err } @@ -131,7 +132,7 @@ func Sync(ctx context.Context, primeUser, primePass, primeURL, dockerUser, docke // prepareSync checks if the prime credentials are provided and creates the synchronizer // with all the oci,naming and remote options needed. -func prepareSync(ctx context.Context, primeUser, primePass, dockerUser, dockerPass string) (*synchronizer, error) { +func prepareSync(ctx context.Context, primeUser, primePass, dockerUser, dockerPass, stagingUser, staginPass string) (*synchronizer, error) { // Use strict validation for pulling and pushing // These options control how image references (e.g., "myregistry/myimage:tag") // are parsed and validated by go-containerregistry's 'name' package. @@ -151,29 +152,43 @@ func prepareSync(ctx context.Context, primeUser, primePass, dockerUser, dockerPa // applied to the puller and subsequently used by cosign's oci/remote // package when fetching signed entities. - clientOpts := []remote.Option{ + dockerClientOpts := []remote.Option{ remote.WithContext(ctx), remote.WithUserAgent(uaString), remote.WithAuth(&authn.Basic{Username: dockerUser, Password: dockerPass}), remote.WithTransport(tr), } + stagingClientOpts := []remote.Option{ + remote.WithContext(ctx), + remote.WithUserAgent(uaString), + remote.WithAuth(&authn.Basic{Username: stagingUser, Password: staginPass}), + remote.WithTransport(tr), + } // reuse new remote puller for efficiency across operations. // puller interacts only with docker.io and staging registry. - puller, err := remote.NewPuller(clientOpts...) + dockerPuller, err := remote.NewPuller(dockerClientOpts...) if err == nil { - clientOpts = append(clientOpts, remote.Reuse(puller)) + dockerClientOpts = append(dockerClientOpts, remote.Reuse(dockerPuller)) + } + stagingPuller, err := remote.NewPuller(stagingClientOpts...) + if err == nil { + stagingClientOpts = append(stagingClientOpts, remote.Reuse(stagingPuller)) } // These options are specifically for cosign's 'pkg/oci/remote' functions // (e.g., ociremote.SignedEntity, ociremote.SignatureTag). They bridge the // 'go-containerregistry' remote options to cosign's operations. - pullerOpts := []ociremote.Option{ + dockerPullerOpts := []ociremote.Option{ + ociremote.WithNameOptions(nameOpts...), + } + stagingPullerOpts := []ociremote.Option{ ociremote.WithNameOptions(nameOpts...), } // Embed the 'go-containerregistry' remote options // (context, user agent, keychain auth, insecure transport) into cosign's client setup. - pullerOpts = append(pullerOpts, ociremote.WithRemoteOptions(clientOpts...)) + dockerPullerOpts = append(dockerPullerOpts, ociremote.WithRemoteOptions(dockerClientOpts...)) + stagingPullerOpts = append(stagingPullerOpts, ociremote.WithRemoteOptions(stagingClientOpts...)) // prime (destination) registry. They use explicit basic authentication? remoteOpts := []remote.Option{ @@ -190,7 +205,8 @@ func prepareSync(ctx context.Context, primeUser, primePass, dockerUser, dockerPa return &synchronizer{ nameOpts: nameOpts, sourceRegistry: &sourceRegistry{ - ociOpts: pullerOpts, + dockerOpts: dockerPullerOpts, + stagingOpts: stagingPullerOpts, }, primeRegistry: &primeRegistry{ pusher: pusher, @@ -262,7 +278,7 @@ func (s *synchronizer) pullFromDocker(ctx context.Context, imgRepo string) error // Get the base repository reference for the source image. srcRepoRef := s.repoImage.srcRef.Context() - root, err := ociremote.SignedEntity(s.repoImage.srcRef, s.sourceRegistry.ociOpts...) + root, err := ociremote.SignedEntity(s.repoImage.srcRef, s.sourceRegistry.dockerOpts...) if err != nil { logger.Log(ctx, slog.LevelError, "signedEntity failure", logger.Err(err)) return err @@ -318,7 +334,7 @@ func (s *synchronizer) pullFromStaging(ctx context.Context, imgRepo string) erro dstRepoRef := s.repoImage.dstRef.Context() // An oci.SignedEntity represents the image manifest itself AND all its associated - root, err := ociremote.SignedEntity(s.repoImage.srcRef, s.sourceRegistry.ociOpts...) + root, err := ociremote.SignedEntity(s.repoImage.srcRef, s.sourceRegistry.stagingOpts...) if err != nil { logger.Log(ctx, slog.LevelError, "signedEntity failure", logger.Err(err)) return err @@ -337,7 +353,7 @@ func (s *synchronizer) pullFromStaging(ctx context.Context, imgRepo string) erro copyTag := func(tm tagMap) error { // Construct the *expected name* (e.g., "myimage:sha256-digest.sig") // for the artifact based on its digest. This call does NOT confirm existence yet. - src, err := tm(srcDigest, s.sourceRegistry.ociOpts...) + src, err := tm(srcDigest, s.sourceRegistry.stagingOpts...) if err != nil { return err }