Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce output format flag for zarf tools get-creds and zarf package list #3415

Merged
merged 4 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ linters-settings:
- (*github.com/spf13/cobra.Command).MarkFlagRequired
- (*github.com/spf13/pflag.FlagSet).MarkHidden
- (*github.com/spf13/pflag.FlagSet).MarkDeprecated
- fmt.Fprintln
- fmt.Fprint
sloglint:
no-mixed-args: true
key-naming-case: camel
Expand Down
3 changes: 2 additions & 1 deletion site/src/content/docs/commands/zarf_package_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ zarf package list [flags]
### Options

```
-h, --help help for list
-h, --help help for list
-o, --output-format outputFormat Prints the output in the specified format. Valid options: table, json, yaml (default table)
```

### Options inherited from parent commands
Expand Down
3 changes: 2 additions & 1 deletion site/src/content/docs/commands/zarf_tools_get-creds.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ $ zarf tools get-creds artifact
### Options

```
-h, --help help for get-creds
-h, --help help for get-creds
-o, --output-format outputFormat Prints the output in the specified format. Valid options: table, json, yaml (default table)
```

### Options inherited from parent commands
Expand Down
4 changes: 2 additions & 2 deletions site/src/content/docs/commands/zarf_version.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ zarf version [flags]
### Options

```
-h, --help help for version
-o, --output string Output format (yaml|json)
-h, --help help for version
-o, --output-format outputFormat Output format (yaml|json)
```

### Options inherited from parent commands
Expand Down
90 changes: 70 additions & 20 deletions src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
Expand All @@ -16,6 +18,7 @@ import (

"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/pkg/helpers/v2"
goyaml "github.com/goccy/go-yaml"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"oras.land/oras-go/v2/registry"
Expand Down Expand Up @@ -406,56 +409,103 @@ func (o *packageInspectOptions) run(cmd *cobra.Command, args []string) error {
return nil
}

type packageListOptions struct{}
type packageListOptions struct {
outputFormat outputFormat
outputWriter io.Writer
cluster *cluster.Cluster
}

func newPackageListOptions() *packageListOptions {
return &packageListOptions{
outputFormat: outputTable,
// TODO accept output writer as a parameter to the root Zarf command and pass it through here
outputWriter: message.OutputWriter,
}
}

func newPackageListCommand() *cobra.Command {
o := &packageListOptions{}
o := newPackageListOptions()

cmd := &cobra.Command{
Use: "list",
Aliases: []string{"l", "ls"},
Short: lang.CmdPackageListShort,
RunE: o.run,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
err := o.complete(ctx)
if err != nil {
return err
}
return o.run(ctx)
},
}

cmd.Flags().VarP(&o.outputFormat, "output-format", "o", "Prints the output in the specified format. Valid options: table, json, yaml")

return cmd
}

func (o *packageListOptions) run(cmd *cobra.Command, _ []string) error {
timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout)
func (o *packageListOptions) complete(ctx context.Context) error {
timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout)
defer cancel()
c, err := cluster.NewClusterWithWait(timeoutCtx)
if err != nil {
return err
}
o.cluster = c
return nil
}

ctx := cmd.Context()
deployedZarfPackages, err := c.GetDeployedZarfPackages(ctx)
// packageListInfo represents the package information for output.
type packageListInfo struct {
Package string `json:"package"`
Version string `json:"version"`
Components []string `json:"components"`
}

