Use single OCI layer selector flag

Signed-off-by: Dan Meier <me.daniel.meier@gmail.com>
Assisted-by: Codex/gpt-5
This commit is contained in:
Dan Meier 2026-05-13 16:11:18 +02:00
parent 63281daf2f
commit 57f0ac3142
No known key found for this signature in database
GPG key ID: 3A683CC6ECF7AB69
3 changed files with 65 additions and 0 deletions

View file

@ -53,6 +53,12 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
--verify-provider=cosign \
--verify-subject="^https://github.com/stefanprodan/podinfo/.github/workflows/release.yml@refs/tags/6.6.2$" \
--verify-issuer="^https://token.actions.githubusercontent.com$"
# Create an OCIRepository for a Helm chart layer
flux create source oci valkey-cluster \
--url=oci://example.com/charts/valkey \
--tag=0.11.6 \
--layer-selector=application/vnd.cncf.helm.chart.content.v1.tar+gzip:copy
`,
RunE: createSourceOCIRepositoryCmdRun,
}
@ -73,6 +79,7 @@ type sourceOCIRepositoryFlags struct {
ignorePaths []string
provider flags.SourceOCIProvider
insecure bool
layerSelector string
}
var sourceOCIRepositoryArgs = newSourceOCIFlags()
@ -99,6 +106,7 @@ func init() {
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.verifyOIDCIssuer, "verify-issuer", "", "regular expression to use for the OIDC issuer during signature verification")
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
createSourceOCIRepositoryCmd.Flags().BoolVar(&sourceOCIRepositoryArgs.insecure, "insecure", false, "for when connecting to a non-TLS registries over plain HTTP")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.layerSelector, "layer-selector", "", "the OCI artifact layer selector in the format '<media-type>:<operation>'")
createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
}
@ -114,6 +122,11 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("--tag, --tag-semver or --digest is required")
}
layerSelector, err := parseLayerSelector(sourceOCIRepositoryArgs.layerSelector)
if err != nil {
return err
}
sourceLabels, err := parseLabels()
if err != nil {
return err
@ -152,6 +165,7 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
if tag := sourceOCIRepositoryArgs.tag; tag != "" {
repository.Spec.Reference.Tag = tag
}
repository.Spec.LayerSelector = layerSelector
if createSourceArgs.fetchTimeout > 0 {
repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
@ -234,6 +248,28 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
return nil
}
func parseLayerSelector(selector string) (*sourcev1.OCILayerSelector, error) {
if selector == "" {
return nil, nil
}
mediaType, operation, found := strings.Cut(selector, ":")
if !found || mediaType == "" || operation == "" {
return nil, fmt.Errorf("invalid --layer-selector %q: must be in the format '<media-type>:<operation>'", selector)
}
switch operation {
case sourcev1.OCILayerExtract, sourcev1.OCILayerCopy:
default:
return nil, fmt.Errorf("invalid --layer-selector %q: operation must be %q or %q", selector, sourcev1.OCILayerExtract, sourcev1.OCILayerCopy)
}
return &sourcev1.OCILayerSelector{
MediaType: mediaType,
Operation: operation,
}, nil
}
func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{

View file

@ -81,6 +81,21 @@ func TestCreateSourceOCI(t *testing.T) {
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --verify-provider=cosign --verify-secret-ref=cosign-pub --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_verify_secret.golden"),
},
{
name: "export manifest with layer selector",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --interval 10m --layer-selector=application/vnd.cncf.helm.chart.content.v1.tar+gzip:copy --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_layer_selector.golden"),
},
{
name: "invalid layer selector operation",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --layer-selector=application/vnd.cncf.helm.chart.content.v1.tar+gzip:move --export",
assertFunc: assertError("invalid --layer-selector \"application/vnd.cncf.helm.chart.content.v1.tar+gzip:move\": operation must be \"extract\" or \"copy\""),
},
{
name: "invalid layer selector format",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.3.5 --layer-selector=application/vnd.cncf.helm.chart.content.v1.tar+gzip --export",
assertFunc: assertError("invalid --layer-selector \"application/vnd.cncf.helm.chart.content.v1.tar+gzip\": must be in the format '<media-type>:<operation>'"),
},
}
for _, tt := range tests {

View file

@ -0,0 +1,14 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 10m0s
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: 6.3.5
url: oci://ghcr.io/stefanprodan/manifests/podinfo