Skip to content

Commit

Permalink
Reduced the number of prints and updated how flags are handled
Browse files Browse the repository at this point in the history
  • Loading branch information
EraYaN committed Jun 26, 2023
1 parent e45072b commit 1f5875f
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 174 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ For jobs it wait until the `Completed` condition is true.

For services it will wait until all pods that match the service selector are Ready and Available (like above).

## Example

```
$ kube-wait-for-multi default,job,some-job default,service,some-service default,pod,some-pod-88bb5f7bb-wx4f7
```
Wait for the job `some-job` to complete, the service `some-service` to have all available pods and the pod `some-pod-88bb5f7bb-wx4f7` to be ready and available.

The program will also wait when a service does not exist yet.
```
$ kube-wait-for-multi default,job,some-job default,job,test default,service,service1 default,service,service2
Starting with namespaces: [default]
Starting informers...
wait status
└── [❔] namespace/default
├── [✅] service/service1: Available
├── [❔] service/service2: Unavailable
├── [✅] job/some-job: Complete
└── [❌] job/test: NotComplete
[... some time later ...]
wait status
└── [✅] namespace/default
```

## Docker

Hosted on Docker Hub: https://hub.docker.com/r/erayan/k8s-wait-for-multi
Expand Down
14 changes: 6 additions & 8 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ package cmd

import (
"os"
"time"

"github.com/erayan/k8s-wait-for-multi/flags"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
Expand All @@ -30,6 +31,7 @@ var (
KubeResourceBuilderFlags *genericclioptions.ResourceBuilderFlags
KubernetesPrintFlags *genericclioptions.PrintFlags
KubernetesGetPrintFlags *kubectlget.PrintFlags
WaitForConfigFlags *flags.ConfigFlags
)

// rootCmd represents the base command when called without any subcommands
Expand All @@ -40,7 +42,7 @@ var rootCmd = &cobra.Command{
This is an implementation of k8s-wait-for that allows you to wait for multiple items in one process.
This uses informers to get the status updates for all the items that this application is waiting for.
You can omit the NAMESPACE and KIND, they default to the value of the --namespace flag and pod respectively. Supported string for KIND are service, job and pod.`,
You can omit the NAMESPACE and KIND, they default to the value of the --namespace flag and 'pod' respectively. Supported strings for KIND are service, job and pod.`,
RunE: wait,
Version: version,
}
Expand All @@ -57,12 +59,8 @@ func init() {

KubernetesConfigFlags.AddFlags(rootCmd.PersistentFlags())

rootCmd.Flags().BoolP("version", "v", false, "Display version info")

rootCmd.Flags().Bool("no-collapse", false, "Do not collapse the status tree for done subtrees")

rootCmd.Flags().Bool("no-tree", false, "Do not print the status as a tree")
WaitForConfigFlags = flags.NewConfigFlags()

rootCmd.PersistentFlags().DurationP("timeout", "t", time.Duration(600*time.Second), "The length of time to wait before ending watch, zero means never. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h). Default is 10 minutes")
WaitForConfigFlags.AddFlags(rootCmd.Flags())

}
62 changes: 18 additions & 44 deletions cmd/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,10 @@ var mu sync.Mutex
var waits *pkg.Waitables

func wait(cmd *cobra.Command, args []string) error {
isVersion, err := cmd.Flags().GetBool("version")

if err != nil {
return err
}

if isVersion {
if *WaitForConfigFlags.PrintVersion {
return printVersion(cmd, args)
}

noCollapseTree, err := cmd.Flags().GetBool("no-collapse")

if err != nil {
return err
}

noTree, err := cmd.Flags().GetBool("no-tree")

if err != nil {
return err
}

if len(args) < 1 {
return errors.New("command needs one or more arguments to wait for")
}
Expand All @@ -74,7 +56,9 @@ func wait(cmd *cobra.Command, args []string) error {
KubernetesConfigFlags.Namespace = pointer.String("default")
}

waits = pkg.NewWaitables()
waits = pkg.NewWaitables(WaitForConfigFlags)

waits.Start()

timeout, err := cmd.Flags().GetDuration("timeout")

Expand Down Expand Up @@ -155,11 +139,7 @@ func wait(cmd *cobra.Command, args []string) error {
return errors.New("not enough arguments")
}

if noTree {
log.Println(waits.GetStatusString())
} else {
log.Println(waits.GetStatusTreeString(noCollapseTree))
}
waits.PrintStatus()

if waits.HasServices() {
svc_informer, err := cc.GetInformerForKind(timeoutCtx, schema.FromAPIVersionAndKind("v1", "Service"))
Expand All @@ -169,13 +149,13 @@ func wait(cmd *cobra.Command, args []string) error {

svc_informer.AddEventHandler(toolscache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventAddService, obj.(*corev1.Service), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventAddService, obj.(*corev1.Service))
},
UpdateFunc: func(obj interface{}, newObj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventUpdateService, newObj.(*corev1.Service), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventUpdateService, newObj.(*corev1.Service))
},
DeleteFunc: func(obj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventDeleteService, obj.(*corev1.Service), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventDeleteService, obj.(*corev1.Service))
},
})
}
Expand All @@ -188,13 +168,13 @@ func wait(cmd *cobra.Command, args []string) error {

pod_informer.AddEventHandler(toolscache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventAddPod, obj.(*corev1.Pod), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventAddPod, obj.(*corev1.Pod))
},
UpdateFunc: func(obj interface{}, newObj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventUpdatePod, newObj.(*corev1.Pod), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventUpdatePod, newObj.(*corev1.Pod))
},
DeleteFunc: func(obj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventDeletePod, obj.(*corev1.Pod), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventDeletePod, obj.(*corev1.Pod))
},
})
}
Expand All @@ -207,34 +187,32 @@ func wait(cmd *cobra.Command, args []string) error {

job_informer.AddEventHandler(toolscache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventAddJob, obj.(*batchv1.Job), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventAddJob, obj.(*batchv1.Job))
},
UpdateFunc: func(obj interface{}, newObj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventUpdateJob, newObj.(*batchv1.Job), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventUpdateJob, newObj.(*batchv1.Job))
},
DeleteFunc: func(obj interface{}) {
handleEvent(timeoutCtx, waits.ProcessEventDeleteJob, obj.(*batchv1.Job), !noCollapseTree, noTree)
handleEvent(timeoutCtx, waits.ProcessEventDeleteJob, obj.(*batchv1.Job))
},
})
}

