-
Notifications
You must be signed in to change notification settings - Fork 50
/
jobs.go
150 lines (130 loc) · 4.31 KB
/
jobs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package jobs
import (
"context"
"sort"
"strings"
"time"
"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/helper"
"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/templates"
"github.com/jenkins-x/jx-helpers/v3/pkg/errorutil"
"github.com/jenkins-x/jx-helpers/v3/pkg/kube"
"github.com/jenkins-x/jx-logging/v3/pkg/log"
"github.com/pkg/errors"
"github.com/spf13/cobra"
batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// Options containers the CLI options
type Options struct {
DryRun bool
Selector string
Namespace string
Age time.Duration
Keep int
KubeClient kubernetes.Interface
}
var (
cmdLong = templates.LongDesc(`
Garbage collect old Jobs that have completed or failed
`)
cmdExample = templates.Examples(`
# garbage collect old jobs of the default age keeping 1
jx gitops gc jobs
# garbage collect jobs older than 10 minutes and keeping 10
jx gitops gc jobs -a 10m -k 10
# garbage collect jobs older than 10 (don't keep any job)
jx gitops gc jobs -a 10m -k 0
# dry run mode
jx gitops gc jobs --dry-run
`)
)
// NewCmdGCJods creates the command object
func NewCmdGCJobs() (*cobra.Command, *Options) {
o := &Options{}
cmd := &cobra.Command{
Use: "jobs",
Short: "garbage collection for jobs",
Aliases: []string{"job"},
Long: cmdLong,
Example: cmdExample,
Run: func(cmd *cobra.Command, args []string) {
err := o.Run()
helper.CheckErr(err)
},
}
cmd.Flags().BoolVarP(&o.DryRun, "dry-run", "d", false, "Dry run mode. If enabled just list the jobs that would be removed")
cmd.Flags().StringVarP(&o.Selector, "selector", "s", "", "The selector to use to filter the jobs")
cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", "", "The namespace to look for the jobs. Defaults to the current namespace")
cmd.Flags().DurationVarP(&o.Age, "age", "a", time.Hour, "The minimum age of jobs to garbage collect. Any newer jobs will be kept")
cmd.Flags().IntVarP(&o.Keep, "keep", "k", 1, "The minimum jobs to keep. Jobs to keep even if they are older than the age parameter")
return cmd, o
}
// Run implements this command
func (o *Options) Run() error {
var err error
o.KubeClient, o.Namespace, err = kube.LazyCreateKubeClientAndNamespace(o.KubeClient, o.Namespace)
if err != nil {
return errors.Wrapf(err, "failed to create kube client")
}
kubeClient := o.KubeClient
ns := o.Namespace
ctx := context.TODO()
opts := metav1.ListOptions{
LabelSelector: o.Selector,
}
jobInterface := kubeClient.BatchV1().Jobs(o.Namespace)
jobList, err := jobInterface.List(ctx, opts)
if err != nil {
return err
}
deleteOptions := metav1.DeleteOptions{}
errors := []error{}
// we need to keep all jobs, don't waste time sorting or iterating
if o.Keep >= len(jobList.Items) {
log.Logger().Infof("Not deleting any job. You want to keep %d jobs and we have %d jobs in the list.", o.Keep, len(jobList.Items))
return errorutil.CombineErrors(errors...)
}
// if you don't want keep any job don't waste time sorting
if o.Keep != 0 {
// sort list by creationTimestamp
sort.Slice(jobList.Items, func(i, j int) bool {
return jobList.Items[j].Status.StartTime.Before(jobList.Items[i].Status.StartTime)
})
// shrink jobList by keep
jobList.Items = jobList.Items[o.Keep:]
}
for k := range jobList.Items {
job := jobList.Items[k]
matches, age := o.matchesJob(&job)
ageText := strings.TrimSuffix(age.Round(time.Minute).String(), "0s")
if matches {
if !o.DryRun {
err := jobInterface.Delete(ctx, job.Name, deleteOptions)
if err != nil {
log.Logger().Warnf("Failed to delete job %s in namespace %s: %s", job.Name, ns, err.Error())
errors = append(errors, err)
} else {
log.Logger().Infof("Deleted job %s in namespace %s as its age is: %s", job.Name, ns, ageText)
}
} else {
log.Logger().Infof("Not deleting job %s in namespace %s. It's age is: %s", job.Name, ns, ageText)
}
}
}
return errorutil.CombineErrors(errors...)
}
// matchesJob returns true if this job can be garbage collected
func (o *Options) matchesJob(job *batchv1.Job) (bool, time.Duration) {
now := time.Now()
var age time.Duration
if job.Status.StartTime != nil {
age = now.Sub(job.Status.StartTime.Time)
} else {
return false, age
}
if job.Status.Active > 0 {
return false, age
}
return age > o.Age, age
}