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

Implement list command in kubectl plugin #204

Merged
merged 1 commit into from
Oct 16, 2019
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
1 change: 1 addition & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/apis/rollouts/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const (
)

// RolloutStrategy defines strategy to apply during next rollout
// TODO(jessesuen): rename field names to match json tags to remove api violations
type RolloutStrategy struct {
// +optional
BlueGreenStrategy *BlueGreenStrategy `json:"blueGreen,omitempty"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"github.com/spf13/cobra"

"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/cmd/list"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/cmd/pause"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/cmd/resume"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/cmd/version"
Expand Down Expand Up @@ -30,6 +31,7 @@ func NewCmdArgoRollouts(o *options.ArgoRolloutsOptions) *cobra.Command {
return o.UsageErr(c)
},
}
cmd.AddCommand(list.NewCmdList(o))
cmd.AddCommand(pause.NewCmdPause(o))
cmd.AddCommand(resume.NewCmdResume(o))
cmd.AddCommand(version.NewCmdVersion(o))
Expand Down
181 changes: 181 additions & 0 deletions pkg/kubectl-argo-rollouts/cmd/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package list

import (
"context"
"fmt"
"text/tabwriter"
"time"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
argoprojv1alpha1 "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/typed/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/pkg/kubectl-argo-rollouts/options"
)

const (
example = `
# List rollouts
%[1]s list

# List rollouts from all namespaces
%[1]s list --all-namespaces

# List rollouts and watch for changes
%[1]s list --watch
`
)

type ListOptions struct {
name string
allNamespaces bool
watch bool
timestamps bool

options.ArgoRolloutsOptions
}

// NewCmdList returns a new instance of an `rollouts list` command
func NewCmdList(o *options.ArgoRolloutsOptions) *cobra.Command {
listOptions := ListOptions{
ArgoRolloutsOptions: *o,
}

var cmd = &cobra.Command{
Use: "list",
Short: "List rollouts",
Example: o.Example(example),
SilenceUsage: true,
RunE: func(c *cobra.Command, args []string) error {
var namespace string
if listOptions.allNamespaces {
namespace = metav1.NamespaceAll
} else {
namespace = o.Namespace()
}
rolloutIf := o.RolloutsClientset().ArgoprojV1alpha1().Rollouts(namespace)
opts := listOptions.ListOptions()
rolloutList, err := rolloutIf.List(opts)
if err != nil {
return err
}
err = listOptions.PrintRolloutTable(rolloutList)
if err != nil {
return err
}
if listOptions.watch {
ctx := context.Background()
err = listOptions.PrintRolloutUpdates(ctx, rolloutIf, rolloutList)
if err != nil {
return err
}
}
return nil
},
}
o.AddKubectlFlags(cmd)
cmd.Flags().StringVar(&listOptions.name, "name", "", "Only show rollout with specified name")
cmd.Flags().BoolVar(&listOptions.allNamespaces, "all-namespaces", false, "Include all namespaces")
cmd.Flags().BoolVarP(&listOptions.watch, "watch", "w", false, "Watch for changes")
cmd.Flags().BoolVar(&listOptions.timestamps, "timestamps", false, "Print timestamps on updates")
return cmd
}

// ListOptions returns a metav1.ListOptions based on user supplied flags
func (o *ListOptions) ListOptions() metav1.ListOptions {
opts := metav1.ListOptions{}
if o.name != "" {
nameSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", o.name))
opts.FieldSelector = nameSelector.String()
}
return opts
}

// PrintRolloutTable prints rollouts in table format
func (o *ListOptions) PrintRolloutTable(roList *v1alpha1.RolloutList) error {
if len(roList.Items) == 0 && !o.watch {
fmt.Fprintln(o.ErrOut, "No resources found.")
return nil
}
w := tabwriter.NewWriter(o.Out, 0, 0, 2, ' ', 0)
headerStr := headerFmtString
if o.allNamespaces {
headerStr = "NAMESPACE\t" + headerStr
}
if o.timestamps {
headerStr = "TIMESTAMP\t" + headerStr
}
fmt.Fprintf(w, headerStr)
for _, ro := range roList.Items {
roLine := newRolloutInfo(ro)
fmt.Fprintln(w, roLine.String(o.timestamps, o.allNamespaces))
}
_ = w.Flush()
return nil
}

// PrintRolloutUpdates watches for changes to rollouts and prints the updates
func (o *ListOptions) PrintRolloutUpdates(ctx context.Context, rolloutIf argoprojv1alpha1.RolloutInterface, roList *v1alpha1.RolloutList) error {
w := tabwriter.NewWriter(o.Out, 0, 0, 2, ' ', 0)

opts := o.ListOptions()
opts.ResourceVersion = roList.ListMeta.ResourceVersion
watchIf, err := rolloutIf.Watch(opts)
if err != nil {
return err
}
// ticker is used to flush the tabwriter every few moments so that table is aligned when there
// are a flood of results in the watch channel
ticker := time.NewTicker(500 * time.Millisecond)

// prevLines remembers the most recent rollout lines we printed, so that we only print new lines
// when they have have changed in a meaningful way
prevLines := make(map[rolloutInfoKey]rolloutInfo)
for _, ro := range roList.Items {
roLine := newRolloutInfo(ro)
prevLines[roLine.key()] = roLine
}

var ro *v1alpha1.Rollout
retries := 0
L:
for {
select {
case next := <-watchIf.ResultChan():
ro, _ = next.Object.(*v1alpha1.Rollout)
case <-ticker.C:
_ = w.Flush()
continue
case <-ctx.Done():
break L
}
if ro == nil {
// if we get here, it means an error on the watch. try to re-establish the watch
watchIf.Stop()
newWatchIf, err := rolloutIf.Watch(opts)
if err != nil {
if retries > 5 {
return err
}
o.Log.Warn(err)
// this sleep prevents a hot-loop in the event there is a persistent error
time.Sleep(time.Second)
retries++
} else {
watchIf = newWatchIf
retries = 0
}
continue
}
opts.ResourceVersion = ro.ObjectMeta.ResourceVersion
roLine := newRolloutInfo(*ro)
if prevLine, ok := prevLines[roLine.key()]; !ok || prevLine != roLine {
fmt.Fprintln(w, roLine.String(o.timestamps, o.allNamespaces))
prevLines[roLine.key()] = roLine
}
}
watchIf.Stop()
return nil
}
Loading