Support logging in directly to the provider when pushing OCI artifacts
I've noticed during CI, that the current command already expected a configured Docker client to push artifacts to authenticated registries. Some users might not want to have the Docker client in their process (like a CI job) or build an handcrafted config.json file. This would allow this kind of behavior: ``` flux push artifact oci://my-registry.dev/foo:v1 \ --source xxx \ --revision xxx \ --path . \ --creds $TOKEN # Authenticate via "Bearer $TOKEN" Authorization header ``` Or via Autologin: ``` flux push artifact oci://012345678901.dkr.ecr.us-east-1.amazonaws.com/foo:v1 \ --source xxx \ --revision xxx \ --path . \ --provider aws ``` This has been implemented for: * flux push artifact * flux list artifact * flux tag artifact * flux pull artifact This will require another PR in https://github.com/fluxcd/pkg/pull/352 Signed-off-by: Adrien Fillon <adrien.fillon@manomano.com>
This commit is contained in:
@@ -20,6 +20,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
oci "github.com/fluxcd/pkg/oci/client"
|
||||
@@ -30,15 +32,23 @@ import (
|
||||
type listArtifactFlags struct {
|
||||
semverFilter string
|
||||
regexFilter string
|
||||
creds string
|
||||
provider flags.SourceOCIProvider
|
||||
}
|
||||
|
||||
var listArtifactArgs listArtifactFlags
|
||||
var listArtifactArgs = newListArtifactFlags()
|
||||
|
||||
func newListArtifactFlags() listArtifactFlags {
|
||||
return listArtifactFlags{
|
||||
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
|
||||
}
|
||||
}
|
||||
|
||||
var listArtifactsCmd = &cobra.Command{
|
||||
Use: "artifacts",
|
||||
Short: "list artifacts",
|
||||
Long: `The list command fetches the tags and their metadata from a remote OCI repository.
|
||||
The command uses the credentials from '~/.docker/config.json'.`,
|
||||
The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,
|
||||
Example: ` # List the artifacts stored in an OCI repository
|
||||
flux list artifact oci://ghcr.io/org/config/app
|
||||
`,
|
||||
@@ -48,6 +58,8 @@ The command uses the credentials from '~/.docker/config.json'.`,
|
||||
func init() {
|
||||
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.semverFilter, "filter-semver", "", "filter tags returned from the oci repository using semver")
|
||||
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.regexFilter, "filter-regex", "", "filter tags returned from the oci repository using regex")
|
||||
listArtifactsCmd.Flags().StringVar(&listArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
|
||||
listArtifactsCmd.Flags().Var(&listArtifactArgs.provider, "provider", listArtifactArgs.provider.Description())
|
||||
|
||||
listCmd.AddCommand(listArtifactsCmd)
|
||||
}
|
||||
@@ -61,12 +73,32 @@ func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
|
||||
if listArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && listArtifactArgs.creds != "" {
|
||||
logger.Actionf("logging in to registry with credentials")
|
||||
if err := ociClient.LoginWithCredentials(listArtifactArgs.creds); err != nil {
|
||||
return fmt.Errorf("could not login with credentials: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if listArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||
logger.Actionf("logging in to registry with provider credentials")
|
||||
ociProvider, err := listArtifactArgs.provider.ToOCIProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("provider not supported: %w", err)
|
||||
}
|
||||
|
||||
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
|
||||
return fmt.Errorf("error during login with provider: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts := oci.ListOptions{
|
||||
RegexFilter: listArtifactArgs.regexFilter,
|
||||
SemverFilter: listArtifactArgs.semverFilter,
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
oci "github.com/fluxcd/pkg/oci/client"
|
||||
@@ -30,7 +32,7 @@ var pullArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Pull artifact",
|
||||
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'.`,
|
||||
The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,
|
||||
Example: ` # Pull an OCI artifact created by flux from GHCR
|
||||
flux pull artifact oci://ghcr.io/org/manifests/app:v0.0.1 --output ./path/to/local/manifests
|
||||
`,
|
||||
@@ -38,13 +40,23 @@ The pull command uses the credentials from '~/.docker/config.json'.`,
|
||||
}
|
||||
|
||||
type pullArtifactFlags struct {
|
||||
output string
|
||||
output string
|
||||
creds string
|
||||
provider flags.SourceOCIProvider
|
||||
}
|
||||
|
||||
var pullArtifactArgs pullArtifactFlags
|
||||
var pullArtifactArgs = newPullArtifactFlags()
|
||||
|
||||
func newPullArtifactFlags() pullArtifactFlags {
|
||||
return pullArtifactFlags{
|
||||
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
pullArtifactCmd.Flags().StringVarP(&pullArtifactArgs.output, "output", "o", "", "path where the artifact content should be extracted.")
|
||||
pullArtifactCmd.Flags().StringVar(&pullArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
|
||||
pullArtifactCmd.Flags().Var(&pullArtifactArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
|
||||
pullCmd.AddCommand(pullArtifactCmd)
|
||||
}
|
||||
|
||||
@@ -62,7 +74,6 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
|
||||
}
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -71,6 +82,27 @@ func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
|
||||
if pullArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pullArtifactArgs.creds != "" {
|
||||
logger.Actionf("logging in to registry with credentials")
|
||||
if err := ociClient.LoginWithCredentials(pullArtifactArgs.creds); err != nil {
|
||||
return fmt.Errorf("could not login with credentials: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if pullArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||
logger.Actionf("logging in to registry with provider credentials")
|
||||
ociProvider, err := pullArtifactArgs.provider.ToOCIProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("provider not supported: %w", err)
|
||||
}
|
||||
|
||||
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
|
||||
return fmt.Errorf("error during login with provider: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Actionf("pulling artifact from %s", url)
|
||||
|
||||
meta, err := ociClient.Pull(ctx, url, pullArtifactArgs.output)
|
||||
|
||||
@@ -19,9 +19,12 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
oci "github.com/fluxcd/pkg/oci/client"
|
||||
)
|
||||
|
||||
@@ -29,7 +32,7 @@ var pushArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Push artifact",
|
||||
Long: `The push artifact command creates a tarball from the given directory and uploads the artifact to an OCI repository.
|
||||
The command uses the credentials from '~/.docker/config.json'.`,
|
||||
The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,
|
||||
Example: ` # Push manifests to GHCR using the short Git SHA as the OCI artifact tag
|
||||
echo $GITHUB_PAT | docker login ghcr.io --username flux --password-stdin
|
||||
flux push artifact oci://ghcr.io/org/config/app:$(git rev-parse --short HEAD) \
|
||||
@@ -43,6 +46,22 @@ The command uses the credentials from '~/.docker/config.json'.`,
|
||||
--path="./path/to/local/manifests" \
|
||||
--source="$(git config --get remote.origin.url)" \
|
||||
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
|
||||
|
||||
# Login directly to the registry provider
|
||||
# You might need to export the following variable if you use local config files for AWS:
|
||||
# export AWS_SDK_LOAD_CONFIG=1
|
||||
flux push artifact oci://<account>.dkr.ecr.<region>.amazonaws.com/foo:v1:$(git tag --points-at HEAD) \
|
||||
--path="./path/to/local/manifests" \
|
||||
--source="$(git config --get remote.origin.url)" \
|
||||
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)" \
|
||||
--provider aws
|
||||
|
||||
# Or pass credentials directly
|
||||
flux push artifact oci://docker.io/org/app-config:$(git tag --points-at HEAD) \
|
||||
--path="./path/to/local/manifests" \
|
||||
--source="$(git config --get remote.origin.url)" \
|
||||
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)" \
|
||||
--creds flux:$DOCKER_PAT
|
||||
`,
|
||||
RunE: pushArtifactCmdRun,
|
||||
}
|
||||
@@ -51,15 +70,25 @@ type pushArtifactFlags struct {
|
||||
path string
|
||||
source string
|
||||
revision string
|
||||
creds string
|
||||
provider flags.SourceOCIProvider
|
||||
ignorePaths []string
|
||||
}
|
||||
|
||||
var pushArtifactArgs pushArtifactFlags
|
||||
var pushArtifactArgs = newPushArtifactFlags()
|
||||
|
||||
func newPushArtifactFlags() pushArtifactFlags {
|
||||
return pushArtifactFlags{
|
||||
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.path, "path", "", "path to the directory where the Kubernetes manifests are located")
|
||||
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.source, "source", "", "the source address, e.g. the Git URL")
|
||||
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.revision, "revision", "", "the source revision in the format '<branch|tag>/<commit-sha>'")
|
||||
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
|
||||
pushArtifactCmd.Flags().Var(&pushArtifactArgs.provider, "provider", pushArtifactArgs.provider.Description())
|
||||
pushArtifactCmd.Flags().StringSliceVar(&pushArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
|
||||
|
||||
pushCmd.AddCommand(pushArtifactCmd)
|
||||
@@ -83,7 +112,6 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
|
||||
}
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,6 +129,27 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
|
||||
if pushArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && pushArtifactArgs.creds != "" {
|
||||
logger.Actionf("logging in to registry with credentials")
|
||||
if err := ociClient.LoginWithCredentials(pushArtifactArgs.creds); err != nil {
|
||||
return fmt.Errorf("could not login with credentials: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if pushArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||
logger.Actionf("logging in to registry with provider credentials")
|
||||
ociProvider, err := pushArtifactArgs.provider.ToOCIProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("provider not supported: %w", err)
|
||||
}
|
||||
|
||||
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
|
||||
return fmt.Errorf("error during login with provider: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Actionf("pushing artifact to %s", url)
|
||||
|
||||
digest, err := ociClient.Push(ctx, url, pushArtifactArgs.path, meta, pushArtifactArgs.ignorePaths)
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/flags"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
oci "github.com/fluxcd/pkg/oci/client"
|
||||
@@ -29,7 +31,7 @@ var tagArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Tag artifact",
|
||||
Long: `The tag artifact command creates tags for the given OCI artifact.
|
||||
The command uses the credentials from '~/.docker/config.json'.`,
|
||||
The command can read the credentials from '~/.docker/config.json' but they can also be passed with --creds. It can also login to a supported provider with the --provider flag.`,
|
||||
Example: ` # Tag an artifact version as latest
|
||||
flux tag artifact oci://ghcr.io/org/manifests/app:v0.0.1 --tag latest
|
||||
`,
|
||||
@@ -37,13 +39,23 @@ The command uses the credentials from '~/.docker/config.json'.`,
|
||||
}
|
||||
|
||||
type tagArtifactFlags struct {
|
||||
tags []string
|
||||
tags []string
|
||||
creds string
|
||||
provider flags.SourceOCIProvider
|
||||
}
|
||||
|
||||
var tagArtifactArgs tagArtifactFlags
|
||||
var tagArtifactArgs = newTagArtifactFlags()
|
||||
|
||||
func newTagArtifactFlags() tagArtifactFlags {
|
||||
return tagArtifactFlags{
|
||||
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
tagArtifactCmd.Flags().StringSliceVar(&tagArtifactArgs.tags, "tag", nil, "tag name")
|
||||
tagArtifactCmd.Flags().StringVar(&tagArtifactArgs.creds, "creds", "", "credentials for OCI registry in the format <username>[:<password>] if --provider is generic")
|
||||
tagArtifactCmd.Flags().Var(&tagArtifactArgs.provider, "provider", tagArtifactArgs.provider.Description())
|
||||
tagCmd.AddCommand(tagArtifactCmd)
|
||||
}
|
||||
|
||||
@@ -57,7 +69,6 @@ func tagArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("--tag is required")
|
||||
}
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
url, err := oci.ParseArtifactURL(ociURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -66,6 +77,27 @@ func tagArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
ociClient := oci.NewLocalClient()
|
||||
|
||||
if tagArtifactArgs.provider.String() == sourcev1.GenericOCIProvider && tagArtifactArgs.creds != "" {
|
||||
logger.Actionf("logging in to registry with credentials")
|
||||
if err := ociClient.LoginWithCredentials(tagArtifactArgs.creds); err != nil {
|
||||
return fmt.Errorf("could not login with credentials: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tagArtifactArgs.provider.String() != sourcev1.GenericOCIProvider {
|
||||
logger.Actionf("logging in to registry with provider credentials")
|
||||
ociProvider, err := tagArtifactArgs.provider.ToOCIProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("provider not supported: %w", err)
|
||||
}
|
||||
|
||||
if err := ociClient.LoginWithProvider(ctx, url, ociProvider); err != nil {
|
||||
return fmt.Errorf("error during login with provider: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Actionf("tagging artifact")
|
||||
|
||||
for _, tag := range tagArtifactArgs.tags {
|
||||
|
||||
Reference in New Issue
Block a user