log.Printf("Starting informers...")

err = cc.Start(timeoutCtx)
if err != nil {
return err
}
log.Printf("Shutdown informers.")

waits.Done()

return nil
}

func processCompletion() {
log.Printf("All items have completed or are ready")
cancelFn()
}

func handleEvent[V *corev1.Pod | *corev1.Service | *batchv1.Job](ctx context.Context, f func(ctx context.Context, obj V) (bool, error), obj V, collapseTree bool, noTree bool) {
func handleEvent[V *corev1.Pod | *corev1.Service | *batchv1.Job](ctx context.Context, f func(ctx context.Context, obj V) (bool, error), obj V) {
mu.Lock()
defer mu.Unlock()

Expand All @@ -244,11 +222,7 @@ func handleEvent[V *corev1.Pod | *corev1.Service | *batchv1.Job](ctx context.Con
}

if matches {
if noTree {
log.Println(waits.GetStatusString())
} else {
log.Println(waits.GetStatusTreeString(collapseTree))
}
waits.PrintStatus()

if waits.IsDone() {
processCompletion()
Expand Down
61 changes: 61 additions & 0 deletions flags/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2023 The k8s-wait-for-multi 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 flags

import (
"time"

"github.com/spf13/pflag"

utilpointer "k8s.io/utils/pointer"
)

type ConfigFlags struct {
PrintVersion *bool
PrintTree *bool
PrintCollapsedTree *bool

Timeout *time.Duration
}

func NewConfigFlags() *ConfigFlags {
return &ConfigFlags{
PrintVersion: utilpointer.Bool(false),
PrintTree: utilpointer.Bool(true),
PrintCollapsedTree: utilpointer.Bool(true),

Timeout: utilpointer.Duration(time.Duration(600 * time.Second)),
}
}

func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {
if f.Timeout != nil {
flags.DurationVarP(f.Timeout, "timeout", "t", *f.Timeout, "The length of time to wait before ending watch, zero means never. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h)")
}

if f.PrintVersion != nil {
flags.BoolVarP(f.PrintVersion, "version", "v", *f.PrintVersion, "Display version info")
}

if f.PrintTree != nil {
flags.BoolVar(f.PrintTree, "print-tree", *f.PrintTree, "Print the status as a tree")
}

if f.PrintCollapsedTree != nil {
flags.BoolVar(f.PrintCollapsedTree, "print-collapsed-tree", *f.PrintCollapsedTree, "Collapse the status tree for done subtrees")
}
}
94 changes: 0 additions & 94 deletions kube/client.go

This file was deleted.

23 changes: 23 additions & 0 deletions pkg/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,26 @@ func metaInSlice(a treeprint.MetaValue, list []treeprint.MetaValue) bool {
}
return false
}

func getNodesStatus(item *treeprint.Node, collapseTree bool) treeprint.MetaValue {
if len(item.Nodes) > 0 && item.Meta == TreeStatusUnknown {
status := []treeprint.MetaValue{}

for _, node := range item.Nodes {
status = append(status, getNodesStatus(node, collapseTree))
}
if metaInSlice(TreeStatusUnknown, status) {
item.Meta = TreeStatusUnknown
} else if metaInSlice(TreeStatusNotDone, status) {
item.Meta = TreeStatusNotDone
} else {
if collapseTree {
item.Nodes = nil
}
item.Meta = TreeStatusDone
}
// branch nodes
}

return item.Meta
}
Loading

0 comments on commit 1f5875f

Please sign in to comment.