Add list artifacts command

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
pull/2971/head
Stefan Prodan 3 years ago
parent 2d8db4f20d
commit 2f35367a7f
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF

@ -19,7 +19,6 @@ package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
@ -53,8 +52,10 @@ var createSourceOCIRepositoryCmd = &cobra.Command{
type sourceOCIRepositoryFlags struct {
url string
tag string
semver string
digest string
secretRef string
serviceAccount string
ignorePaths []string
}
@ -63,8 +64,10 @@ var sourceOCIRepositoryArgs = sourceOCIRepositoryFlags{}
func init() {
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, "url", "", "the OCI repository URL")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, "tag", "", "the OCI artifact tag")
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 an existing secret containing credentials")
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().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
@ -77,8 +80,8 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("url is required")
}
if sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" {
return fmt.Errorf("--tag or --digest is required")
if sourceOCIRepositoryArgs.semver == "" && sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" {
return fmt.Errorf("--tag, --tag-semver or --digest is required")
}
sourceLabels, err := parseLabels()
@ -86,12 +89,6 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
return err
}
tmpDir, err := os.MkdirTemp("", name)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
var ignorePaths *string
if len(sourceOCIRepositoryArgs.ignorePaths) > 0 {
ignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, "\n")
@ -114,20 +111,27 @@ func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
},
}
if sourceOCIRepositoryArgs.tag != "" {
repository.Spec.Reference.Tag = sourceOCIRepositoryArgs.tag
if digest := sourceOCIRepositoryArgs.digest; digest != "" {
repository.Spec.Reference.Digest = digest
}
if sourceOCIRepositoryArgs.digest != "" {
repository.Spec.Reference.Digest = sourceOCIRepositoryArgs.digest
if semver := sourceOCIRepositoryArgs.semver; semver != "" {
repository.Spec.Reference.SemVer = semver
}
if tag := sourceOCIRepositoryArgs.tag; tag != "" {
repository.Spec.Reference.Tag = tag
}
if createSourceArgs.fetchTimeout > 0 {
repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
}
if sourceOCIRepositoryArgs.secretRef != "" {
if saName := sourceOCIRepositoryArgs.serviceAccount; saName != "" {
repository.Spec.ServiceAccountName = saName
}
if secretName := sourceOCIRepositoryArgs.secretRef; secretName != "" {
repository.Spec.SecretRef = &meta.LocalObjectReference{
Name: sourceOCIRepositoryArgs.secretRef,
Name: secretName,
}
}

@ -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 listCmd = &cobra.Command{
Use: "list",
Short: "List artifacts",
Long: "The list command is used for printing the OCI artifacts metadata.",
}
func init() {
rootCmd.AddCommand(listCmd)
}

@ -0,0 +1,68 @@
/*
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"
"github.com/fluxcd/flux2/internal/oci"
"github.com/fluxcd/flux2/pkg/printers"
"github.com/spf13/cobra"
)
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 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
`,
RunE: listArtifactsCmdRun,
}
func init() {
listCmd.AddCommand(listArtifactsCmd)
}
func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact repository is required")
}
url := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
metas, err := oci.List(ctx, url)
if err != nil {
return err
}
var rows [][]string
for _, meta := range metas {
rows = append(rows, []string{meta.URL, meta.Digest, meta.Source, meta.Revision})
}
err = printers.TablePrinter([]string{"artifact", "digest", "source", "revision"}).Print(cmd.OutOrStdout(), rows)
if err != nil {
return err
}
return nil
}

@ -0,0 +1,70 @@
/*
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 (
"bytes"
"context"
"fmt"
"sort"
"strings"
"github.com/google/go-containerregistry/pkg/crane"
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
)
// List fetches the tags and their manifests for a given OCI repository.
func List(ctx context.Context, url string) ([]Metadata, error) {
metas := make([]Metadata, 0)
tags, err := crane.ListTags(url, craneOptions(ctx)...)
if err != nil {
return nil, fmt.Errorf("listing tags failed: %w", err)
}
sort.Slice(tags, func(i, j int) bool { return tags[i] > tags[j] })
for _, tag := range tags {
// exclude cosign signatures
if strings.HasSuffix(tag, ".sig") {
continue
}
meta := Metadata{
URL: fmt.Sprintf("%s:%s", url, tag),
}
manifestJSON, err := crane.Manifest(meta.URL, craneOptions(ctx)...)
if err != nil {
return nil, fmt.Errorf("fetching manifest failed: %w", err)
}
manifest, err := gcrv1.ParseManifest(bytes.NewReader(manifestJSON))
if err != nil {
return nil, fmt.Errorf("parsing manifest failed: %w", err)
}
meta.Digest = manifest.Config.Digest.String()
if m, err := MetadataFromAnnotations(manifest.Annotations); err == nil {
meta.Revision = m.Revision
meta.Source = m.Source
}
metas = append(metas, meta)
}
return metas, nil
}

@ -29,6 +29,7 @@ type Metadata struct {
Source string `json:"source_url"`
Revision string `json:"source_revision"`
Digest string `json:"digest"`
URL string `json:"url"`
}
func (m *Metadata) ToAnnotations() map[string]string {
@ -40,7 +41,7 @@ func (m *Metadata) ToAnnotations() map[string]string {
return annotations
}
func GetMetadata(annotations map[string]string) (*Metadata, error) {
func MetadataFromAnnotations(annotations map[string]string) (*Metadata, error) {
source, ok := annotations[SourceAnnotation]
if !ok {
return nil, fmt.Errorf("'%s' annotation not found", SourceAnnotation)

@ -47,7 +47,7 @@ func Pull(ctx context.Context, url, outDir string) (*Metadata, error) {
return nil, fmt.Errorf("parsing manifest failed: %w", err)
}
meta, err := GetMetadata(manifest.Annotations)
meta, err := MetadataFromAnnotations(manifest.Annotations)
if err != nil {
return nil, err
}

Loading…
Cancel
Save