Skip to content

Commit

Permalink
Add artifact commands
Browse files Browse the repository at this point in the history
Implement build, push, pull and tag artifact commands.
For authentication purposes, all `flux <verb> artifact` commands are using the '~/.docker/config.json' config file and the Docker credential helpers.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
  • Loading branch information
stefanprodan committed Jun 21, 2022
1 parent f009fbe commit 5f2dcf0
Show file tree
Hide file tree
Showing 7 changed files with 416 additions and 0 deletions.
69 changes: 69 additions & 0 deletions cmd/flux/build_artifact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
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 main

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/fluxcd/flux2/internal/oci"
)

var buildArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Build artifact",
Long: `The build artifact command creates an tgz file with the manifests from the given directory.`,
Example: `# Build the given manifests directory into an artifact
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz
`,
RunE: buildArtifactCmdRun,
}

type buildArtifactFlags struct {
output string
path string
}

var buildArtifactArgs buildArtifactFlags

func init() {
buildArtifactCmd.Flags().StringVar(&buildArtifactArgs.path, "path", "", "Path to the directory where the Kubernetes manifests are located.")
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "0", "artifact.tgz", "Path to where the artifact tgz file should be written.")
buildCmd.AddCommand(buildArtifactCmd)
}

func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
if buildArtifactArgs.path == "" {
return fmt.Errorf("invalid path %q", buildArtifactArgs.path)
}

if fs, err := os.Stat(buildArtifactArgs.path); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid path %q", buildArtifactArgs.path)
}

logger.Actionf("building artifact from %s", buildArtifactArgs.path)

if err := oci.Build(buildArtifactArgs.output, buildArtifactArgs.path); err != nil {
return fmt.Errorf("bulding artifact failed, error: %w", err)
}

logger.Successf("artifact created at %s", buildArtifactArgs.output)

return nil
}
31 changes: 31 additions & 0 deletions cmd/flux/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
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 main

import (
"github.com/spf13/cobra"
)

var pullCmd = &cobra.Command{
Use: "pull",
Short: "Pull artifacts",
Long: "The pull command is used to download OCI artifacts.",
}

func init() {
rootCmd.AddCommand(pullCmd)
}
81 changes: 81 additions & 0 deletions cmd/flux/pull_artifact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
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 main

import (
"context"
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/fluxcd/flux2/internal/oci"
)

var pullArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Push 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'.`,
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
`,
RunE: pullArtifactCmdRun,
}

type pullArtifactFlags struct {
output string
}

var pullArtifactArgs pullArtifactFlags

func init() {
pullArtifactCmd.Flags().StringVarP(&pullArtifactArgs.output, "output", "o", "", "Path where the artifact content should be extracted.")
pullCmd.AddCommand(pullArtifactCmd)
}

func pullArtifactCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact name is required")
}
url := args[0]

if pullArtifactArgs.output == "" {
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
}

if fs, err := os.Stat(pullArtifactArgs.output); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid output path %s", pullArtifactArgs.output)
}

ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()

logger.Actionf("pulling artifact from %s", url)

meta, err := oci.Pull(ctx, url, pullArtifactArgs.output)
if err != nil {
return err
}

logger.Successf("source %s", meta.Source)
logger.Successf("revision %s", meta.Revision)
logger.Successf("digest %s", meta.Digest)
logger.Successf("artifact content extracted to %s", pullArtifactArgs.output)

return nil
}
31 changes: 31 additions & 0 deletions cmd/flux/push.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2021 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 main

import (
"github.com/spf13/cobra"
)

var pushCmd = &cobra.Command{
Use: "push",
Short: "Push artifacts",
Long: "The push command is used to publish OCI artifacts.",
}

func init() {
rootCmd.AddCommand(pushCmd)
}
99 changes: 99 additions & 0 deletions cmd/flux/push_artifact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
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 main

import (
"context"
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/fluxcd/flux2/internal/oci"
)

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 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 \
--path="./path/to/local/manifests" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)/$(git rev-parse HEAD)"
`,
RunE: pushArtifactCmdRun,
}

type pushArtifactFlags struct {
path string
source string
revision string
}

var pushArtifactArgs pushArtifactFlags

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. Git URL.")
pushArtifactCmd.Flags().StringVar(&pushArtifactArgs.revision, "revision", "", "The source revision in the format '<branch|tag>/<commit-sha>'")
pushCmd.AddCommand(pushArtifactCmd)
}

func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact name is required")
}
url := args[0]

if pushArtifactArgs.source == "" {
return fmt.Errorf("--source is required")
}

if pushArtifactArgs.revision == "" {
return fmt.Errorf("--revision is required")
}

if pushArtifactArgs.path == "" {
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
}

if fs, err := os.Stat(pushArtifactArgs.path); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid path %q", pushArtifactArgs.path)
}

meta := oci.Metadata{
Source: pushArtifactArgs.source,
Revision: pushArtifactArgs.revision,
}

ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()

logger.Actionf("pushing artifact to %s", url)

digest, err := oci.Push(ctx, url, pushArtifactArgs.path, meta)
if err != nil {
return fmt.Errorf("pushing artifact failed: %w", err)
}

logger.Successf("artifact successfully pushed to %s", digest)

return nil

}
31 changes: 31 additions & 0 deletions cmd/flux/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2021 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 main

import (
"github.com/spf13/cobra"
)

var tagCmd = &cobra.Command{
Use: "tag",
Short: "Tag artifacts",
Long: "The tag command is used to tag OCI artifacts.",
}

func init() {
rootCmd.AddCommand(tagCmd)
}
Loading

0 comments on commit 5f2dcf0

Please sign in to comment.