Repository: regclient/regclient
Pull request #873 (2024-12-04 20:19:48)

Feat: Add external flag to regctl artifact put
<!--

Commits must be signed indicating your agreement to the [DCO](https://developercertificate.org/).
See [DCO missing](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) for steps to fix a missing signoff.

-->

### Fixes issue

<!-- If this is a bug fix, include "fixes #xxxx", or "closes #xxxx" -->

### Describe the change

This adds a `--external` flag to `regctl artifact put`, aligning it with other `regctl artifact` commands.
<!-- Include the type of change: bug fix, new feature, breaking change, documentation update -->
<!-- Describe what was changed, why the change was made, and how it was implemented -->

### How to verify it

```shell
echo "hello external artifact" | regctl artifact put --subject alpine --external ocidir://test/external --artifact-type applicaiton/example.test 
```
<!-- Include steps that can be taken to verify the change -->

### Changelog text

- Feat: Add external flag to regctl artifact put.
<!-- If the release changelog should have an entry for this, include it here -->

### Please verify and check that the pull request fulfills the following requirements

<!-- Mark the following with an [X] to verify they are included -->

- [X] Tests have been added or not applicable
- [X] Documentation has been added, updated, or not applicable
- [X] Changes have been rebased to main
- [X] Multiple commits to the same code have been squashed

<!-- markdownlint-disable-file MD041 -->



Patch:
diff --git a/cmd/regctl/artifact.go b/cmd/regctl/artifact.go
index bb4101b..cec0632 100644
--- a/cmd/regctl/artifact.go
+++ b/cmd/regctl/artifact.go
@@ -222,6 +222,7 @@ regctl artifact tree --digest-tags ghcr.io/regclient/regsync:latest`,
 	_ = artifactPutCmd.RegisterFlagCompletionFunc("config-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return configKnownTypes, cobra.ShellCompDirectiveNoFileComp
 	})
+	artifactPutCmd.Flags().StringVar(&artifactOpts.externalRepo, "external", "", "Push referrers to a separate repository")
 	artifactPutCmd.Flags().StringArrayVarP(&artifactOpts.artifactFile, "file", "f", []string{}, "Artifact filename")
 	artifactPutCmd.Flags().StringArrayVarP(&artifactOpts.artifactFileMT, "file-media-type", "m", []string{}, "Set the mediaType for the individual files")
 	_ = artifactPutCmd.RegisterFlagCompletionFunc("file-media-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -708,6 +709,21 @@ func (artifactOpts *artifactCmd) runArtifactPut(cmd *cobra.Command, args []strin
 		}
 		r = rArt
 	}
+	if artifactOpts.externalRepo != "" {
+		if rSubject.IsZero() {
+			return fmt.Errorf("pushing a referrer to an external repository requires a subject%.0w", errs.ErrUnsupported)
+		}
+		rExt, err := ref.New(artifactOpts.externalRepo)
+		if err != nil {
+			return err
+		}
+		if rArt.IsSet() && !ref.EqualRepository(rExt, rArt) {
+			return fmt.Errorf("push by reference and external to separate repositories is not supported%.0w", errs.ErrUnsupported)
+		}
+		if !rArt.IsSet() {
+			r = rExt
+		}
+	}
 	if !rArt.IsSet() && !rSubject.IsSet() {
 		return fmt.Errorf("either a reference or subject must be provided")
 	}


Test patch:
diff --git a/cmd/regctl/artifact_test.go b/cmd/regctl/artifact_test.go
index 90a5a06..32e3097 100644
--- a/cmd/regctl/artifact_test.go
+++ b/cmd/regctl/artifact_test.go
@@ -232,10 +232,32 @@ func TestArtifactPut(t *testing.T) {
 			in:   testData,
 		},
 		{
-			name: "Put external subject",
+			name: "Put subject to external repo",
+			args: []string{"artifact", "put", "--artifact-type", "application/vnd.example", "--subject", "ocidir://" + testDir + ":put-example-at", "--external", "ocidir://" + testDir + "/external"},
+			in:   testData,
+		},
+		{
+			name: "Put subject to external name",
 			args: []string{"artifact", "put", "--artifact-type", "application/vnd.example", "--subject", "ocidir://" + testDir + ":put-example-at", "ocidir://" + testDir + "/external:external-subj"},
 			in:   testData,
 		},
+		{
+			name: "Put subject to external repo and name",
+			args: []string{"artifact", "put", "--artifact-type", "application/vnd.example", "--subject", "ocidir://" + testDir + ":put-example-at", "--external", "ocidir://" + testDir + "/external", "ocidir://" + testDir + "/external:external-subj"},
+			in:   testData,
+		},
+		{
+			name:      "Put external name without subject",
+			args:      []string{"artifact", "put", "--artifact-type", "application/vnd.example", "--external", "ocidir://" + testDir + "/external", "ocidir://" + testDir + "/external:external-subj"},
+			in:        testData,
+			expectErr: errs.ErrUnsupported,
+		},
+		{
+			name:      "Put subject to external repo and different name",
+			args:      []string{"artifact", "put", "--artifact-type", "application/vnd.example", "--subject", "ocidir://" + testDir + ":put-example-at", "--external", "ocidir://" + testDir + "/external", "ocidir://" + testDir + "/copy:copy-subj"},
+			in:        testData,
+			expectErr: errs.ErrUnsupported,
+		},
 		{
 			name: "Put create index",
 			args: []string{"artifact", "put", "--artifact-type", "application/vnd.example", "--annotation", "test=a", "--platform", "linux/amd64", "--index", "ocidir://" + testDir + ":index"},