mirror of
https://github.com/fluxcd/flux2.git
synced 2025-10-29 23:37:47 +08:00
Add oci:// prefix
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
8
.github/workflows/e2e.yaml
vendored
8
.github/workflows/e2e.yaml
vendored
@ -175,17 +175,17 @@ jobs:
|
||||
/tmp/flux delete source git podinfo --silent
|
||||
- name: flux oci artifacts
|
||||
run: |
|
||||
/tmp/flux push artifact localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
/tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
--path="./manifests" \
|
||||
--source="${{ github.repositoryUrl }}" \
|
||||
--revision="${{ github.ref }}/${{ github.sha }}"
|
||||
/tmp/flux tag artifact localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
/tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
|
||||
--tag latest
|
||||
/tmp/flux list artifacts localhost:5000/fluxcd/flux
|
||||
/tmp/flux list artifacts oci://localhost:5000/fluxcd/flux
|
||||
- name: flux oci repositories
|
||||
run: |
|
||||
/tmp/flux create source oci podinfo-oci \
|
||||
--url ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--url oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--tag-semver 6.1.x \
|
||||
--interval 10m
|
||||
/tmp/flux create kustomization podinfo-oci \
|
||||
|
||||
@ -42,7 +42,7 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
|
||||
Long: `The create source oci command generates an OCIRepository resource and waits for it to be ready.`,
|
||||
Example: ` # Create an OCIRepository for a public container image
|
||||
flux create source oci podinfo \
|
||||
--url=ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \
|
||||
--tag=6.1.6 \
|
||||
--interval=10m
|
||||
`,
|
||||
@ -67,7 +67,7 @@ func init() {
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, "tag-semver", "", "the OCI artifact tag semver range")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
||||
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
|
||||
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
|
||||
|
||||
createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
|
||||
|
||||
@ -38,12 +38,12 @@ func TestCreateSourceOCI(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "export manifest",
|
||||
args: "create source oci podinfo --url=ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --export",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --export",
|
||||
assertFunc: assertGoldenFile("./testdata/oci/export.golden"),
|
||||
},
|
||||
{
|
||||
name: "export manifest with secret",
|
||||
args: "create source oci podinfo --url=ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --secret-ref=creds --export",
|
||||
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --secret-ref=creds --export",
|
||||
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ var listArtifactsCmd = &cobra.Command{
|
||||
Long: `The list command fetches the tags and their metadata from a remote OCI repository.
|
||||
The list command uses the credentials from '~/.docker/config.json'.`,
|
||||
Example: `# list the artifacts stored in an OCI repository
|
||||
flux list artifact ghcr.io/org/manifests/app
|
||||
flux list artifact oci://ghcr.io/org/manifests/app
|
||||
`,
|
||||
RunE: listArtifactsCmdRun,
|
||||
}
|
||||
@ -41,13 +41,18 @@ func init() {
|
||||
|
||||
func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("artifact repository is required")
|
||||
return fmt.Errorf("artifact repository URL is required")
|
||||
}
|
||||
url := args[0]
|
||||
ociURL := args[0]
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metas, err := oci.List(ctx, url)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -387,6 +387,9 @@ func resetCmdArgs() {
|
||||
createArgs = createFlags{}
|
||||
getArgs = GetFlags{}
|
||||
sourceHelmArgs = sourceHelmFlags{}
|
||||
sourceOCIRepositoryArgs = sourceOCIRepositoryFlags{}
|
||||
sourceGitArgs = sourceGitFlags{}
|
||||
sourceBucketArgs = sourceBucketFlags{}
|
||||
secretGitArgs = NewSecretGitFlags()
|
||||
*kubeconfigArgs.Namespace = rootArgs.defaults.Namespace
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ var pullArtifactCmd = &cobra.Command{
|
||||
Long: `The pull artifact command downloads and extracts the OCI artifact content to the given path.
|
||||
The pull command uses the credentials from '~/.docker/config.json'.`,
|
||||
Example: `# Pull an OCI artifact created by flux from GHCR
|
||||
flux pull artifact ghcr.io/org/manifests/app:v0.0.1 --output ./path/to/local/manifests
|
||||
flux pull artifact oci://ghcr.io/org/manifests/app:v0.0.1 --output ./path/to/local/manifests
|
||||
`,
|
||||
RunE: pullArtifactCmdRun,
|
||||
}
|
||||
@ -50,9 +50,9 @@ func init() {
|
||||
|
||||
func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("artifact name is required")
|
||||
return fmt.Errorf("artifact URL is required")
|
||||
}
|
||||
url := args[0]
|
||||
ociURL := args[0]
|
||||
|
||||
if pullArtifactArgs.output == "" {
|
||||
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
|
||||
@ -62,6 +62,11 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
|
||||
}
|
||||
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ var pushArtifactCmd = &cobra.Command{
|
||||
Long: `The push artifact command creates a tarball from the given directory and uploads the artifact to a OCI repository.
|
||||
The push command uses the credentials from '~/.docker/config.json'.`,
|
||||
Example: `# Push the local manifests to GHCR
|
||||
flux push artifact ghcr.io/org/manifests/app:v0.0.1 \
|
||||
flux push artifact oci://ghcr.io/org/manifests/app:v0.0.1 \
|
||||
--path="./path/to/local/manifests" \
|
||||
--source="$(git config --get remote.origin.url)" \
|
||||
--revision="$(git branch --show-current)/$(git rev-parse HEAD)"
|
||||
@ -57,9 +57,9 @@ func init() {
|
||||
|
||||
func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("artifact name is required")
|
||||
return fmt.Errorf("artifact URL is required")
|
||||
}
|
||||
url := args[0]
|
||||
ociURL := args[0]
|
||||
|
||||
if pushArtifactArgs.source == "" {
|
||||
return fmt.Errorf("--source is required")
|
||||
@ -73,6 +73,11 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
|
||||
}
|
||||
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fs, err := os.Stat(pushArtifactArgs.path); err != nil || !fs.IsDir() {
|
||||
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ func TestSourceOCI(t *testing.T) {
|
||||
goldenFile string
|
||||
}{
|
||||
{
|
||||
"create source oci thrfg --url=ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m",
|
||||
"create source oci thrfg --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m",
|
||||
"testdata/oci/create_source_oci.golden",
|
||||
},
|
||||
{
|
||||
|
||||
@ -29,7 +29,7 @@ var tagArtifactCmd = &cobra.Command{
|
||||
Long: `The tag artifact command creates tags for the given OCI artifact.
|
||||
The tag command uses the credentials from '~/.docker/config.json'.`,
|
||||
Example: `# Tag an artifact version as latest
|
||||
flux tag artifact ghcr.io/org/manifests/app:v0.0.1 --tag latest
|
||||
flux tag artifact oci://ghcr.io/org/manifests/app:v0.0.1 --tag latest
|
||||
`,
|
||||
RunE: tagArtifactCmdRun,
|
||||
}
|
||||
@ -49,12 +49,17 @@ func tagArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("artifact name is required")
|
||||
}
|
||||
url := args[0]
|
||||
ociURL := args[0]
|
||||
|
||||
if len(tagArtifactArgs.tags) < 1 {
|
||||
return fmt.Errorf("--tag is required")
|
||||
}
|
||||
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
|
||||
2
cmd/flux/testdata/oci/export.golden
vendored
2
cmd/flux/testdata/oci/export.golden
vendored
@ -8,5 +8,5 @@ spec:
|
||||
interval: 10m0s
|
||||
ref:
|
||||
tag: 6.1.6
|
||||
url: ghcr.io/stefanprodan/manifests/podinfo
|
||||
url: oci://ghcr.io/stefanprodan/manifests/podinfo
|
||||
|
||||
|
||||
@ -10,5 +10,5 @@ spec:
|
||||
tag: 6.1.6
|
||||
secretRef:
|
||||
name: creds
|
||||
url: ghcr.io/stefanprodan/manifests/podinfo
|
||||
url: oci://ghcr.io/stefanprodan/manifests/podinfo
|
||||
|
||||
|
||||
54
internal/oci/url.go
Normal file
54
internal/oci/url.go
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package oci
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
)
|
||||
|
||||
// ParseArtifactURL validates the OCI URL and returns the address of the artifact.
|
||||
func ParseArtifactURL(ociURL string) (string, error) {
|
||||
if !strings.HasPrefix(ociURL, sourcev1.OCIRepositoryPrefix) {
|
||||
return "", fmt.Errorf("URL must be in format 'oci://<domain>/<org>/<repo>'")
|
||||
}
|
||||
|
||||
url := strings.TrimPrefix(ociURL, sourcev1.OCIRepositoryPrefix)
|
||||
if _, err := name.ParseReference(url); err != nil {
|
||||
return "", fmt.Errorf("'%s' invalid URL: %w", ociURL, err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// ParseRepositoryURL validates the OCI URL and returns the address of the artifact repository.
|
||||
func ParseRepositoryURL(ociURL string) (string, error) {
|
||||
if !strings.HasPrefix(ociURL, sourcev1.OCIRepositoryPrefix) {
|
||||
return "", fmt.Errorf("URL must be in format 'oci://<domain>/<org>/<repo>'")
|
||||
}
|
||||
|
||||
url := strings.TrimPrefix(ociURL, sourcev1.OCIRepositoryPrefix)
|
||||
ref, err := name.ParseReference(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("'%s' invalid URL: %w", ociURL, err)
|
||||
}
|
||||
|
||||
return ref.Context().Name(), nil
|
||||
}
|
||||
@ -15,4 +15,4 @@ patchesJson6902:
|
||||
# TODO: remove the hardcoded image when OCIRepository is released
|
||||
images:
|
||||
- name: fluxcd/source-controller
|
||||
newTag: oci-8509ac03
|
||||
newTag: oci-ba5f5353
|
||||
|
||||
Reference in New Issue
Block a user