func (o *packageListOptions) run(ctx context.Context) error {
deployedZarfPackages, err := o.cluster.GetDeployedZarfPackages(ctx)
if err != nil && len(deployedZarfPackages) == 0 {
return fmt.Errorf("unable to get the packages deployed to the cluster: %w", err)
}

// Populate a matrix of all the deployed packages
packageData := [][]string{}

var packageList []packageListInfo
for _, pkg := range deployedZarfPackages {
var components []string

for _, component := range pkg.DeployedComponents {
components = append(components, component.Name)
}

packageData = append(packageData, []string{
pkg.Name, pkg.Data.Metadata.Version, fmt.Sprintf("%v", components),
packageList = append(packageList, packageListInfo{
Package: pkg.Name,
Version: pkg.Data.Metadata.Version,
Components: components,
})
}

header := []string{"Package", "Version", "Components"}
message.TableWithWriter(message.OutputWriter, header, packageData)

// Print out any unmarshalling errors
if err != nil {
return fmt.Errorf("unable to read all of the packages deployed to the cluster: %w", err)
switch o.outputFormat {
case outputJSON:
output, err := json.MarshalIndent(packageList, "", " ")
if err != nil {
return err
}
fmt.Fprintln(o.outputWriter, string(output))
case outputYAML:
output, err := goyaml.Marshal(packageList)
if err != nil {
return err
}
fmt.Fprint(o.outputWriter, string(output))
case outputTable:
header := []string{"Package", "Version", "Components"}
var packageData [][]string
for _, info := range packageList {
packageData = append(packageData, []string{
info.Package, info.Version, fmt.Sprintf("%v", info.Components),
})
}
message.TableWithWriter(o.outputWriter, header, packageData)
default:
return fmt.Errorf("unsupported output format: %s", o.outputFormat)
}
return nil
}
Expand Down
124 changes: 124 additions & 0 deletions src/cmd/package_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

// Package cmd contains the CLI commands for Zarf.
package cmd

import (
"bytes"
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"
"github.com/zarf-dev/zarf/src/api/v1alpha1"
"github.com/zarf-dev/zarf/src/config"
"github.com/zarf-dev/zarf/src/pkg/cluster"
"github.com/zarf-dev/zarf/src/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)

func TestPackageList(t *testing.T) {
t.Parallel()
tests := []struct {
name string
outputFormat outputFormat
file string
}{
{
name: "json package list",
outputFormat: outputJSON,
file: "expected.json",
},
{
name: "yaml package list",
outputFormat: outputYAML,
file: "expected.yaml",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := context.Background()
c := &cluster.Cluster{
Clientset: fake.NewClientset(),
}

packages := []types.DeployedPackage{
{
Name: "package1",
Data: v1alpha1.ZarfPackage{
Metadata: v1alpha1.ZarfMetadata{
Version: "0.42.0",
},
},
DeployedComponents: []types.DeployedComponent{
{
Name: "component1",
},
{
Name: "component2",
},
},
},
{
Name: "package2",
Data: v1alpha1.ZarfPackage{
Metadata: v1alpha1.ZarfMetadata{
Version: "1.0.0",
},
},
DeployedComponents: []types.DeployedComponent{
{
Name: "component3",
},
{
Name: "component4",
},
},
},
}

for _, p := range packages {
b, err := json.Marshal(p)
require.NoError(t, err)
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: strings.Join([]string{config.ZarfPackagePrefix, p.Name}, ""),
Namespace: "zarf",
Labels: map[string]string{
cluster.ZarfPackageInfoLabel: p.Name,
},
},
Data: map[string][]byte{
"data": b,
},
}
_, err = c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &secret, metav1.CreateOptions{})
require.NoError(t, err)
}
buf := new(bytes.Buffer)
listOpts := packageListOptions{
outputFormat: tt.outputFormat,
outputWriter: buf,
cluster: c,
}
err := listOpts.run(ctx)
require.NoError(t, err)
b, err := os.ReadFile(filepath.Join("testdata", "package-list", tt.file))
require.NoError(t, err)
if tt.outputFormat == outputJSON {
require.JSONEq(t, string(b), buf.String())
}
if tt.outputFormat == outputYAML {
require.YAMLEq(t, string(b), buf.String())
}
})
}
}
30 changes: 30 additions & 0 deletions src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/zarf-dev/zarf/src/config"
"github.com/zarf-dev/zarf/src/config/lang"
Expand All @@ -41,6 +42,35 @@ var (
OutputWriter = os.Stdout
)

type outputFormat string

const (
outputTable outputFormat = "table"
outputJSON outputFormat = "json"
outputYAML outputFormat = "yaml"
)

// must implement this interface for cmd.Flags().VarP
var _ pflag.Value = (*outputFormat)(nil)

func (o *outputFormat) Set(s string) error {
switch s {
case string(outputTable), string(outputJSON), string(outputYAML):
*o = outputFormat(s)
return nil
default:
return fmt.Errorf("invalid output format: %s", s)
}
}

func (o *outputFormat) String() string {
return string(*o)
}

func (o *outputFormat) Type() string {
return "outputFormat"
}

var rootCmd = NewZarfCommand()

func preRun(cmd *cobra.Command, _ []string) error {
Expand Down
37 changes: 37 additions & 0 deletions src/cmd/testdata/get-creds/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"application": "Registry",
"username": "push-user",
"password": "push-password",
"connect": "zarf connect registry",
"getCredsKey": "registry"
},
{
"application": "Registry (read-only)",
"username": "pull-user",
"password": "pull-password",
"connect": "zarf connect registry",
"getCredsKey": "registry-readonly"
},
{
"application": "Git",
"username": "push-user",
"password": "push-password",
"connect": "zarf connect git",
"getCredsKey": "git"
},
{
"application": "Git (read-only)",
"username": "pull-user",
"password": "pull-password",
"connect": "zarf connect git",
"getCredsKey": "git-readonly"
},
{
"application": "Artifact Token",
"username": "push-user",
"password": "push-password",
"connect": "zarf connect git",
"getCredsKey": "artifact"
}
]
Loading
Loading