From cfdf8b47909e5339c66286853cd458554c3a73b2 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 2 Nov 2019 18:42:30 +0100 Subject: [PATCH 01/58] Add initial Go implementation --- cmd/list.go | 81 ++++++++++++++++++++++++ cmd/logs.go | 91 +++++++++++++++++++++++++++ cmd/remove.go | 39 ++++++++++++ cmd/root.go | 42 +++++++++++++ cmd/run.go | 77 +++++++++++++++++++++++ go.mod | 11 ++++ go.sum | 129 ++++++++++++++++++++++++++++++++++++++ main.go | 13 ++++ pkg/kube/client/client.go | 54 ++++++++++++++++ pkg/kube/retry/retry.go | 97 ++++++++++++++++++++++++++++ pkg/types/jobspec.go | 55 ++++++++++++++++ pkg/types/stringarray.go | 33 ++++++++++ 12 files changed, 722 insertions(+) create mode 100644 cmd/list.go create mode 100644 cmd/logs.go create mode 100644 cmd/remove.go create mode 100644 cmd/root.go create mode 100644 cmd/run.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/kube/client/client.go create mode 100644 pkg/kube/retry/retry.go create mode 100644 pkg/types/jobspec.go create mode 100644 pkg/types/stringarray.go diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..30308a1 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,81 @@ +package cmd + +import ( + "fmt" + "os" + "strconv" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" + "github.com/uitml/frink/pkg/kube/client" + batchv1 "k8s.io/api/batch/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + headerNames = []string{ + "NAME", + "SUCCEEDED", + } +) + +// Flags +var ( + showAll bool +) + +var listCmd = &cobra.Command{ + Use: "ls", + Short: "List jobs", + RunE: func(cmd *cobra.Command, args []string) error { + clientset, namespace, err := client.ForContext("") + if err != nil { + return fmt.Errorf("could not get k8s client: %v", err) + } + + jobs, err := clientset.BatchV1().Jobs(namespace).List(metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("could not list jobs: %v", err) + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + defer w.Flush() + + fmt.Fprintln(w, header()) + for _, job := range jobs.Items { + fmt.Fprintln(w, row(job)) + } + + return nil + }, +} + +func init() { + listCmd.Flags().BoolVarP(&showAll, "all", "a", false, "show all jobs (defaults to only active)") +} + +func header() string { + return strings.Join(headerNames, "\t") + "\t" // TODO: Check if the trailing tab is required +} + +func underlinedHeader() string { + var rules []string + for _, name := range headerNames { + rules = append(rules, strings.Repeat("-", len(name))) + } + + head := header() + rule := strings.Join(rules, "\t") + + return fmt.Sprintf("%s\n%s\t", head, rule) +} + +func row(job batchv1.Job) string { + data := []string{ + job.Name, + strconv.FormatInt(int64(job.Status.Succeeded), 10), + } + + return strings.Join(data, "\t") + "\t" // TODO: Check if the trailing tab is required +} diff --git a/cmd/logs.go b/cmd/logs.go new file mode 100644 index 0000000..db3909a --- /dev/null +++ b/cmd/logs.go @@ -0,0 +1,91 @@ +package cmd + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "github.com/uitml/frink/pkg/kube/client" + "github.com/uitml/frink/pkg/types" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +var logsCmd = &cobra.Command{ + Use: "logs [name]", + Short: "Fetch the logs of a job", + Aliases: []string{"watch"}, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job name must be specified") + } + + name := args[0] + + clientset, namespace, err := client.ForContext("") + if err != nil { + return fmt.Errorf("unable to get kube client: %v", err) + } + + getOptions := metav1.GetOptions{} + job, err := clientset.BatchV1().Jobs(namespace).Get(name, getOptions) + if err != nil { + return fmt.Errorf("unable to get job: %v", err) + } + + listOptions := metav1.ListOptions{ + LabelSelector: labels.Set(job.Spec.Selector.MatchLabels).String(), + } + pods, err := clientset.CoreV1().Pods(namespace).List(listOptions) + if err != nil { + return fmt.Errorf("unable to get pods for job: %v", err) + } + + if len(pods.Items) == 0 { + return nil + } + + // TODO: Add support for multiple pods? + pod := pods.Items[0] + + logOptions := &corev1.PodLogOptions{ + // TODO: Make these configurable via flags? + TailLines: types.Int64Ptr(20), + Follow: true, + // TODO: Figure out how to support both current and previous somehow (if necessary). + // Previous: true, + } + req := clientset.CoreV1().Pods(namespace).GetLogs(pod.Name, logOptions) + if err != nil { + return fmt.Errorf("unable to get logs: %v", err) + } + + logs, err := req.Stream() + if err != nil { + return fmt.Errorf("unable to stream logs: %v", err) + } + defer logs.Close() + + r := bufio.NewReader(logs) + for { + p, err := r.ReadBytes('\n') + if _, err := os.Stdout.Write(p); err != nil { + return fmt.Errorf("unable to write output: %v", err) + } + + if err != nil { + if errors.Is(err, io.EOF) { + break + } + + return fmt.Errorf("unable to read stream: %v", err) + } + } + + return nil + }, +} diff --git a/cmd/remove.go b/cmd/remove.go new file mode 100644 index 0000000..ccd16eb --- /dev/null +++ b/cmd/remove.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/uitml/frink/pkg/kube/client" + "github.com/uitml/frink/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var removeCmd = &cobra.Command{ + Use: "rm [name]", + Short: "Remove a job", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job name must be specified") + } + + name := args[0] + + clientset, namespace, err := client.ForContext("") + if err != nil { + return fmt.Errorf("unable to get kube client: %v", err) + } + + deletePolicy := metav1.DeletePropagationBackground + deleteOptions := &metav1.DeleteOptions{ + GracePeriodSeconds: types.Int64Ptr(0), + PropagationPolicy: &deletePolicy, + } + err = clientset.BatchV1().Jobs(namespace).Delete(name, deleteOptions) + if err != nil { + return fmt.Errorf("unable to delete job: %v", err) + } + + return nil + }, +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..2c18679 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "frink", + Short: "Simplifies your Springfield workflows", + + // Silence usage when an error occurs. + SilenceUsage: true, +} + +// Execute executes the root command. +func Execute() error { + return rootCmd.Execute() +} + +func init() { + rootCmd.AddCommand(listCmd) + rootCmd.AddCommand(logsCmd) + rootCmd.AddCommand(removeCmd) + rootCmd.AddCommand(runCmd) + + disableFlagsInUseLine(rootCmd) +} + +// visitAll visits the entire command tree rooted at cmd, invoking fn on each command. +func visitAll(cmd *cobra.Command, fn func(*cobra.Command)) { + fn(cmd) + for _, child := range cmd.Commands() { + visitAll(child, fn) + } +} + +// disableFlagsInUseLine sets the disableFlagsInUseLine flag on the entire command tree rooted at cmd. +func disableFlagsInUseLine(cmd *cobra.Command) { + visitAll(cmd, func(c *cobra.Command) { + c.DisableFlagsInUseLine = true + }) +} diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000..bfcb16e --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/spf13/cobra" + "github.com/uitml/frink/pkg/kube/client" + "github.com/uitml/frink/pkg/kube/retry" + "github.com/uitml/frink/pkg/types" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var runCmd = &cobra.Command{ + Use: "run [file]", + Short: "Schedule a job", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job specification file must be specified") + } + + file := args[0] + if _, err := os.Stat(file); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("specified file does not exist: %v", file) + } + + return fmt.Errorf("unable to access file: %v", err) + } + + data, err := ioutil.ReadFile(file) + if err != nil { + return fmt.Errorf("unable to read file: %v", err) + } + + // TODO: Implement support for "normal" k8s job specs. Determine based on presence of 'kind' and/or 'apiVersion' keys? + spec := types.SimpleJobSpec{} + if err := yaml.Unmarshal(data, &spec); err != nil { + return fmt.Errorf("unable to parse file: %v", err) + } + job := spec.Expand() + + clientset, namespace, err := client.ForContext("") + if err != nil { + return fmt.Errorf("unable to get kube client: %v", err) + } + + jobClient := clientset.BatchV1().Jobs(namespace) + + // Delete existing job with same name. + deletePolicy := metav1.DeletePropagationBackground + deleteOptions := &metav1.DeleteOptions{ + GracePeriodSeconds: types.Int64Ptr(0), + PropagationPolicy: &deletePolicy, + } + err = jobClient.Delete(spec.Name, deleteOptions) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("unable to previous job: %v", err) + } + + // Try to create the job using retry with backoff. + err = retry.RetryOnExists(retry.DefaultBackoff, func() error { + _, err = jobClient.Create(job) + return err + }) + if err != nil { + return fmt.Errorf("unable to create job: %v", err) + } + + // TODO: Implement support for streaming job/pod log to stdout. + + return nil + }, +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c38a18c --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/uitml/frink + +go 1.13 + +require ( + github.com/spf13/cobra v0.0.5 + gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 + k8s.io/api v0.0.0-20191016110246-af539daaa43a + k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 + k8s.io/client-go v0.0.0-20191016110837-54936ba21026 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..239c0a8 --- /dev/null +++ b/go.sum @@ -0,0 +1,129 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g= +golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 h1:VKvJ/mQ4BgCjZUDggYFxTe0qv9jPMHsZPD4Xt91Y5H4= +gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.0.0-20191016110246-af539daaa43a h1:IocS6+jQEuO8ZGQXhrD9BZ7Ze+Ly6FUKPlYs/m4I6xo= +k8s.io/api v0.0.0-20191016110246-af539daaa43a/go.mod h1:ceHJE/vDjU8jKnRV6Vqn/+vyZmC6NvOluInN+RhQkIs= +k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 h1:GYWOVyO+ZU+YK01nyPiAwB/fQrkxysXwkjbSpIIHdN4= +k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762/go.mod h1:Xc10RHc1U+F/e9GCloJ8QAeCGevSVP5xhOhqlE+e1kM= +k8s.io/client-go v0.0.0-20191016110837-54936ba21026 h1:HEJL/LGwm+NIepJIQML4AeU/BTn9vhDNnIE3qzpKL7g= +k8s.io/client-go v0.0.0-20191016110837-54936ba21026/go.mod h1:OjMHP19NDb8WkZ+pxaQjCDVs1IRkcpFn97Snv6K5w6A= +k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= +k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f2d4195 --- /dev/null +++ b/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" + + "github.com/uitml/frink/cmd" +) + +func main() { + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/pkg/kube/client/client.go b/pkg/kube/client/client.go new file mode 100644 index 0000000..22bb46a --- /dev/null +++ b/pkg/kube/client/client.go @@ -0,0 +1,54 @@ +package client + +import ( + "fmt" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// ForContext returns a client and namespace for the specified context. +func ForContext(context string) (*kubernetes.Clientset, string, error) { + config, namespace, err := buildClientConfig(context) + if err != nil { + return nil, "", err + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, "", err + } + + return client, namespace, nil +} + +// buildClientConfig returns a complete client config and the namespace for the given context. +func buildClientConfig(context string) (*rest.Config, string, error) { + clientConfig := buildClientCmd(context) + config, err := clientConfig.ClientConfig() + if err != nil { + return nil, "", fmt.Errorf("could not get k8s config for context %q: %s", context, err) + } + + namespace, _, err := clientConfig.Namespace() + if err != nil { + return nil, "", fmt.Errorf("could not get namespace for context %q: %s", context, err) + } + + return config, namespace, nil +} + +// buildClientCmd returns an (incomplete) API server client config. +func buildClientCmd(context string) clientcmd.ClientConfig { + rules := clientcmd.NewDefaultClientConfigLoadingRules() + rules.DefaultClientConfig = &clientcmd.DefaultClientConfig + + overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} + + if context != "" { + overrides.CurrentContext = context + } + + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides) +} diff --git a/pkg/kube/retry/retry.go b/pkg/kube/retry/retry.go new file mode 100644 index 0000000..d8bce35 --- /dev/null +++ b/pkg/kube/retry/retry.go @@ -0,0 +1,97 @@ +package retry + +// TODO: Remove this module once we upgrade to client-go v16. + +import ( + "time" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/wait" +) + +// DefaultRetry is the recommended retry for a conflict where multiple clients +// are making changes to the same resource. +var DefaultRetry = wait.Backoff{ + Steps: 5, + Duration: 10 * time.Millisecond, + Factor: 1.0, + Jitter: 0.1, +} + +// DefaultBackoff is the recommended backoff for a conflict where a client +// may be attempting to make an unrelated modification to a resource under +// active management by one or more controllers. +var DefaultBackoff = wait.Backoff{ + Steps: 4, + Duration: 10 * time.Millisecond, + Factor: 5.0, + Jitter: 0.1, +} + +// OnError allows the caller to retry fn in case the error returned by fn is retriable +// according to the provided function. backoff defines the maximum retries and the wait +// interval between two retries. +func OnError(backoff wait.Backoff, retriable func(error) bool, fn func() error) error { + var lastErr error + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + err := fn() + switch { + case err == nil: + return true, nil + case retriable(err): + lastErr = err + return false, nil + default: + return false, err + } + }) + if err == wait.ErrWaitTimeout { + err = lastErr + } + return err +} + +// RetryOnConflict is used to make an update to a resource when you have to worry about +// conflicts caused by other code making unrelated updates to the resource at the same +// time. fn should fetch the resource to be modified, make appropriate changes to it, try +// to update it, and return (unmodified) the error from the update function. On a +// successful update, RetryOnConflict will return nil. If the update function returns a +// "Conflict" error, RetryOnConflict will wait some amount of time as described by +// backoff, and then try again. On a non-"Conflict" error, or if it retries too many times +// and gives up, RetryOnConflict will return an error to the caller. +// +// err := retry.RetryOnConflict(retry.DefaultRetry, func() error { +// // Fetch the resource here; you need to refetch it on every try, since +// // if you got a conflict on the last update attempt then you need to get +// // the current version before making your own changes. +// pod, err := c.Pods("mynamespace").Get(name, metav1.GetOptions{}) +// if err ! nil { +// return err +// } +// +// // Make whatever updates to the resource are needed +// pod.Status.Phase = v1.PodFailed +// +// // Try to update +// _, err = c.Pods("mynamespace").UpdateStatus(pod) +// // You have to return err itself here (not wrapped inside another error) +// // so that RetryOnConflict can identify it correctly. +// return err +// }) +// if err != nil { +// // May be conflict if max retries were hit, or may be something unrelated +// // like permissions or a network error +// return err +// } +// ... +// +// TODO: Make Backoff an interface? +func RetryOnConflict(backoff wait.Backoff, fn func() error) error { + return OnError(backoff, errors.IsConflict, fn) +} + +// RetryOnExists can be used to recreate a resource immediately after the previous copy +// has been deleted, and thus might still be in a state of being removed. +func RetryOnExists(backoff wait.Backoff, fn func() error) error { + return OnError(backoff, errors.IsAlreadyExists, fn) +} diff --git a/pkg/types/jobspec.go b/pkg/types/jobspec.go new file mode 100644 index 0000000..b5b63d6 --- /dev/null +++ b/pkg/types/jobspec.go @@ -0,0 +1,55 @@ +package types + +import ( + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SimpleJobSpec represents an extremely simplified k8s job specification. +type SimpleJobSpec struct { + Name string `yaml:"name"` + Image string `yaml:"image"` + Command StringArray `yaml:"command"` + GPU uint `yaml:"gpu"` +} + +// Expand expands the simplified job spec into a full job object. +func (spec *SimpleJobSpec) Expand() *batchv1.Job { + return &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: spec.Name, + }, + Spec: batchv1.JobSpec{ + BackoffLimit: Int32Ptr(0), + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyOnFailure, + Containers: []corev1.Container{ + { + Name: spec.Name, + Image: spec.Image, + Command: spec.Command, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "storage", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "storage", + }, + }, + }, + }, + }, + }, + }, + } +} + +// Int32Ptr returns a pointer to the specified int32 value. +func Int32Ptr(i int32) *int32 { return &i } + +// Int64Ptr returns a pointer to the specified int64 value. +func Int64Ptr(i int64) *int64 { return &i } diff --git a/pkg/types/stringarray.go b/pkg/types/stringarray.go new file mode 100644 index 0000000..bacb13e --- /dev/null +++ b/pkg/types/stringarray.go @@ -0,0 +1,33 @@ +package types + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +// StringArray is a proxy type to support umarshalling YAML nodes that are scalars or sequences of type string. +type StringArray []string + +// UnmarshalYAML implements behavior to handle nodes that are scalars or sequences of type string. +// TODO: Refactor this; don't like the way it looks and "feels"... +func (array *StringArray) UnmarshalYAML(node *yaml.Node) error { + switch node.Kind { + case yaml.SequenceNode: + var sequence []string + if err := node.Decode(&sequence); err != nil { + return err + } + *array = sequence + case yaml.ScalarNode: + var scalar string + if err := node.Decode(&scalar); err != nil { + return err + } + *array = []string{scalar} + default: + return fmt.Errorf("kind must be scalar or sequence") + } + + return nil +} From 790668e6489e46686d0dcf02d01f4ddd42f7f920 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 3 Nov 2019 23:17:29 +0100 Subject: [PATCH 02/58] Introduce new top-level `job` command group Make all job related commands, previously top-level, subcommands of the new command group. This is intended to make the CLI more organized. The downside of this is that it might become too "convoluted". Although it should be more user friendly (in some sense) to introduce new non-job related functionality in the future by introducing new command groups. --- cmd/job.go | 15 +++++++++++++++ cmd/root.go | 5 +---- 2 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 cmd/job.go diff --git a/cmd/job.go b/cmd/job.go new file mode 100644 index 0000000..5590fe4 --- /dev/null +++ b/cmd/job.go @@ -0,0 +1,15 @@ +package cmd + +import "github.com/spf13/cobra" + +var jobCmd = &cobra.Command{ + Use: "job", + Short: "Manage scheduled jobs", +} + +func init() { + jobCmd.AddCommand(listCmd) + jobCmd.AddCommand(logsCmd) + jobCmd.AddCommand(removeCmd) + jobCmd.AddCommand(runCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 2c18679..d601200 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,10 +18,7 @@ func Execute() error { } func init() { - rootCmd.AddCommand(listCmd) - rootCmd.AddCommand(logsCmd) - rootCmd.AddCommand(removeCmd) - rootCmd.AddCommand(runCmd) + rootCmd.AddCommand(jobCmd) disableFlagsInUseLine(rootCmd) } From 79ffc9b0a51648dcd10b3e5233ac8210a4705ea4 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 3 Nov 2019 23:26:43 +0100 Subject: [PATCH 03/58] Prefix job command filenames --- cmd/{list.go => job_list.go} | 0 cmd/{logs.go => job_logs.go} | 0 cmd/{remove.go => job_remove.go} | 0 cmd/{run.go => job_run.go} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename cmd/{list.go => job_list.go} (100%) rename cmd/{logs.go => job_logs.go} (100%) rename cmd/{remove.go => job_remove.go} (100%) rename cmd/{run.go => job_run.go} (100%) diff --git a/cmd/list.go b/cmd/job_list.go similarity index 100% rename from cmd/list.go rename to cmd/job_list.go diff --git a/cmd/logs.go b/cmd/job_logs.go similarity index 100% rename from cmd/logs.go rename to cmd/job_logs.go diff --git a/cmd/remove.go b/cmd/job_remove.go similarity index 100% rename from cmd/remove.go rename to cmd/job_remove.go diff --git a/cmd/run.go b/cmd/job_run.go similarity index 100% rename from cmd/run.go rename to cmd/job_run.go From 0ffc623196ea6ecf40f5de2fb2c1b801b2561a47 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 3 Nov 2019 23:29:56 +0100 Subject: [PATCH 04/58] Ignore build output directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1d74e21..a395e03 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vscode/ +/out/ From 6dd41ddced40f2bf660cd55a1180bfb84f1c5087 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 4 Nov 2019 14:01:36 +0100 Subject: [PATCH 05/58] Move implementation details away from commands --- cmd/job.go | 3 + cmd/job_list.go | 7 +- cmd/job_logs.go | 38 +------ cmd/job_remove.go | 13 +-- cmd/job_run.go | 25 ++--- {pkg/kube/client => internal/k8s}/client.go | 19 ++-- internal/k8s/jobs.go | 111 ++++++++++++++++++++ {pkg/kube/retry => internal/k8s}/retry.go | 2 +- {pkg/types => internal/k8s}/stringarray.go | 2 +- pkg/types/jobspec.go | 55 ---------- 10 files changed, 144 insertions(+), 131 deletions(-) rename {pkg/kube/client => internal/k8s}/client.go (80%) create mode 100644 internal/k8s/jobs.go rename {pkg/kube/retry => internal/k8s}/retry.go (99%) rename {pkg/types => internal/k8s}/stringarray.go (98%) delete mode 100644 pkg/types/jobspec.go diff --git a/cmd/job.go b/cmd/job.go index 5590fe4..1798354 100644 --- a/cmd/job.go +++ b/cmd/job.go @@ -13,3 +13,6 @@ func init() { jobCmd.AddCommand(removeCmd) jobCmd.AddCommand(runCmd) } + +func int32Ptr(i int32) *int32 { return &i } +func int64Ptr(i int64) *int64 { return &i } diff --git a/cmd/job_list.go b/cmd/job_list.go index 30308a1..9eab42f 100644 --- a/cmd/job_list.go +++ b/cmd/job_list.go @@ -8,9 +8,8 @@ import ( "text/tabwriter" "github.com/spf13/cobra" - "github.com/uitml/frink/pkg/kube/client" + "github.com/uitml/frink/internal/k8s" batchv1 "k8s.io/api/batch/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ( @@ -29,12 +28,12 @@ var listCmd = &cobra.Command{ Use: "ls", Short: "List jobs", RunE: func(cmd *cobra.Command, args []string) error { - clientset, namespace, err := client.ForContext("") + kubectx, err := k8s.Client("") if err != nil { return fmt.Errorf("could not get k8s client: %v", err) } - jobs, err := clientset.BatchV1().Jobs(namespace).List(metav1.ListOptions{}) + jobs, err := kubectx.ListJobs() if err != nil { return fmt.Errorf("could not list jobs: %v", err) } diff --git a/cmd/job_logs.go b/cmd/job_logs.go index db3909a..faa2eba 100644 --- a/cmd/job_logs.go +++ b/cmd/job_logs.go @@ -8,11 +8,7 @@ import ( "os" "github.com/spf13/cobra" - "github.com/uitml/frink/pkg/kube/client" - "github.com/uitml/frink/pkg/types" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" + "github.com/uitml/frink/internal/k8s" ) var logsCmd = &cobra.Command{ @@ -26,40 +22,12 @@ var logsCmd = &cobra.Command{ name := args[0] - clientset, namespace, err := client.ForContext("") + kubectx, err := k8s.Client("") if err != nil { return fmt.Errorf("unable to get kube client: %v", err) } - getOptions := metav1.GetOptions{} - job, err := clientset.BatchV1().Jobs(namespace).Get(name, getOptions) - if err != nil { - return fmt.Errorf("unable to get job: %v", err) - } - - listOptions := metav1.ListOptions{ - LabelSelector: labels.Set(job.Spec.Selector.MatchLabels).String(), - } - pods, err := clientset.CoreV1().Pods(namespace).List(listOptions) - if err != nil { - return fmt.Errorf("unable to get pods for job: %v", err) - } - - if len(pods.Items) == 0 { - return nil - } - - // TODO: Add support for multiple pods? - pod := pods.Items[0] - - logOptions := &corev1.PodLogOptions{ - // TODO: Make these configurable via flags? - TailLines: types.Int64Ptr(20), - Follow: true, - // TODO: Figure out how to support both current and previous somehow (if necessary). - // Previous: true, - } - req := clientset.CoreV1().Pods(namespace).GetLogs(pod.Name, logOptions) + req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) if err != nil { return fmt.Errorf("unable to get logs: %v", err) } diff --git a/cmd/job_remove.go b/cmd/job_remove.go index ccd16eb..956a458 100644 --- a/cmd/job_remove.go +++ b/cmd/job_remove.go @@ -4,9 +4,7 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/uitml/frink/pkg/kube/client" - "github.com/uitml/frink/pkg/types" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/uitml/frink/internal/k8s" ) var removeCmd = &cobra.Command{ @@ -19,17 +17,12 @@ var removeCmd = &cobra.Command{ name := args[0] - clientset, namespace, err := client.ForContext("") + kubectx, err := k8s.Client("") if err != nil { return fmt.Errorf("unable to get kube client: %v", err) } - deletePolicy := metav1.DeletePropagationBackground - deleteOptions := &metav1.DeleteOptions{ - GracePeriodSeconds: types.Int64Ptr(0), - PropagationPolicy: &deletePolicy, - } - err = clientset.BatchV1().Jobs(namespace).Delete(name, deleteOptions) + err = kubectx.DeleteJob(name) if err != nil { return fmt.Errorf("unable to delete job: %v", err) } diff --git a/cmd/job_run.go b/cmd/job_run.go index bfcb16e..d54487b 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -6,12 +6,9 @@ import ( "os" "github.com/spf13/cobra" - "github.com/uitml/frink/pkg/kube/client" - "github.com/uitml/frink/pkg/kube/retry" - "github.com/uitml/frink/pkg/types" + "github.com/uitml/frink/internal/k8s" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var runCmd = &cobra.Command{ @@ -37,35 +34,25 @@ var runCmd = &cobra.Command{ } // TODO: Implement support for "normal" k8s job specs. Determine based on presence of 'kind' and/or 'apiVersion' keys? - spec := types.SimpleJobSpec{} + spec := k8s.SimpleJobSpec{} if err := yaml.Unmarshal(data, &spec); err != nil { return fmt.Errorf("unable to parse file: %v", err) } job := spec.Expand() - clientset, namespace, err := client.ForContext("") + kubectx, err := k8s.Client("") if err != nil { return fmt.Errorf("unable to get kube client: %v", err) } - jobClient := clientset.BatchV1().Jobs(namespace) - - // Delete existing job with same name. - deletePolicy := metav1.DeletePropagationBackground - deleteOptions := &metav1.DeleteOptions{ - GracePeriodSeconds: types.Int64Ptr(0), - PropagationPolicy: &deletePolicy, - } - err = jobClient.Delete(spec.Name, deleteOptions) + err = kubectx.DeleteJob(spec.Name) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("unable to previous job: %v", err) } // Try to create the job using retry with backoff. - err = retry.RetryOnExists(retry.DefaultBackoff, func() error { - _, err = jobClient.Create(job) - return err - }) + // This handles scenarios where an existing job is still being terminated, etc. + err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) if err != nil { return fmt.Errorf("unable to create job: %v", err) } diff --git a/pkg/kube/client/client.go b/internal/k8s/client.go similarity index 80% rename from pkg/kube/client/client.go rename to internal/k8s/client.go index 22bb46a..9d49fbf 100644 --- a/pkg/kube/client/client.go +++ b/internal/k8s/client.go @@ -1,4 +1,4 @@ -package client +package k8s import ( "fmt" @@ -8,19 +8,26 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -// ForContext returns a client and namespace for the specified context. -func ForContext(context string) (*kubernetes.Clientset, string, error) { +type KubeContext struct { + Client *kubernetes.Clientset + Namespace string +} + +// Client returns a k8s client and namespace for the specified context. +func Client(context string) (*KubeContext, error) { config, namespace, err := buildClientConfig(context) if err != nil { - return nil, "", err + return nil, err } client, err := kubernetes.NewForConfig(config) if err != nil { - return nil, "", err + return nil, err } - return client, namespace, nil + kubectx := &KubeContext{client, namespace} + + return kubectx, nil } // buildClientConfig returns a complete client config and the namespace for the given context. diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go new file mode 100644 index 0000000..5a6f2b3 --- /dev/null +++ b/internal/k8s/jobs.go @@ -0,0 +1,111 @@ +package k8s + +import ( + "fmt" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/rest" +) + +// SimpleJobSpec represents an extremely simplified k8s job specification. +type SimpleJobSpec struct { + Name string `yaml:"name"` + Image string `yaml:"image"` + Command StringArray `yaml:"command"` + GPU uint `yaml:"gpu"` +} + +var DefaultLogOptions = &corev1.PodLogOptions{ + // TODO: Make these configurable via flags? + Follow: true, + // TailLines: int64Ptr(20), +} + +// Expand expands the simplified job spec into a full job object. +func (spec *SimpleJobSpec) Expand() *batchv1.Job { + return &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: spec.Name, + }, + Spec: batchv1.JobSpec{ + BackoffLimit: int32Ptr(0), + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyOnFailure, + Containers: []corev1.Container{ + { + Name: spec.Name, + Image: spec.Image, + Command: spec.Command, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "storage", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "storage", + }, + }, + }, + }, + }, + }, + }, + } +} + +func (kubectx *KubeContext) ListJobs() (*batchv1.JobList, error) { + jobs, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + return jobs, nil +} + +func (kubectx *KubeContext) DeleteJob(name string) error { + deletePolicy := metav1.DeletePropagationBackground + deleteOptions := &metav1.DeleteOptions{ + GracePeriodSeconds: int64Ptr(0), + PropagationPolicy: &deletePolicy, + } + + err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Delete(name, deleteOptions) + return err +} + +func (kubectx *KubeContext) CreateJob(job *batchv1.Job) error { + _, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Create(job) + return err +} + +func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) (*rest.Request, error) { + getOptions := metav1.GetOptions{} + job, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Get(name, getOptions) + if err != nil { + return nil, fmt.Errorf("unable to get job: %v", err) + } + + selector := labels.Set(job.Spec.Selector.MatchLabels).String() + listOptions := metav1.ListOptions{LabelSelector: selector} + pods, err := kubectx.Client.CoreV1().Pods(kubectx.Namespace).List(listOptions) + if err != nil { + return nil, fmt.Errorf("unable to get pods for job: %v", err) + } + + if len(pods.Items) == 0 { + return nil, nil + } + + // TODO: Add support for multiple pods? + pod := pods.Items[0] + req := kubectx.Client.CoreV1().Pods(kubectx.Namespace).GetLogs(pod.Name, opts) + return req, nil +} + +func int32Ptr(i int32) *int32 { return &i } +func int64Ptr(i int64) *int64 { return &i } diff --git a/pkg/kube/retry/retry.go b/internal/k8s/retry.go similarity index 99% rename from pkg/kube/retry/retry.go rename to internal/k8s/retry.go index d8bce35..fbeba5e 100644 --- a/pkg/kube/retry/retry.go +++ b/internal/k8s/retry.go @@ -1,4 +1,4 @@ -package retry +package k8s // TODO: Remove this module once we upgrade to client-go v16. diff --git a/pkg/types/stringarray.go b/internal/k8s/stringarray.go similarity index 98% rename from pkg/types/stringarray.go rename to internal/k8s/stringarray.go index bacb13e..b962947 100644 --- a/pkg/types/stringarray.go +++ b/internal/k8s/stringarray.go @@ -1,4 +1,4 @@ -package types +package k8s import ( "fmt" diff --git a/pkg/types/jobspec.go b/pkg/types/jobspec.go deleted file mode 100644 index b5b63d6..0000000 --- a/pkg/types/jobspec.go +++ /dev/null @@ -1,55 +0,0 @@ -package types - -import ( - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// SimpleJobSpec represents an extremely simplified k8s job specification. -type SimpleJobSpec struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Command StringArray `yaml:"command"` - GPU uint `yaml:"gpu"` -} - -// Expand expands the simplified job spec into a full job object. -func (spec *SimpleJobSpec) Expand() *batchv1.Job { - return &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: spec.Name, - }, - Spec: batchv1.JobSpec{ - BackoffLimit: Int32Ptr(0), - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyOnFailure, - Containers: []corev1.Container{ - { - Name: spec.Name, - Image: spec.Image, - Command: spec.Command, - }, - }, - Volumes: []corev1.Volume{ - { - Name: "storage", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "storage", - }, - }, - }, - }, - }, - }, - }, - } -} - -// Int32Ptr returns a pointer to the specified int32 value. -func Int32Ptr(i int32) *int32 { return &i } - -// Int64Ptr returns a pointer to the specified int64 value. -func Int64Ptr(i int64) *int64 { return &i } From 29fa6147c8c4dbc4e505a69aaed113c96acb117d Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 4 Nov 2019 14:15:10 +0100 Subject: [PATCH 06/58] Add documentation to exported identifiers --- internal/k8s/client.go | 1 + internal/k8s/jobs.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/internal/k8s/client.go b/internal/k8s/client.go index 9d49fbf..4edce12 100644 --- a/internal/k8s/client.go +++ b/internal/k8s/client.go @@ -8,6 +8,7 @@ import ( "k8s.io/client-go/tools/clientcmd" ) +// KubeContext represents a concrete Kubernetes API context. type KubeContext struct { Client *kubernetes.Clientset Namespace string diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 5a6f2b3..461d69e 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -18,6 +18,7 @@ type SimpleJobSpec struct { GPU uint `yaml:"gpu"` } +// DefaultLogOptions is the default set of options used for retrieving pod logs. var DefaultLogOptions = &corev1.PodLogOptions{ // TODO: Make these configurable via flags? Follow: true, @@ -58,6 +59,7 @@ func (spec *SimpleJobSpec) Expand() *batchv1.Job { } } +// ListJobs returns all jobs. func (kubectx *KubeContext) ListJobs() (*batchv1.JobList, error) { jobs, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).List(metav1.ListOptions{}) if err != nil { @@ -67,6 +69,7 @@ func (kubectx *KubeContext) ListJobs() (*batchv1.JobList, error) { return jobs, nil } +// DeleteJob deletes the job with the given name. func (kubectx *KubeContext) DeleteJob(name string) error { deletePolicy := metav1.DeletePropagationBackground deleteOptions := &metav1.DeleteOptions{ @@ -78,11 +81,13 @@ func (kubectx *KubeContext) DeleteJob(name string) error { return err } +// CreateJob creates a job with the given specification. func (kubectx *KubeContext) CreateJob(job *batchv1.Job) error { _, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Create(job) return err } +// GetJobLogs returns the pod logs for the job with the given name. func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) (*rest.Request, error) { getOptions := metav1.GetOptions{} job, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Get(name, getOptions) From 0dbcd26886219459b00fb81e1ea410bb74a9f18a Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 4 Nov 2019 19:20:19 +0100 Subject: [PATCH 07/58] Add some TODO comments --- internal/k8s/jobs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 461d69e..3556f59 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -27,6 +27,8 @@ var DefaultLogOptions = &corev1.PodLogOptions{ // Expand expands the simplified job spec into a full job object. func (spec *SimpleJobSpec) Expand() *batchv1.Job { + // TODO: Implement support for resources. + // TODO: Implement "NVIDIA_XYZ" environment variables to fix e.g. `gpu: 0` problem. return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: spec.Name, From 82c208f0629c1421588c04d33a093b2eb8a7e67d Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 5 Nov 2019 08:30:06 +0100 Subject: [PATCH 08/58] Remove old shellscript implementation --- bin/kubectl-job-kill | 11 ----------- bin/kubectl-job-list | 6 ------ bin/kubectl-job-run | 18 ------------------ bin/kubectl-job-watch | 8 -------- 4 files changed, 43 deletions(-) delete mode 100755 bin/kubectl-job-kill delete mode 100755 bin/kubectl-job-list delete mode 100755 bin/kubectl-job-run delete mode 100755 bin/kubectl-job-watch diff --git a/bin/kubectl-job-kill b/bin/kubectl-job-kill deleted file mode 100755 index d1ec3d2..0000000 --- a/bin/kubectl-job-kill +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -eu - -job_name="$1" - -# Delete existing job. -kubectl delete jobs \ - --now \ - --ignore-not-found \ - "${job_name}" diff --git a/bin/kubectl-job-list b/bin/kubectl-job-list deleted file mode 100755 index 0ca3f41..0000000 --- a/bin/kubectl-job-list +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -eu - -# Print job summary. -kubectl get jobs --ignore-not-found diff --git a/bin/kubectl-job-run b/bin/kubectl-job-run deleted file mode 100755 index 01ef0be..0000000 --- a/bin/kubectl-job-run +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -set -eu - -job_file="$1" - -# Delete existing job. -kubectl delete \ - --now \ - --ignore-not-found \ - --filename="${job_file}" - -# Create job. -kubectl apply --filename="${job_file}" - -# Print job summary? -echo "" -kubectl get --filename="${job_file}" diff --git a/bin/kubectl-job-watch b/bin/kubectl-job-watch deleted file mode 100755 index cb4b4ec..0000000 --- a/bin/kubectl-job-watch +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -eu - -job_name="$1" - -# TODO: Follow log for most recently scheduled job, or (newest) running job? -kubectl logs --follow "jobs/${job_name}" From 4ba481c4b8a94be5fb3cdcc2a9c67196295156db Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 8 Nov 2019 17:38:45 +0100 Subject: [PATCH 09/58] Add support for volumes and resources --- cmd/job_run.go | 6 ++- internal/k8s/jobs.go | 104 ++++++++++++++++++++++++++++++++----------- 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index d54487b..bbe100c 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -38,7 +38,11 @@ var runCmd = &cobra.Command{ if err := yaml.Unmarshal(data, &spec); err != nil { return fmt.Errorf("unable to parse file: %v", err) } - job := spec.Expand() + + job, err := spec.Expand() + if err != nil { + return fmt.Errorf("invalid job spec: %v", err) + } kubectx, err := k8s.Client("") if err != nil { diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 3556f59..d0b2ce2 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -5,6 +5,7 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" @@ -12,53 +13,102 @@ import ( // SimpleJobSpec represents an extremely simplified k8s job specification. type SimpleJobSpec struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Command StringArray `yaml:"command"` - GPU uint `yaml:"gpu"` + Name string `yaml:"name"` + Image string `yaml:"image"` + Command StringArray `yaml:"command"` + WorkingDir string `yaml:"workingDir,omitempty"` + CPU string `yaml:"cpu,omitempty"` + Memory string `yaml:"memory,omitempty"` + GPU string `yaml:"gpu,omitempty"` } -// DefaultLogOptions is the default set of options used for retrieving pod logs. +// DefaultLogOptions is the default set of options used when retrieving logs. var DefaultLogOptions = &corev1.PodLogOptions{ // TODO: Make these configurable via flags? Follow: true, // TailLines: int64Ptr(20), } +// DefaultVolumes is the default set of volumes mounted provided to the job containers. +var DefaultVolumes = []corev1.Volume{ + { + Name: "storage", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "storage", + }, + }, + }, +} + +// DefaultVolumeMounts is the default set of mount paths for each of the default container volumes. +var DefaultVolumeMounts = []corev1.VolumeMount{ + { + Name: "storage", + MountPath: "/storage", + }, +} + // Expand expands the simplified job spec into a full job object. -func (spec *SimpleJobSpec) Expand() *batchv1.Job { - // TODO: Implement support for resources. +func (spec *SimpleJobSpec) Expand() (*batchv1.Job, error) { + resources := corev1.ResourceRequirements{Limits: corev1.ResourceList{}} + if spec.CPU != "" { + cpu, err := resource.ParseQuantity(spec.CPU) + if err != nil { + return nil, err + } + + resources.Limits["cpu"] = cpu + } + if spec.Memory != "" { + memory, err := resource.ParseQuantity(spec.Memory) + if err != nil { + return nil, err + } + + resources.Limits["memory"] = memory + } + if spec.GPU != "" { + gpu, err := resource.ParseQuantity(spec.GPU) + if err != nil { + return nil, err + } + + resources.Limits["nvidia.com/gpu"] = gpu + } + + volumes := DefaultVolumes + volumeMounts := DefaultVolumeMounts + // TODO: Implement "NVIDIA_XYZ" environment variables to fix e.g. `gpu: 0` problem. - return &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: spec.Name, + containers := []corev1.Container{ + { + Name: spec.Name, + Image: spec.Image, + Command: spec.Command, + WorkingDir: spec.WorkingDir, + VolumeMounts: volumeMounts, + Resources: resources, + + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, }, + } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: spec.Name}, Spec: batchv1.JobSpec{ BackoffLimit: int32Ptr(0), Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyOnFailure, - Containers: []corev1.Container{ - { - Name: spec.Name, - Image: spec.Image, - Command: spec.Command, - }, - }, - Volumes: []corev1.Volume{ - { - Name: "storage", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "storage", - }, - }, - }, - }, + Containers: containers, + Volumes: volumes, }, }, }, } + + return job, nil } // ListJobs returns all jobs. From 04075369bc56b041aa6110d71582e470f64a2e89 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 9 Nov 2019 18:19:58 +0100 Subject: [PATCH 10/58] Refactor simple job spec expansion --- internal/k8s/jobs.go | 94 ------------------------ internal/k8s/simplejobspec.go | 132 ++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 94 deletions(-) create mode 100644 internal/k8s/simplejobspec.go diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index d0b2ce2..2724ca4 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -5,23 +5,11 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" ) -// SimpleJobSpec represents an extremely simplified k8s job specification. -type SimpleJobSpec struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Command StringArray `yaml:"command"` - WorkingDir string `yaml:"workingDir,omitempty"` - CPU string `yaml:"cpu,omitempty"` - Memory string `yaml:"memory,omitempty"` - GPU string `yaml:"gpu,omitempty"` -} - // DefaultLogOptions is the default set of options used when retrieving logs. var DefaultLogOptions = &corev1.PodLogOptions{ // TODO: Make these configurable via flags? @@ -29,88 +17,6 @@ var DefaultLogOptions = &corev1.PodLogOptions{ // TailLines: int64Ptr(20), } -// DefaultVolumes is the default set of volumes mounted provided to the job containers. -var DefaultVolumes = []corev1.Volume{ - { - Name: "storage", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "storage", - }, - }, - }, -} - -// DefaultVolumeMounts is the default set of mount paths for each of the default container volumes. -var DefaultVolumeMounts = []corev1.VolumeMount{ - { - Name: "storage", - MountPath: "/storage", - }, -} - -// Expand expands the simplified job spec into a full job object. -func (spec *SimpleJobSpec) Expand() (*batchv1.Job, error) { - resources := corev1.ResourceRequirements{Limits: corev1.ResourceList{}} - if spec.CPU != "" { - cpu, err := resource.ParseQuantity(spec.CPU) - if err != nil { - return nil, err - } - - resources.Limits["cpu"] = cpu - } - if spec.Memory != "" { - memory, err := resource.ParseQuantity(spec.Memory) - if err != nil { - return nil, err - } - - resources.Limits["memory"] = memory - } - if spec.GPU != "" { - gpu, err := resource.ParseQuantity(spec.GPU) - if err != nil { - return nil, err - } - - resources.Limits["nvidia.com/gpu"] = gpu - } - - volumes := DefaultVolumes - volumeMounts := DefaultVolumeMounts - - // TODO: Implement "NVIDIA_XYZ" environment variables to fix e.g. `gpu: 0` problem. - containers := []corev1.Container{ - { - Name: spec.Name, - Image: spec.Image, - Command: spec.Command, - WorkingDir: spec.WorkingDir, - VolumeMounts: volumeMounts, - Resources: resources, - - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - }, - } - - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{Name: spec.Name}, - Spec: batchv1.JobSpec{ - BackoffLimit: int32Ptr(0), - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyOnFailure, - Containers: containers, - Volumes: volumes, - }, - }, - }, - } - - return job, nil -} - // ListJobs returns all jobs. func (kubectx *KubeContext) ListJobs() (*batchv1.JobList, error) { jobs, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).List(metav1.ListOptions{}) diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejobspec.go new file mode 100644 index 0000000..d647f8b --- /dev/null +++ b/internal/k8s/simplejobspec.go @@ -0,0 +1,132 @@ +package k8s + +import ( + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SimpleJobSpec represents an extremely simplified k8s job specification. +type SimpleJobSpec struct { + Name string `yaml:"name"` + Image string `yaml:"image"` + Command StringArray `yaml:"command"` + WorkingDir string `yaml:"workingDir,omitempty"` + CPU string `yaml:"cpu,omitempty"` + Memory string `yaml:"memory,omitempty"` + GPU string `yaml:"gpu,omitempty"` +} + +var ( + defaultTerminationMessagePolicy = corev1.TerminationMessageFallbackToLogsOnError + + // Do not restart failing jobs. + defaultRestartPolicy = corev1.RestartPolicyOnFailure + defaultBackoffLimit = int32Ptr(0) +) + +var defaultVolumes = []corev1.Volume{ + { + Name: "storage", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "storage", + }, + }, + }, +} + +var defaultVolumeMounts = []corev1.VolumeMount{ + { + Name: "storage", + MountPath: "/storage", + }, +} + +func (spec *SimpleJobSpec) resources() (*corev1.ResourceRequirements, error) { + resources := &corev1.ResourceRequirements{Limits: corev1.ResourceList{}} + + if spec.CPU != "" { + qty, err := resource.ParseQuantity(spec.CPU) + if err != nil { + return nil, err + } + + resources.Limits["cpu"] = qty + } + + if spec.Memory != "" { + qty, err := resource.ParseQuantity(spec.Memory) + if err != nil { + return nil, err + } + + resources.Limits["memory"] = qty + } + + if spec.GPU != "" { + qty, err := resource.ParseQuantity(spec.GPU) + if err != nil { + return nil, err + } + + resources.Limits["nvidia.com/gpu"] = qty + } + + return resources, nil +} + +func (spec *SimpleJobSpec) containers() ([]corev1.Container, error) { + resources, err := spec.resources() + if err != nil { + return nil, err + } + + // TODO: Implement "NVIDIA_XYZ" environment variables to fix e.g. `gpu: 0` problem. + + containers := []corev1.Container{ + { + Name: spec.Name, + Image: spec.Image, + Command: spec.Command, + WorkingDir: spec.WorkingDir, + VolumeMounts: defaultVolumeMounts, + Resources: *resources, + + TerminationMessagePolicy: defaultTerminationMessagePolicy, + }, + } + + return containers, nil +} + +func (spec *SimpleJobSpec) meta() metav1.ObjectMeta { + return metav1.ObjectMeta{ + Name: spec.Name, + } +} + +// Expand expands the simplified job spec into a full job object. +func (spec *SimpleJobSpec) Expand() (*batchv1.Job, error) { + containers, err := spec.containers() + if err != nil { + return nil, err + } + + job := &batchv1.Job{ + ObjectMeta: spec.meta(), + Spec: batchv1.JobSpec{ + BackoffLimit: defaultBackoffLimit, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: containers, + Volumes: defaultVolumes, + RestartPolicy: defaultRestartPolicy, + }, + }, + }, + } + + return job, nil +} From df43899f66f0b3654e0d677ea0049a310626190d Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 10 Nov 2019 16:28:15 +0100 Subject: [PATCH 11/58] Ignore .DS_Store files --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index a395e03..ac61951 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ +# Platform. +.DS_Store + +# Generic. .vscode/ + +# Project. /out/ From 34779b06c8b90d65eef8fe00f2657099fb8bad25 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 10 Nov 2019 21:43:55 +0100 Subject: [PATCH 12/58] Add initial support for "normal" job specs --- cmd/job_run.go | 30 +++++++++++++++++++++--------- go.mod | 1 + 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index bbe100c..ee9c2e6 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -4,11 +4,14 @@ import ( "fmt" "io/ioutil" "os" + "regexp" "github.com/spf13/cobra" "github.com/uitml/frink/internal/k8s" "gopkg.in/yaml.v3" + batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/api/errors" + sigyaml "sigs.k8s.io/yaml" ) var runCmd = &cobra.Command{ @@ -33,15 +36,24 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to read file: %v", err) } - // TODO: Implement support for "normal" k8s job specs. Determine based on presence of 'kind' and/or 'apiVersion' keys? - spec := k8s.SimpleJobSpec{} - if err := yaml.Unmarshal(data, &spec); err != nil { - return fmt.Errorf("unable to parse file: %v", err) - } + // TODO: Refactor this by extracting functions, etc. + var job *batchv1.Job + re := regexp.MustCompile(`apiVersion:`) + if re.Match(data) { + job = &batchv1.Job{} + if err := sigyaml.UnmarshalStrict(data, job); err != nil { + return fmt.Errorf("unable to parse file: %v", err) + } + } else { + spec := &k8s.SimpleJobSpec{} + if err := yaml.Unmarshal(data, spec); err != nil { + return fmt.Errorf("unable to parse file: %v", err) + } - job, err := spec.Expand() - if err != nil { - return fmt.Errorf("invalid job spec: %v", err) + job, err = spec.Expand() + if err != nil { + return fmt.Errorf("invalid job spec: %v", err) + } } kubectx, err := k8s.Client("") @@ -49,7 +61,7 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to get kube client: %v", err) } - err = kubectx.DeleteJob(spec.Name) + err = kubectx.DeleteJob(job.Name) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("unable to previous job: %v", err) } diff --git a/go.mod b/go.mod index c38a18c..30b405f 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,5 @@ require ( k8s.io/api v0.0.0-20191016110246-af539daaa43a k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 k8s.io/client-go v0.0.0-20191016110837-54936ba21026 + sigs.k8s.io/yaml v1.1.0 ) From c6f57951e7416c8e935a1f3746aa6ece8c1ba5a4 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 11 Nov 2019 12:15:19 +0100 Subject: [PATCH 13/58] Nuke `StringArray` type --- internal/k8s/simplejobspec.go | 14 +++++++------- internal/k8s/stringarray.go | 33 --------------------------------- 2 files changed, 7 insertions(+), 40 deletions(-) delete mode 100644 internal/k8s/stringarray.go diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejobspec.go index d647f8b..01aa29a 100644 --- a/internal/k8s/simplejobspec.go +++ b/internal/k8s/simplejobspec.go @@ -9,13 +9,13 @@ import ( // SimpleJobSpec represents an extremely simplified k8s job specification. type SimpleJobSpec struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Command StringArray `yaml:"command"` - WorkingDir string `yaml:"workingDir,omitempty"` - CPU string `yaml:"cpu,omitempty"` - Memory string `yaml:"memory,omitempty"` - GPU string `yaml:"gpu,omitempty"` + Name string `yaml:"name"` + Image string `yaml:"image"` + Command []string `yaml:"command,omitempty,flow"` + WorkingDir string `yaml:"workingDir,omitempty"` + CPU string `yaml:"cpu,omitempty"` + Memory string `yaml:"memory,omitempty"` + GPU string `yaml:"gpu,omitempty"` } var ( diff --git a/internal/k8s/stringarray.go b/internal/k8s/stringarray.go deleted file mode 100644 index b962947..0000000 --- a/internal/k8s/stringarray.go +++ /dev/null @@ -1,33 +0,0 @@ -package k8s - -import ( - "fmt" - - "gopkg.in/yaml.v3" -) - -// StringArray is a proxy type to support umarshalling YAML nodes that are scalars or sequences of type string. -type StringArray []string - -// UnmarshalYAML implements behavior to handle nodes that are scalars or sequences of type string. -// TODO: Refactor this; don't like the way it looks and "feels"... -func (array *StringArray) UnmarshalYAML(node *yaml.Node) error { - switch node.Kind { - case yaml.SequenceNode: - var sequence []string - if err := node.Decode(&sequence); err != nil { - return err - } - *array = sequence - case yaml.ScalarNode: - var scalar string - if err := node.Decode(&scalar); err != nil { - return err - } - *array = []string{scalar} - default: - return fmt.Errorf("kind must be scalar or sequence") - } - - return nil -} From d914282a0d1b0b7de94fc6cd34837d4f4e3f6fbf Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 11 Nov 2019 12:55:54 +0100 Subject: [PATCH 14/58] Convert from YAML to JSON before unmarshalling Switch from treating SimpleJobSpec as YAML by converting to JSON before unmarshalling. The biggest advantage of using JSON internally is that all of the k8s plumbing uses JSON. By going the same route our code can be simplified. The specific approach implemented has the benefit of still providing a pleasant user experience by keeping support for jobs defined in YAML. In other words, this change is transparent to the user. --- cmd/job_run.go | 7 +++--- go.mod | 1 - go.sum | 2 -- internal/k8s/simplejobspec.go | 41 +++++++++++------------------------ 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index ee9c2e6..6f2c8d5 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -8,10 +8,9 @@ import ( "github.com/spf13/cobra" "github.com/uitml/frink/internal/k8s" - "gopkg.in/yaml.v3" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/api/errors" - sigyaml "sigs.k8s.io/yaml" + "sigs.k8s.io/yaml" ) var runCmd = &cobra.Command{ @@ -41,12 +40,12 @@ var runCmd = &cobra.Command{ re := regexp.MustCompile(`apiVersion:`) if re.Match(data) { job = &batchv1.Job{} - if err := sigyaml.UnmarshalStrict(data, job); err != nil { + if err := yaml.UnmarshalStrict(data, job); err != nil { return fmt.Errorf("unable to parse file: %v", err) } } else { spec := &k8s.SimpleJobSpec{} - if err := yaml.Unmarshal(data, spec); err != nil { + if err := yaml.UnmarshalStrict(data, spec); err != nil { return fmt.Errorf("unable to parse file: %v", err) } diff --git a/go.mod b/go.mod index 30b405f..fbc0db4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.13 require ( github.com/spf13/cobra v0.0.5 - gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 k8s.io/api v0.0.0-20191016110246-af539daaa43a k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 k8s.io/client-go v0.0.0-20191016110837-54936ba21026 diff --git a/go.sum b/go.sum index 239c0a8..5986b1a 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 h1:VKvJ/mQ4BgCjZUDggYFxTe0qv9jPMHsZPD4Xt91Y5H4= -gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.0.0-20191016110246-af539daaa43a h1:IocS6+jQEuO8ZGQXhrD9BZ7Ze+Ly6FUKPlYs/m4I6xo= k8s.io/api v0.0.0-20191016110246-af539daaa43a/go.mod h1:ceHJE/vDjU8jKnRV6Vqn/+vyZmC6NvOluInN+RhQkIs= k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 h1:GYWOVyO+ZU+YK01nyPiAwB/fQrkxysXwkjbSpIIHdN4= diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejobspec.go index 01aa29a..e11eed6 100644 --- a/internal/k8s/simplejobspec.go +++ b/internal/k8s/simplejobspec.go @@ -9,13 +9,13 @@ import ( // SimpleJobSpec represents an extremely simplified k8s job specification. type SimpleJobSpec struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Command []string `yaml:"command,omitempty,flow"` - WorkingDir string `yaml:"workingDir,omitempty"` - CPU string `yaml:"cpu,omitempty"` - Memory string `yaml:"memory,omitempty"` - GPU string `yaml:"gpu,omitempty"` + Name string `json:"name"` + Image string `json:"image"` + WorkingDir string `json:"workingDir,omitempty"` + Command []string `json:"command,omitempty"` + CPU resource.Quantity `json:"cpu,omitempty"` + Memory resource.Quantity `json:"memory,omitempty"` + GPU resource.Quantity `json:"gpu,omitempty"` } var ( @@ -47,31 +47,16 @@ var defaultVolumeMounts = []corev1.VolumeMount{ func (spec *SimpleJobSpec) resources() (*corev1.ResourceRequirements, error) { resources := &corev1.ResourceRequirements{Limits: corev1.ResourceList{}} - if spec.CPU != "" { - qty, err := resource.ParseQuantity(spec.CPU) - if err != nil { - return nil, err - } - - resources.Limits["cpu"] = qty + if !spec.CPU.IsZero() { + resources.Limits["cpu"] = spec.CPU } - if spec.Memory != "" { - qty, err := resource.ParseQuantity(spec.Memory) - if err != nil { - return nil, err - } - - resources.Limits["memory"] = qty + if !spec.Memory.IsZero() { + resources.Limits["memory"] = spec.Memory } - if spec.GPU != "" { - qty, err := resource.ParseQuantity(spec.GPU) - if err != nil { - return nil, err - } - - resources.Limits["nvidia.com/gpu"] = qty + if !spec.GPU.IsZero() { + resources.Limits["nvidia.com/gpu"] = spec.GPU } return resources, nil From 815da96c7f237bf06c0bfd411c9af2fa74c01295 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 11 Nov 2019 17:52:33 +0100 Subject: [PATCH 15/58] Add logic for overriding job specs --- cmd/job_run.go | 2 + internal/k8s/jobs.go | 39 ++++++++++++++++ internal/k8s/simplejobspec.go | 85 ++++++++++++----------------------- 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index 6f2c8d5..e86f9ef 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -65,6 +65,8 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to previous job: %v", err) } + k8s.OverrideJobSpec(job) + // Try to create the job using retry with backoff. // This handles scenarios where an existing job is still being terminated, etc. err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 2724ca4..c72c123 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -10,6 +10,14 @@ import ( "k8s.io/client-go/rest" ) +var defaultTerminationMessagePolicy = corev1.TerminationMessageFallbackToLogsOnError + +var ( + // Do not restart failing jobs. + defaultRestartPolicy = corev1.RestartPolicyOnFailure + defaultBackoffLimit = int32Ptr(0) +) + // DefaultLogOptions is the default set of options used when retrieving logs. var DefaultLogOptions = &corev1.PodLogOptions{ // TODO: Make these configurable via flags? @@ -70,5 +78,36 @@ func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) return req, nil } +// OverrideJobSpec removes zero quantity resources, and sets other important defaults. +func OverrideJobSpec(job *batchv1.Job) { + containers := job.Spec.Template.Spec.Containers + for i := range containers { + container := &containers[i] + removeZeroResources(container) + setTerminationPolicy(container) + } + + setRestartPolicy(job) +} + +func removeZeroResources(container *corev1.Container) { + limits := container.Resources.Limits + for k, v := range limits { + if v.IsZero() { + // TODO: Notify user. + delete(limits, k) + } + } +} + +func setTerminationPolicy(container *corev1.Container) { + container.TerminationMessagePolicy = defaultTerminationMessagePolicy +} + +func setRestartPolicy(job *batchv1.Job) { + job.Spec.BackoffLimit = defaultBackoffLimit + job.Spec.Template.Spec.RestartPolicy = defaultRestartPolicy +} + func int32Ptr(i int32) *int32 { return &i } func int64Ptr(i int64) *int64 { return &i } diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejobspec.go index e11eed6..6718bd3 100644 --- a/internal/k8s/simplejobspec.go +++ b/internal/k8s/simplejobspec.go @@ -18,46 +18,26 @@ type SimpleJobSpec struct { GPU resource.Quantity `json:"gpu,omitempty"` } -var ( - defaultTerminationMessagePolicy = corev1.TerminationMessageFallbackToLogsOnError - - // Do not restart failing jobs. - defaultRestartPolicy = corev1.RestartPolicyOnFailure - defaultBackoffLimit = int32Ptr(0) -) - -var defaultVolumes = []corev1.Volume{ - { - Name: "storage", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "storage", - }, +var defaultVolumes = []corev1.Volume{{ + Name: "storage", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "storage", }, }, -} +}} -var defaultVolumeMounts = []corev1.VolumeMount{ - { - Name: "storage", - MountPath: "/storage", - }, -} +var defaultVolumeMounts = []corev1.VolumeMount{{ + Name: "storage", + MountPath: "/storage", +}} func (spec *SimpleJobSpec) resources() (*corev1.ResourceRequirements, error) { - resources := &corev1.ResourceRequirements{Limits: corev1.ResourceList{}} - - if !spec.CPU.IsZero() { - resources.Limits["cpu"] = spec.CPU - } - - if !spec.Memory.IsZero() { - resources.Limits["memory"] = spec.Memory - } - - if !spec.GPU.IsZero() { - resources.Limits["nvidia.com/gpu"] = spec.GPU - } + resources := &corev1.ResourceRequirements{Limits: corev1.ResourceList{ + "cpu": spec.CPU, + "memory": spec.Memory, + "nvidia.com/gpu": spec.GPU, + }} return resources, nil } @@ -68,20 +48,17 @@ func (spec *SimpleJobSpec) containers() ([]corev1.Container, error) { return nil, err } - // TODO: Implement "NVIDIA_XYZ" environment variables to fix e.g. `gpu: 0` problem. + containers := []corev1.Container{{ + Name: spec.Name, + Image: spec.Image, + Command: spec.Command, + WorkingDir: spec.WorkingDir, + VolumeMounts: defaultVolumeMounts, + Resources: *resources, - containers := []corev1.Container{ - { - Name: spec.Name, - Image: spec.Image, - Command: spec.Command, - WorkingDir: spec.WorkingDir, - VolumeMounts: defaultVolumeMounts, - Resources: *resources, - - TerminationMessagePolicy: defaultTerminationMessagePolicy, - }, - } + Stdin: true, + TTY: true, + }} return containers, nil } @@ -102,14 +79,10 @@ func (spec *SimpleJobSpec) Expand() (*batchv1.Job, error) { job := &batchv1.Job{ ObjectMeta: spec.meta(), Spec: batchv1.JobSpec{ - BackoffLimit: defaultBackoffLimit, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: containers, - Volumes: defaultVolumes, - RestartPolicy: defaultRestartPolicy, - }, - }, + Template: corev1.PodTemplateSpec{Spec: corev1.PodSpec{ + Containers: containers, + Volumes: defaultVolumes, + }}, }, } From cd2a807c142dd860bccdec99a505e5ccabd73301 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 11 Nov 2019 18:13:57 +0100 Subject: [PATCH 16/58] Refactor SimpleJobSpec expansion logic --- cmd/job_run.go | 6 ++---- internal/k8s/simplejobspec.go | 40 +++++++++++++++++------------------ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index e86f9ef..1e7b6a4 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -49,10 +49,7 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to parse file: %v", err) } - job, err = spec.Expand() - if err != nil { - return fmt.Errorf("invalid job spec: %v", err) - } + job = spec.Expand() } kubectx, err := k8s.Client("") @@ -65,6 +62,7 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to previous job: %v", err) } + // TODO: Reconsider this? Many reasons to avoid this; should be challenged. k8s.OverrideJobSpec(job) // Try to create the job using retry with backoff. diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejobspec.go index 6718bd3..b003806 100644 --- a/internal/k8s/simplejobspec.go +++ b/internal/k8s/simplejobspec.go @@ -32,35 +32,38 @@ var defaultVolumeMounts = []corev1.VolumeMount{{ MountPath: "/storage", }} -func (spec *SimpleJobSpec) resources() (*corev1.ResourceRequirements, error) { - resources := &corev1.ResourceRequirements{Limits: corev1.ResourceList{ +func (spec *SimpleJobSpec) volumes() []corev1.Volume { + return defaultVolumes +} + +func (spec *SimpleJobSpec) volumeMounts() []corev1.VolumeMount { + return defaultVolumeMounts +} + +func (spec *SimpleJobSpec) resources() corev1.ResourceRequirements { + resources := corev1.ResourceRequirements{Limits: corev1.ResourceList{ "cpu": spec.CPU, "memory": spec.Memory, "nvidia.com/gpu": spec.GPU, }} - return resources, nil + return resources } -func (spec *SimpleJobSpec) containers() ([]corev1.Container, error) { - resources, err := spec.resources() - if err != nil { - return nil, err - } - +func (spec *SimpleJobSpec) containers() []corev1.Container { containers := []corev1.Container{{ Name: spec.Name, Image: spec.Image, Command: spec.Command, WorkingDir: spec.WorkingDir, - VolumeMounts: defaultVolumeMounts, - Resources: *resources, + VolumeMounts: spec.volumeMounts(), + Resources: spec.resources(), Stdin: true, TTY: true, }} - return containers, nil + return containers } func (spec *SimpleJobSpec) meta() metav1.ObjectMeta { @@ -70,21 +73,16 @@ func (spec *SimpleJobSpec) meta() metav1.ObjectMeta { } // Expand expands the simplified job spec into a full job object. -func (spec *SimpleJobSpec) Expand() (*batchv1.Job, error) { - containers, err := spec.containers() - if err != nil { - return nil, err - } - +func (spec *SimpleJobSpec) Expand() *batchv1.Job { job := &batchv1.Job{ ObjectMeta: spec.meta(), Spec: batchv1.JobSpec{ Template: corev1.PodTemplateSpec{Spec: corev1.PodSpec{ - Containers: containers, - Volumes: defaultVolumes, + Containers: spec.containers(), + Volumes: spec.volumes(), }}, }, } - return job, nil + return job } From 6f3d122b962ac18dd725b00f9b5b3c779b2c7d35 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 11 Nov 2019 18:23:59 +0100 Subject: [PATCH 17/58] Restructure code --- cmd/job_run.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index 1e7b6a4..567fced 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -48,10 +48,12 @@ var runCmd = &cobra.Command{ if err := yaml.UnmarshalStrict(data, spec); err != nil { return fmt.Errorf("unable to parse file: %v", err) } - job = spec.Expand() } + // TODO: Reconsider this? Many reasons to avoid this; should be challenged. + k8s.OverrideJobSpec(job) + kubectx, err := k8s.Client("") if err != nil { return fmt.Errorf("unable to get kube client: %v", err) @@ -62,9 +64,6 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to previous job: %v", err) } - // TODO: Reconsider this? Many reasons to avoid this; should be challenged. - k8s.OverrideJobSpec(job) - // Try to create the job using retry with backoff. // This handles scenarios where an existing job is still being terminated, etc. err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) From 57aa5b97475ecb2c30fe59e4caa376fa49e66b5d Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 11 Nov 2019 18:37:29 +0100 Subject: [PATCH 18/58] Extract ParseJob function --- cmd/job_run.go | 24 ++---------------------- internal/k8s/jobs.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/cmd/job_run.go b/cmd/job_run.go index 567fced..68da206 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -2,15 +2,11 @@ package cmd import ( "fmt" - "io/ioutil" "os" - "regexp" "github.com/spf13/cobra" "github.com/uitml/frink/internal/k8s" - batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/yaml" ) var runCmd = &cobra.Command{ @@ -30,25 +26,9 @@ var runCmd = &cobra.Command{ return fmt.Errorf("unable to access file: %v", err) } - data, err := ioutil.ReadFile(file) + job, err := k8s.ParseJob(file) if err != nil { - return fmt.Errorf("unable to read file: %v", err) - } - - // TODO: Refactor this by extracting functions, etc. - var job *batchv1.Job - re := regexp.MustCompile(`apiVersion:`) - if re.Match(data) { - job = &batchv1.Job{} - if err := yaml.UnmarshalStrict(data, job); err != nil { - return fmt.Errorf("unable to parse file: %v", err) - } - } else { - spec := &k8s.SimpleJobSpec{} - if err := yaml.UnmarshalStrict(data, spec); err != nil { - return fmt.Errorf("unable to parse file: %v", err) - } - job = spec.Expand() + return fmt.Errorf("unable to parse job: %v", err) } // TODO: Reconsider this? Many reasons to avoid this; should be challenged. diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index c72c123..282d97a 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -2,12 +2,15 @@ package k8s import ( "fmt" + "io/ioutil" + "regexp" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" + "sigs.k8s.io/yaml" ) var defaultTerminationMessagePolicy = corev1.TerminationMessageFallbackToLogsOnError @@ -78,6 +81,32 @@ func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) return req, nil } +// ParseJob parses the file and returns the corresponding job. +func ParseJob(file string) (*batchv1.Job, error) { + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + // TODO: Refactor this by extracting functions, etc. + var job *batchv1.Job + re := regexp.MustCompile(`apiVersion:`) + if re.Match(data) { + job = &batchv1.Job{} + if err := yaml.UnmarshalStrict(data, job); err != nil { + return nil, err + } + } else { + spec := &SimpleJobSpec{} + if err := yaml.UnmarshalStrict(data, spec); err != nil { + return nil, err + } + job = spec.Expand() + } + + return job, nil +} + // OverrideJobSpec removes zero quantity resources, and sets other important defaults. func OverrideJobSpec(job *batchv1.Job) { containers := job.Spec.Template.Spec.Containers From 22d35ee3f1d098f5a95bae66e2e7458669a3ddf6 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 19 Nov 2019 08:18:38 +0100 Subject: [PATCH 19/58] Simplify log streaming code with io.Copy --- cmd/job_logs.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/cmd/job_logs.go b/cmd/job_logs.go index faa2eba..db4fb01 100644 --- a/cmd/job_logs.go +++ b/cmd/job_logs.go @@ -2,7 +2,6 @@ package cmd import ( "bufio" - "errors" "fmt" "io" "os" @@ -32,26 +31,15 @@ var logsCmd = &cobra.Command{ return fmt.Errorf("unable to get logs: %v", err) } - logs, err := req.Stream() + stream, err := req.Stream() if err != nil { return fmt.Errorf("unable to stream logs: %v", err) } - defer logs.Close() - - r := bufio.NewReader(logs) - for { - p, err := r.ReadBytes('\n') - if _, err := os.Stdout.Write(p); err != nil { - return fmt.Errorf("unable to write output: %v", err) - } - - if err != nil { - if errors.Is(err, io.EOF) { - break - } - - return fmt.Errorf("unable to read stream: %v", err) - } + defer stream.Close() + + reader := bufio.NewReader(stream) + if _, err := io.Copy(os.Stdout, reader); err != nil { + return fmt.Errorf("unable to write output: %v", err) } return nil From 720d2548bed33d68e32b7792d221b51589f3475c Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 20 Nov 2019 22:13:54 +0100 Subject: [PATCH 20/58] Ignore .envrc files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ac61951..8fdbb7b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Generic. .vscode/ +.envrc # Project. /out/ From 69cbecce926afe9746e701985f87e04b327e6577 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 22 Nov 2019 14:56:58 +0100 Subject: [PATCH 21/58] Refactor SimpleJobSpec --- internal/k8s/simplejobspec.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejobspec.go index b003806..55a5e09 100644 --- a/internal/k8s/simplejobspec.go +++ b/internal/k8s/simplejobspec.go @@ -9,13 +9,14 @@ import ( // SimpleJobSpec represents an extremely simplified k8s job specification. type SimpleJobSpec struct { - Name string `json:"name"` - Image string `json:"image"` - WorkingDir string `json:"workingDir,omitempty"` - Command []string `json:"command,omitempty"` - CPU resource.Quantity `json:"cpu,omitempty"` - Memory resource.Quantity `json:"memory,omitempty"` - GPU resource.Quantity `json:"gpu,omitempty"` + Name string `json:"name"` + Image string `json:"image"` + WorkingDir string `json:"workingDir,omitempty"` + Command []string `json:"command,omitempty"` + + Memory resource.Quantity `json:"memory,omitempty"` + CPU resource.Quantity `json:"cpu,omitempty"` + GPU resource.Quantity `json:"gpu,omitempty"` } var defaultVolumes = []corev1.Volume{{ @@ -42,8 +43,8 @@ func (spec *SimpleJobSpec) volumeMounts() []corev1.VolumeMount { func (spec *SimpleJobSpec) resources() corev1.ResourceRequirements { resources := corev1.ResourceRequirements{Limits: corev1.ResourceList{ - "cpu": spec.CPU, "memory": spec.Memory, + "cpu": spec.CPU, "nvidia.com/gpu": spec.GPU, }} From ebb9b7794ec12517f18c49563a56f235d789622a Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sun, 24 Nov 2019 21:23:40 +0100 Subject: [PATCH 22/58] Rename SimpleJobSpec to SimpleJob --- internal/k8s/jobs.go | 6 +-- .../k8s/{simplejobspec.go => simplejob.go} | 44 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) rename internal/k8s/{simplejobspec.go => simplejob.go} (56%) diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 282d97a..3c19316 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -97,11 +97,11 @@ func ParseJob(file string) (*batchv1.Job, error) { return nil, err } } else { - spec := &SimpleJobSpec{} - if err := yaml.UnmarshalStrict(data, spec); err != nil { + simple := &SimpleJob{} + if err := yaml.UnmarshalStrict(data, simple); err != nil { return nil, err } - job = spec.Expand() + job = simple.Expand() } return job, nil diff --git a/internal/k8s/simplejobspec.go b/internal/k8s/simplejob.go similarity index 56% rename from internal/k8s/simplejobspec.go rename to internal/k8s/simplejob.go index 55a5e09..63fcc78 100644 --- a/internal/k8s/simplejobspec.go +++ b/internal/k8s/simplejob.go @@ -7,8 +7,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// SimpleJobSpec represents an extremely simplified k8s job specification. -type SimpleJobSpec struct { +// SimpleJob represents an extremely simplified k8s job specification. +type SimpleJob struct { Name string `json:"name"` Image string `json:"image"` WorkingDir string `json:"workingDir,omitempty"` @@ -33,32 +33,32 @@ var defaultVolumeMounts = []corev1.VolumeMount{{ MountPath: "/storage", }} -func (spec *SimpleJobSpec) volumes() []corev1.Volume { +func (simple *SimpleJob) volumes() []corev1.Volume { return defaultVolumes } -func (spec *SimpleJobSpec) volumeMounts() []corev1.VolumeMount { +func (simple *SimpleJob) volumeMounts() []corev1.VolumeMount { return defaultVolumeMounts } -func (spec *SimpleJobSpec) resources() corev1.ResourceRequirements { +func (simple *SimpleJob) resources() corev1.ResourceRequirements { resources := corev1.ResourceRequirements{Limits: corev1.ResourceList{ - "memory": spec.Memory, - "cpu": spec.CPU, - "nvidia.com/gpu": spec.GPU, + "memory": simple.Memory, + "cpu": simple.CPU, + "nvidia.com/gpu": simple.GPU, }} return resources } -func (spec *SimpleJobSpec) containers() []corev1.Container { +func (simple *SimpleJob) containers() []corev1.Container { containers := []corev1.Container{{ - Name: spec.Name, - Image: spec.Image, - Command: spec.Command, - WorkingDir: spec.WorkingDir, - VolumeMounts: spec.volumeMounts(), - Resources: spec.resources(), + Name: simple.Name, + Image: simple.Image, + Command: simple.Command, + WorkingDir: simple.WorkingDir, + VolumeMounts: simple.volumeMounts(), + Resources: simple.resources(), Stdin: true, TTY: true, @@ -67,20 +67,20 @@ func (spec *SimpleJobSpec) containers() []corev1.Container { return containers } -func (spec *SimpleJobSpec) meta() metav1.ObjectMeta { +func (simple *SimpleJob) meta() metav1.ObjectMeta { return metav1.ObjectMeta{ - Name: spec.Name, + Name: simple.Name, } } -// Expand expands the simplified job spec into a full job object. -func (spec *SimpleJobSpec) Expand() *batchv1.Job { +// Expand expands the simplified job into a full job object. +func (simple *SimpleJob) Expand() *batchv1.Job { job := &batchv1.Job{ - ObjectMeta: spec.meta(), + ObjectMeta: simple.meta(), Spec: batchv1.JobSpec{ Template: corev1.PodTemplateSpec{Spec: corev1.PodSpec{ - Containers: spec.containers(), - Volumes: spec.volumes(), + Containers: simple.containers(), + Volumes: simple.volumes(), }}, }, } From 3256bb50d6655fdb53d58e21a978b3ff03df6f00 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 26 Nov 2019 10:52:58 +0100 Subject: [PATCH 23/58] Add project-wide EditorConfig rules --- .editorconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bb06a0b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +insert_final_newline = true +tab_width = 2 + +[*.go] +indent_style = tab +tab_width = 4 From b44f37a5e0e22b37849851c6c6a558ae82fbf1c8 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 29 Nov 2019 14:14:46 +0100 Subject: [PATCH 24/58] Wrap returned errors with %w --- cmd/job_list.go | 4 ++-- cmd/job_logs.go | 8 ++++---- cmd/job_remove.go | 4 ++-- cmd/job_run.go | 10 +++++----- internal/k8s/client.go | 4 ++-- internal/k8s/jobs.go | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cmd/job_list.go b/cmd/job_list.go index 9eab42f..9a25c21 100644 --- a/cmd/job_list.go +++ b/cmd/job_list.go @@ -30,12 +30,12 @@ var listCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { kubectx, err := k8s.Client("") if err != nil { - return fmt.Errorf("could not get k8s client: %v", err) + return fmt.Errorf("could not get k8s client: %w", err) } jobs, err := kubectx.ListJobs() if err != nil { - return fmt.Errorf("could not list jobs: %v", err) + return fmt.Errorf("could not list jobs: %w", err) } w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) diff --git a/cmd/job_logs.go b/cmd/job_logs.go index db4fb01..7dbf6a3 100644 --- a/cmd/job_logs.go +++ b/cmd/job_logs.go @@ -23,23 +23,23 @@ var logsCmd = &cobra.Command{ kubectx, err := k8s.Client("") if err != nil { - return fmt.Errorf("unable to get kube client: %v", err) + return fmt.Errorf("unable to get kube client: %w", err) } req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) if err != nil { - return fmt.Errorf("unable to get logs: %v", err) + return fmt.Errorf("unable to get logs: %w", err) } stream, err := req.Stream() if err != nil { - return fmt.Errorf("unable to stream logs: %v", err) + return fmt.Errorf("unable to stream logs: %w", err) } defer stream.Close() reader := bufio.NewReader(stream) if _, err := io.Copy(os.Stdout, reader); err != nil { - return fmt.Errorf("unable to write output: %v", err) + return fmt.Errorf("unable to write output: %w", err) } return nil diff --git a/cmd/job_remove.go b/cmd/job_remove.go index 956a458..0004f28 100644 --- a/cmd/job_remove.go +++ b/cmd/job_remove.go @@ -19,12 +19,12 @@ var removeCmd = &cobra.Command{ kubectx, err := k8s.Client("") if err != nil { - return fmt.Errorf("unable to get kube client: %v", err) + return fmt.Errorf("unable to get kube client: %w", err) } err = kubectx.DeleteJob(name) if err != nil { - return fmt.Errorf("unable to delete job: %v", err) + return fmt.Errorf("unable to delete job: %w", err) } return nil diff --git a/cmd/job_run.go b/cmd/job_run.go index 68da206..ebc27be 100644 --- a/cmd/job_run.go +++ b/cmd/job_run.go @@ -23,12 +23,12 @@ var runCmd = &cobra.Command{ return fmt.Errorf("specified file does not exist: %v", file) } - return fmt.Errorf("unable to access file: %v", err) + return fmt.Errorf("unable to access file: %w", err) } job, err := k8s.ParseJob(file) if err != nil { - return fmt.Errorf("unable to parse job: %v", err) + return fmt.Errorf("unable to parse job: %w", err) } // TODO: Reconsider this? Many reasons to avoid this; should be challenged. @@ -36,19 +36,19 @@ var runCmd = &cobra.Command{ kubectx, err := k8s.Client("") if err != nil { - return fmt.Errorf("unable to get kube client: %v", err) + return fmt.Errorf("unable to get kube client: %w", err) } err = kubectx.DeleteJob(job.Name) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("unable to previous job: %v", err) + return fmt.Errorf("unable to previous job: %w", err) } // Try to create the job using retry with backoff. // This handles scenarios where an existing job is still being terminated, etc. err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) if err != nil { - return fmt.Errorf("unable to create job: %v", err) + return fmt.Errorf("unable to create job: %w", err) } // TODO: Implement support for streaming job/pod log to stdout. diff --git a/internal/k8s/client.go b/internal/k8s/client.go index 4edce12..2ec98cf 100644 --- a/internal/k8s/client.go +++ b/internal/k8s/client.go @@ -36,12 +36,12 @@ func buildClientConfig(context string) (*rest.Config, string, error) { clientConfig := buildClientCmd(context) config, err := clientConfig.ClientConfig() if err != nil { - return nil, "", fmt.Errorf("could not get k8s config for context %q: %s", context, err) + return nil, "", fmt.Errorf("could not get k8s config for context %q: %w", context, err) } namespace, _, err := clientConfig.Namespace() if err != nil { - return nil, "", fmt.Errorf("could not get namespace for context %q: %s", context, err) + return nil, "", fmt.Errorf("could not get namespace for context %q: %w", context, err) } return config, namespace, nil diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 3c19316..cea5574 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -61,14 +61,14 @@ func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) getOptions := metav1.GetOptions{} job, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Get(name, getOptions) if err != nil { - return nil, fmt.Errorf("unable to get job: %v", err) + return nil, fmt.Errorf("unable to get job: %w", err) } selector := labels.Set(job.Spec.Selector.MatchLabels).String() listOptions := metav1.ListOptions{LabelSelector: selector} pods, err := kubectx.Client.CoreV1().Pods(kubectx.Namespace).List(listOptions) if err != nil { - return nil, fmt.Errorf("unable to get pods for job: %v", err) + return nil, fmt.Errorf("unable to get pods for job: %w", err) } if len(pods.Items) == 0 { From 884216f2d346f2196d62a3f90e2e1c7075404ca0 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 2 Dec 2019 12:23:09 +0100 Subject: [PATCH 25/58] Remove unused functions --- cmd/job.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/job.go b/cmd/job.go index 1798354..5590fe4 100644 --- a/cmd/job.go +++ b/cmd/job.go @@ -13,6 +13,3 @@ func init() { jobCmd.AddCommand(removeCmd) jobCmd.AddCommand(runCmd) } - -func int32Ptr(i int32) *int32 { return &i } -func int64Ptr(i int64) *int64 { return &i } From a5642d8f97605f25a734509297cbde79634cde63 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 2 Dec 2019 13:13:54 +0100 Subject: [PATCH 26/58] Use strconv.Iota instead of strconv.Format --- cmd/job_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/job_list.go b/cmd/job_list.go index 9a25c21..09c470f 100644 --- a/cmd/job_list.go +++ b/cmd/job_list.go @@ -73,7 +73,7 @@ func underlinedHeader() string { func row(job batchv1.Job) string { data := []string{ job.Name, - strconv.FormatInt(int64(job.Status.Succeeded), 10), + strconv.Itoa(int(job.Status.Succeeded)), } return strings.Join(data, "\t") + "\t" // TODO: Check if the trailing tab is required From 0ba1e0bb4684e1cd02940b59e34f5630d4ae7e95 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Mon, 2 Dec 2019 13:14:04 +0100 Subject: [PATCH 27/58] Remove whitespace --- internal/k8s/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/k8s/client.go b/internal/k8s/client.go index 2ec98cf..ccba2b5 100644 --- a/internal/k8s/client.go +++ b/internal/k8s/client.go @@ -53,7 +53,6 @@ func buildClientCmd(context string) clientcmd.ClientConfig { rules.DefaultClientConfig = &clientcmd.DefaultClientConfig overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} - if context != "" { overrides.CurrentContext = context } From 8c9e7755c448e0c19532acaaf26e132d315547c6 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 4 Dec 2019 16:51:11 +0100 Subject: [PATCH 28/58] Flatten command hierarchy --- cmd/{root.go => frink.go} | 7 +++++-- cmd/job.go | 15 --------------- cmd/{job_list.go => list.go} | 0 cmd/{job_logs.go => logs.go} | 0 cmd/{job_remove.go => remove.go} | 2 +- cmd/{job_run.go => run.go} | 2 +- 6 files changed, 7 insertions(+), 19 deletions(-) rename cmd/{root.go => frink.go} (81%) delete mode 100644 cmd/job.go rename cmd/{job_list.go => list.go} (100%) rename cmd/{job_logs.go => logs.go} (100%) rename cmd/{job_remove.go => remove.go} (93%) rename cmd/{job_run.go => run.go} (97%) diff --git a/cmd/root.go b/cmd/frink.go similarity index 81% rename from cmd/root.go rename to cmd/frink.go index d601200..2ad5865 100644 --- a/cmd/root.go +++ b/cmd/frink.go @@ -6,7 +6,7 @@ import ( var rootCmd = &cobra.Command{ Use: "frink", - Short: "Simplifies your Springfield workflows", + Short: "Frink simplifies your Springfield workflows", // Silence usage when an error occurs. SilenceUsage: true, @@ -18,7 +18,10 @@ func Execute() error { } func init() { - rootCmd.AddCommand(jobCmd) + rootCmd.AddCommand(listCmd) + rootCmd.AddCommand(logsCmd) + rootCmd.AddCommand(removeCmd) + rootCmd.AddCommand(runCmd) disableFlagsInUseLine(rootCmd) } diff --git a/cmd/job.go b/cmd/job.go deleted file mode 100644 index 5590fe4..0000000 --- a/cmd/job.go +++ /dev/null @@ -1,15 +0,0 @@ -package cmd - -import "github.com/spf13/cobra" - -var jobCmd = &cobra.Command{ - Use: "job", - Short: "Manage scheduled jobs", -} - -func init() { - jobCmd.AddCommand(listCmd) - jobCmd.AddCommand(logsCmd) - jobCmd.AddCommand(removeCmd) - jobCmd.AddCommand(runCmd) -} diff --git a/cmd/job_list.go b/cmd/list.go similarity index 100% rename from cmd/job_list.go rename to cmd/list.go diff --git a/cmd/job_logs.go b/cmd/logs.go similarity index 100% rename from cmd/job_logs.go rename to cmd/logs.go diff --git a/cmd/job_remove.go b/cmd/remove.go similarity index 93% rename from cmd/job_remove.go rename to cmd/remove.go index 0004f28..740a957 100644 --- a/cmd/job_remove.go +++ b/cmd/remove.go @@ -9,7 +9,7 @@ import ( var removeCmd = &cobra.Command{ Use: "rm [name]", - Short: "Remove a job", + Short: "Remove job from cluster", RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return fmt.Errorf("job name must be specified") diff --git a/cmd/job_run.go b/cmd/run.go similarity index 97% rename from cmd/job_run.go rename to cmd/run.go index ebc27be..9e1ae2d 100644 --- a/cmd/job_run.go +++ b/cmd/run.go @@ -11,7 +11,7 @@ import ( var runCmd = &cobra.Command{ Use: "run [file]", - Short: "Schedule a job", + Short: "Schedule a job on the cluster", RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return fmt.Errorf("job specification file must be specified") From 9fc480f7b5af0a131b247a0d178d38bbffb1c0aa Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 5 Dec 2019 10:40:17 +0100 Subject: [PATCH 29/58] Prepare KubeContext in root command pre-run routine --- cmd/frink.go | 18 ++++++++++++++++++ cmd/list.go | 6 ------ cmd/logs.go | 6 ------ cmd/remove.go | 10 +--------- cmd/run.go | 5 ----- 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/cmd/frink.go b/cmd/frink.go index 2ad5865..8f6c7b3 100644 --- a/cmd/frink.go +++ b/cmd/frink.go @@ -1,7 +1,15 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" + "github.com/uitml/frink/internal/k8s" +) + +// NOTE: Global package state; bad idea, but works for the time being. +var ( + kubectx *k8s.KubeContext ) var rootCmd = &cobra.Command{ @@ -10,6 +18,16 @@ var rootCmd = &cobra.Command{ // Silence usage when an error occurs. SilenceUsage: true, + + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + ctx, err := k8s.Client("") + if err != nil { + return fmt.Errorf("unable to get kube client: %w", err) + } + kubectx = ctx + + return nil + }, } // Execute executes the root command. diff --git a/cmd/list.go b/cmd/list.go index 09c470f..e6f8bd7 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -8,7 +8,6 @@ import ( "text/tabwriter" "github.com/spf13/cobra" - "github.com/uitml/frink/internal/k8s" batchv1 "k8s.io/api/batch/v1" ) @@ -28,11 +27,6 @@ var listCmd = &cobra.Command{ Use: "ls", Short: "List jobs", RunE: func(cmd *cobra.Command, args []string) error { - kubectx, err := k8s.Client("") - if err != nil { - return fmt.Errorf("could not get k8s client: %w", err) - } - jobs, err := kubectx.ListJobs() if err != nil { return fmt.Errorf("could not list jobs: %w", err) diff --git a/cmd/logs.go b/cmd/logs.go index 7dbf6a3..5bfb825 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -20,12 +20,6 @@ var logsCmd = &cobra.Command{ } name := args[0] - - kubectx, err := k8s.Client("") - if err != nil { - return fmt.Errorf("unable to get kube client: %w", err) - } - req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) if err != nil { return fmt.Errorf("unable to get logs: %w", err) diff --git a/cmd/remove.go b/cmd/remove.go index 740a957..c8c16c8 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/uitml/frink/internal/k8s" ) var removeCmd = &cobra.Command{ @@ -16,14 +15,7 @@ var removeCmd = &cobra.Command{ } name := args[0] - - kubectx, err := k8s.Client("") - if err != nil { - return fmt.Errorf("unable to get kube client: %w", err) - } - - err = kubectx.DeleteJob(name) - if err != nil { + if err := kubectx.DeleteJob(name); err != nil { return fmt.Errorf("unable to delete job: %w", err) } diff --git a/cmd/run.go b/cmd/run.go index 9e1ae2d..09854f2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -34,11 +34,6 @@ var runCmd = &cobra.Command{ // TODO: Reconsider this? Many reasons to avoid this; should be challenged. k8s.OverrideJobSpec(job) - kubectx, err := k8s.Client("") - if err != nil { - return fmt.Errorf("unable to get kube client: %w", err) - } - err = kubectx.DeleteJob(job.Name) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("unable to previous job: %w", err) From dd6e2eb20897f126e8cc13c0d251332eeff8c93c Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 5 Dec 2019 10:40:45 +0100 Subject: [PATCH 30/58] Shorten param name --- cmd/frink.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/frink.go b/cmd/frink.go index 8f6c7b3..04c38ac 100644 --- a/cmd/frink.go +++ b/cmd/frink.go @@ -45,16 +45,16 @@ func init() { } // visitAll visits the entire command tree rooted at cmd, invoking fn on each command. -func visitAll(cmd *cobra.Command, fn func(*cobra.Command)) { - fn(cmd) - for _, child := range cmd.Commands() { +func visitAll(c *cobra.Command, fn func(*cobra.Command)) { + fn(c) + for _, child := range c.Commands() { visitAll(child, fn) } } // disableFlagsInUseLine sets the disableFlagsInUseLine flag on the entire command tree rooted at cmd. -func disableFlagsInUseLine(cmd *cobra.Command) { - visitAll(cmd, func(c *cobra.Command) { +func disableFlagsInUseLine(c *cobra.Command) { + visitAll(c, func(c *cobra.Command) { c.DisableFlagsInUseLine = true }) } From 2ecbfc58b29fb77c8803b73785c0fadbba281c66 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 5 Dec 2019 16:33:56 +0100 Subject: [PATCH 31/58] Move common functions to new cli package --- cmd/frink.go | 24 +++++------------------- internal/cli/visitors.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 internal/cli/visitors.go diff --git a/cmd/frink.go b/cmd/frink.go index 04c38ac..7e98af7 100644 --- a/cmd/frink.go +++ b/cmd/frink.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/uitml/frink/internal/cli" "github.com/uitml/frink/internal/k8s" ) @@ -30,31 +31,16 @@ var rootCmd = &cobra.Command{ }, } -// Execute executes the root command. -func Execute() error { - return rootCmd.Execute() -} - func init() { rootCmd.AddCommand(listCmd) rootCmd.AddCommand(logsCmd) rootCmd.AddCommand(removeCmd) rootCmd.AddCommand(runCmd) - disableFlagsInUseLine(rootCmd) + cli.DisableFlagsInUseLine(rootCmd) } -// visitAll visits the entire command tree rooted at cmd, invoking fn on each command. -func visitAll(c *cobra.Command, fn func(*cobra.Command)) { - fn(c) - for _, child := range c.Commands() { - visitAll(child, fn) - } -} - -// disableFlagsInUseLine sets the disableFlagsInUseLine flag on the entire command tree rooted at cmd. -func disableFlagsInUseLine(c *cobra.Command) { - visitAll(c, func(c *cobra.Command) { - c.DisableFlagsInUseLine = true - }) +// Execute executes the root command. +func Execute() error { + return rootCmd.Execute() } diff --git a/internal/cli/visitors.go b/internal/cli/visitors.go new file mode 100644 index 0000000..56157f3 --- /dev/null +++ b/internal/cli/visitors.go @@ -0,0 +1,18 @@ +package cli + +import "github.com/spf13/cobra" + +// VisitAll visits the entire command tree rooted at cmd, invoking fn on each command visited. +func VisitAll(cmd *cobra.Command, fn func(*cobra.Command)) { + fn(cmd) + for _, child := range cmd.Commands() { + VisitAll(child, fn) + } +} + +// DisableFlagsInUseLine enables the DisableFlagsInUseLine flag on the entire command tree, rooted at cmd. +func DisableFlagsInUseLine(cmd *cobra.Command) { + VisitAll(cmd, func(c *cobra.Command) { + c.DisableFlagsInUseLine = true + }) +} From 8cd4df222afd5a0fdfe4c5441826dbbc89272033 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 29 Feb 2020 16:04:37 +0100 Subject: [PATCH 32/58] Add flags for overriding context and namespace --- cmd/frink.go | 11 +++++++++-- internal/k8s/client.go | 15 +++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/frink.go b/cmd/frink.go index 7e98af7..ef5d7bf 100644 --- a/cmd/frink.go +++ b/cmd/frink.go @@ -13,6 +13,11 @@ var ( kubectx *k8s.KubeContext ) +var ( + context string + namespace string +) + var rootCmd = &cobra.Command{ Use: "frink", Short: "Frink simplifies your Springfield workflows", @@ -21,12 +26,11 @@ var rootCmd = &cobra.Command{ SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - ctx, err := k8s.Client("") + ctx, err := k8s.Client(context, namespace) if err != nil { return fmt.Errorf("unable to get kube client: %w", err) } kubectx = ctx - return nil }, } @@ -37,6 +41,9 @@ func init() { rootCmd.AddCommand(removeCmd) rootCmd.AddCommand(runCmd) + rootCmd.PersistentFlags().StringVar(&context, "context", "", "name of the kubeconfig context to use") + rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "cluster namespace to use") + cli.DisableFlagsInUseLine(rootCmd) } diff --git a/internal/k8s/client.go b/internal/k8s/client.go index ccba2b5..6c399ef 100644 --- a/internal/k8s/client.go +++ b/internal/k8s/client.go @@ -15,8 +15,8 @@ type KubeContext struct { } // Client returns a k8s client and namespace for the specified context. -func Client(context string) (*KubeContext, error) { - config, namespace, err := buildClientConfig(context) +func Client(context, namespace string) (*KubeContext, error) { + config, namespace, err := buildClientConfig(context, namespace) if err != nil { return nil, err } @@ -32,14 +32,14 @@ func Client(context string) (*KubeContext, error) { } // buildClientConfig returns a complete client config and the namespace for the given context. -func buildClientConfig(context string) (*rest.Config, string, error) { - clientConfig := buildClientCmd(context) +func buildClientConfig(context, namespace string) (*rest.Config, string, error) { + clientConfig := buildClientCmd(context, namespace) config, err := clientConfig.ClientConfig() if err != nil { return nil, "", fmt.Errorf("could not get k8s config for context %q: %w", context, err) } - namespace, _, err := clientConfig.Namespace() + namespace, _, err = clientConfig.Namespace() if err != nil { return nil, "", fmt.Errorf("could not get namespace for context %q: %w", context, err) } @@ -48,7 +48,7 @@ func buildClientConfig(context string) (*rest.Config, string, error) { } // buildClientCmd returns an (incomplete) API server client config. -func buildClientCmd(context string) clientcmd.ClientConfig { +func buildClientCmd(context, namespace string) clientcmd.ClientConfig { rules := clientcmd.NewDefaultClientConfigLoadingRules() rules.DefaultClientConfig = &clientcmd.DefaultClientConfig @@ -56,6 +56,9 @@ func buildClientCmd(context string) clientcmd.ClientConfig { if context != "" { overrides.CurrentContext = context } + if namespace != "" { + overrides.Context.Namespace = namespace + } return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides) } From 63ec1cf7a1659fc9b3ed13974d4b0a6306416274 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 3 Mar 2020 11:31:07 +0100 Subject: [PATCH 33/58] Standardize cmd layout conventions --- cmd/frink.go | 41 +++++++++++++++++----------------- cmd/logs.go | 62 +++++++++++++++++++++++++++------------------------ cmd/remove.go | 31 ++++++++++++++------------ 3 files changed, 71 insertions(+), 63 deletions(-) diff --git a/cmd/frink.go b/cmd/frink.go index ef5d7bf..820c3c9 100644 --- a/cmd/frink.go +++ b/cmd/frink.go @@ -16,34 +16,35 @@ var ( var ( context string namespace string -) -var rootCmd = &cobra.Command{ - Use: "frink", - Short: "Frink simplifies your Springfield workflows", - - // Silence usage when an error occurs. - SilenceUsage: true, - - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - ctx, err := k8s.Client(context, namespace) - if err != nil { - return fmt.Errorf("unable to get kube client: %w", err) - } - kubectx = ctx - return nil - }, -} + rootCmd = &cobra.Command{ + Use: "frink", + Short: "Frink simplifies your Springfield workflows", + + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + ctx, err := k8s.Client(context, namespace) + if err != nil { + return fmt.Errorf("unable to get kube client: %w", err) + } + kubectx = ctx + return nil + }, + + // Silence usage when an error occurs. + SilenceUsage: true, + } +) func init() { + pflags := rootCmd.PersistentFlags() + pflags.StringVar(&context, "context", "", "name of the kubeconfig context to use") + pflags.StringVarP(&namespace, "namespace", "n", "", "cluster namespace to use") + rootCmd.AddCommand(listCmd) rootCmd.AddCommand(logsCmd) rootCmd.AddCommand(removeCmd) rootCmd.AddCommand(runCmd) - rootCmd.PersistentFlags().StringVar(&context, "context", "", "name of the kubeconfig context to use") - rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "cluster namespace to use") - cli.DisableFlagsInUseLine(rootCmd) } diff --git a/cmd/logs.go b/cmd/logs.go index 5bfb825..5603fe6 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -10,32 +10,36 @@ import ( "github.com/uitml/frink/internal/k8s" ) -var logsCmd = &cobra.Command{ - Use: "logs [name]", - Short: "Fetch the logs of a job", - Aliases: []string{"watch"}, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("job name must be specified") - } - - name := args[0] - req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) - if err != nil { - return fmt.Errorf("unable to get logs: %w", err) - } - - stream, err := req.Stream() - if err != nil { - return fmt.Errorf("unable to stream logs: %w", err) - } - defer stream.Close() - - reader := bufio.NewReader(stream) - if _, err := io.Copy(os.Stdout, reader); err != nil { - return fmt.Errorf("unable to write output: %w", err) - } - - return nil - }, -} +var ( + logsCmd = &cobra.Command{ + Use: "logs [name]", + Short: "Fetch the logs of a job", + + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job name must be specified") + } + + name := args[0] + req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) + if err != nil { + return fmt.Errorf("unable to get logs: %w", err) + } + + stream, err := req.Stream() + if err != nil { + return fmt.Errorf("unable to stream logs: %w", err) + } + defer stream.Close() + + reader := bufio.NewReader(stream) + if _, err := io.Copy(os.Stdout, reader); err != nil { + return fmt.Errorf("unable to write output: %w", err) + } + + return nil + }, + + Aliases: []string{"watch"}, + } +) diff --git a/cmd/remove.go b/cmd/remove.go index c8c16c8..0d06c08 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -6,19 +6,22 @@ import ( "github.com/spf13/cobra" ) -var removeCmd = &cobra.Command{ - Use: "rm [name]", - Short: "Remove job from cluster", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("job name must be specified") - } +var ( + removeCmd = &cobra.Command{ + Use: "rm [name]", + Short: "Remove job from cluster", - name := args[0] - if err := kubectx.DeleteJob(name); err != nil { - return fmt.Errorf("unable to delete job: %w", err) - } + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job name must be specified") + } - return nil - }, -} + name := args[0] + if err := kubectx.DeleteJob(name); err != nil { + return fmt.Errorf("unable to delete job: %w", err) + } + + return nil + }, + } +) From 96883a793d2ded716d42058bae284efdfba706ee Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 3 Mar 2020 11:31:51 +0100 Subject: [PATCH 34/58] Improve job list format --- cmd/list.go | 78 +++++++++++++++++++++++++++++++++-------------------- go.mod | 2 ++ go.sum | 4 +++ 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/cmd/list.go b/cmd/list.go index e6f8bd7..638f711 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -3,51 +3,55 @@ package cmd import ( "fmt" "os" - "strconv" "strings" "text/tabwriter" + "time" + "github.com/dustin/go-humanize" + "github.com/hako/durafmt" "github.com/spf13/cobra" batchv1 "k8s.io/api/batch/v1" ) -var ( - headerNames = []string{ - "NAME", - "SUCCEEDED", - } -) - -// Flags var ( showAll bool -) -var listCmd = &cobra.Command{ - Use: "ls", - Short: "List jobs", - RunE: func(cmd *cobra.Command, args []string) error { - jobs, err := kubectx.ListJobs() - if err != nil { - return fmt.Errorf("could not list jobs: %w", err) - } + listCmd = &cobra.Command{ + Use: "ls", + Short: "List jobs", - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - defer w.Flush() + RunE: func(cmd *cobra.Command, args []string) error { + jobs, err := kubectx.ListJobs() + if err != nil { + return fmt.Errorf("could not list jobs: %w", err) + } - fmt.Fprintln(w, header()) - for _, job := range jobs.Items { - fmt.Fprintln(w, row(job)) - } + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + defer w.Flush() - return nil - }, -} + fmt.Fprintln(w, header()) + for _, job := range jobs.Items { + fmt.Fprintln(w, row(job)) + } + + return nil + }, + } +) func init() { - listCmd.Flags().BoolVarP(&showAll, "all", "a", false, "show all jobs (defaults to only active)") + listCmd.Flags().BoolVarP(&showAll, "all", "a", false, "show all jobs; active and terminated") } +var ( + headerNames = []string{ + "NAME", + "STATUS", + "DURATION", + "AGE", + } +) + func header() string { return strings.Join(headerNames, "\t") + "\t" // TODO: Check if the trailing tab is required } @@ -65,9 +69,25 @@ func underlinedHeader() string { } func row(job batchv1.Job) string { + total := job.Status.Active + job.Status.Succeeded + job.Status.Failed + succeeded := job.Status.Succeeded + + var start time.Time + duration := time.Duration(0) + if job.Status.StartTime != nil { + start = job.Status.StartTime.Time + end := time.Now() + if job.Status.CompletionTime != nil { + end = job.Status.CompletionTime.Time + } + duration = end.Sub(start) + } + data := []string{ job.Name, - strconv.Itoa(int(job.Status.Succeeded)), + fmt.Sprintf("%d/%d", succeeded, total), + durafmt.Parse(duration).LimitFirstN(2).String(), + humanize.Time(start), } return strings.Join(data, "\t") + "\t" // TODO: Check if the trailing tab is required diff --git a/go.mod b/go.mod index fbc0db4..64fa736 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/uitml/frink go 1.13 require ( + github.com/dustin/go-humanize v1.0.0 + github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 github.com/spf13/cobra v0.0.5 k8s.io/api v0.0.0-20191016110246-af539daaa43a k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 diff --git a/go.sum b/go.sum index 5986b1a..24988e9 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -30,6 +32,8 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 h1:60gBOooTSmNtrqNaRvrDbi8VAne0REaek2agjnITKSw= +github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= From 98bf36f39e7bdd29954033e10bd1f23e95d25896 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 3 Mar 2020 11:33:54 +0100 Subject: [PATCH 35/58] Watch scheduled job automatically --- cmd/run.go | 107 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 35 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 09854f2..edee038 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,53 +1,90 @@ package cmd import ( + "bufio" + "errors" "fmt" + "io" "os" + "time" "github.com/spf13/cobra" "github.com/uitml/frink/internal/k8s" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/wait" ) -var runCmd = &cobra.Command{ - Use: "run [file]", - Short: "Schedule a job on the cluster", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("job specification file must be specified") - } - - file := args[0] - if _, err := os.Stat(file); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("specified file does not exist: %v", file) +var ( + runCmd = &cobra.Command{ + Use: "run [file]", + Short: "Schedule a job on the cluster", + + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job specification file must be specified") } - return fmt.Errorf("unable to access file: %w", err) - } + file := args[0] + if _, err := os.Stat(file); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("specified file does not exist: %v", file) + } - job, err := k8s.ParseJob(file) - if err != nil { - return fmt.Errorf("unable to parse job: %w", err) - } + return fmt.Errorf("unable to access file: %w", err) + } - // TODO: Reconsider this? Many reasons to avoid this; should be challenged. - k8s.OverrideJobSpec(job) + job, err := k8s.ParseJob(file) + if err != nil { + return fmt.Errorf("unable to parse job: %w", err) + } - err = kubectx.DeleteJob(job.Name) - if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("unable to previous job: %w", err) - } + // TODO: Reconsider this? Many reasons to avoid this; should be challenged. + k8s.OverrideJobSpec(job) - // Try to create the job using retry with backoff. - // This handles scenarios where an existing job is still being terminated, etc. - err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) - if err != nil { - return fmt.Errorf("unable to create job: %w", err) - } + err = kubectx.DeleteJob(job.Name) + if err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("unable to previous job: %w", err) + } - // TODO: Implement support for streaming job/pod log to stdout. + // Try to create the job using retry with backoff. + // This handles scenarios where an existing job is still being terminated, etc. + err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) + if err != nil { + return fmt.Errorf("unable to create job: %w", err) + } + + // TODO: Enable/disable using flag. + backoff := wait.Backoff{ + Steps: 30, + Duration: 1 * time.Second, + Factor: 1.0, + Jitter: 0.1, + } - return nil - }, -} + err = k8s.OnError(backoff, apierrors.IsBadRequest, func() error { + req, err := kubectx.GetJobLogs(job.Name, k8s.DefaultLogOptions) + if err != nil { + return errors.Unwrap(err) + } + + stream, err := req.Stream() + if err != nil { + return err + } + defer stream.Close() + + reader := bufio.NewReader(stream) + if _, err := io.Copy(os.Stdout, reader); err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + return nil + }, + } +) From 388e854deb3419f5ab7abf616b64394a61f0c4b4 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 3 Mar 2020 12:09:34 +0100 Subject: [PATCH 36/58] Update client-go to v0.17.0 --- go.mod | 6 +- go.sum | 183 +++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 147 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 64fa736..3a09226 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 github.com/spf13/cobra v0.0.5 - k8s.io/api v0.0.0-20191016110246-af539daaa43a - k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 - k8s.io/client-go v0.0.0-20191016110837-54936ba21026 + k8s.io/api v0.17.0 + k8s.io/apimachinery v0.17.0 + k8s.io/client-go v0.17.0 sigs.k8s.io/yaml v1.1.0 ) diff --git a/go.sum b/go.sum index 24988e9..343e13c 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,74 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM= -github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 h1:60gBOooTSmNtrqNaRvrDbi8VAne0REaek2agjnITKSw= github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -42,90 +76,161 @@ github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -k8s.io/api v0.0.0-20191016110246-af539daaa43a h1:IocS6+jQEuO8ZGQXhrD9BZ7Ze+Ly6FUKPlYs/m4I6xo= -k8s.io/api v0.0.0-20191016110246-af539daaa43a/go.mod h1:ceHJE/vDjU8jKnRV6Vqn/+vyZmC6NvOluInN+RhQkIs= -k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 h1:GYWOVyO+ZU+YK01nyPiAwB/fQrkxysXwkjbSpIIHdN4= -k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762/go.mod h1:Xc10RHc1U+F/e9GCloJ8QAeCGevSVP5xhOhqlE+e1kM= -k8s.io/client-go v0.0.0-20191016110837-54936ba21026 h1:HEJL/LGwm+NIepJIQML4AeU/BTn9vhDNnIE3qzpKL7g= -k8s.io/client-go v0.0.0-20191016110837-54936ba21026/go.mod h1:OjMHP19NDb8WkZ+pxaQjCDVs1IRkcpFn97Snv6K5w6A= -k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= -k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= -k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM= +k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo= +k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= +k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 2f93ce5e67345c4ea7f30518e5134b901b30480f Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Tue, 3 Mar 2020 12:10:00 +0100 Subject: [PATCH 37/58] Restructure package layout --- cmd/frink/main.go | 13 +++++++++++++ {cmd => internal/commands}/frink.go | 4 ++-- {cmd => internal/commands}/list.go | 2 +- {cmd => internal/commands}/logs.go | 9 +++++++-- {cmd => internal/commands}/remove.go | 2 +- {cmd => internal/commands}/run.go | 2 +- main.go | 13 ------------- 7 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 cmd/frink/main.go rename {cmd => internal/commands}/frink.go (94%) rename {cmd => internal/commands}/list.go (99%) rename {cmd => internal/commands}/logs.go (80%) rename {cmd => internal/commands}/remove.go (96%) rename {cmd => internal/commands}/run.go (99%) delete mode 100644 main.go diff --git a/cmd/frink/main.go b/cmd/frink/main.go new file mode 100644 index 0000000..19bac9f --- /dev/null +++ b/cmd/frink/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" + + "github.com/uitml/frink/internal/commands" +) + +func main() { + if err := commands.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/frink.go b/internal/commands/frink.go similarity index 94% rename from cmd/frink.go rename to internal/commands/frink.go index 820c3c9..405c93e 100644 --- a/cmd/frink.go +++ b/internal/commands/frink.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "fmt" @@ -30,7 +30,7 @@ var ( return nil }, - // Silence usage when an error occurs. + // Do not display usage when an error occurs. SilenceUsage: true, } ) diff --git a/cmd/list.go b/internal/commands/list.go similarity index 99% rename from cmd/list.go rename to internal/commands/list.go index 638f711..ef908ee 100644 --- a/cmd/list.go +++ b/internal/commands/list.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "fmt" diff --git a/cmd/logs.go b/internal/commands/logs.go similarity index 80% rename from cmd/logs.go rename to internal/commands/logs.go index 5603fe6..4e880f8 100644 --- a/cmd/logs.go +++ b/internal/commands/logs.go @@ -1,7 +1,8 @@ -package cmd +package commands import ( "bufio" + "errors" "fmt" "io" "os" @@ -23,7 +24,11 @@ var ( name := args[0] req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) if err != nil { - return fmt.Errorf("unable to get logs: %w", err) + return fmt.Errorf("unable to get logs: %w", errors.Unwrap(err)) + } + + if req == nil { + return fmt.Errorf("unable to get logs: request not returned (nil)") } stream, err := req.Stream() diff --git a/cmd/remove.go b/internal/commands/remove.go similarity index 96% rename from cmd/remove.go rename to internal/commands/remove.go index 0d06c08..d58817c 100644 --- a/cmd/remove.go +++ b/internal/commands/remove.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "fmt" diff --git a/cmd/run.go b/internal/commands/run.go similarity index 99% rename from cmd/run.go rename to internal/commands/run.go index edee038..68faa70 100644 --- a/cmd/run.go +++ b/internal/commands/run.go @@ -1,4 +1,4 @@ -package cmd +package commands import ( "bufio" diff --git a/main.go b/main.go deleted file mode 100644 index f2d4195..0000000 --- a/main.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "os" - - "github.com/uitml/frink/cmd" -) - -func main() { - if err := cmd.Execute(); err != nil { - os.Exit(1) - } -} From 19e6c3dd6de7cd548b0000ca493f697e17dde734 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 3 Jun 2020 08:55:23 +0200 Subject: [PATCH 38/58] Add CLI flag for following job logs --- internal/commands/run.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/commands/run.go b/internal/commands/run.go index 68faa70..46a8768 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -15,6 +15,8 @@ import ( ) var ( + follow bool + runCmd = &cobra.Command{ Use: "run [file]", Short: "Schedule a job on the cluster", @@ -53,7 +55,10 @@ var ( return fmt.Errorf("unable to create job: %w", err) } - // TODO: Enable/disable using flag. + if !follow { + return nil + } + backoff := wait.Backoff{ Steps: 30, Duration: 1 * time.Second, @@ -61,6 +66,7 @@ var ( Jitter: 0.1, } + // TODO: Ensure nil references are properly handled in this block. err = k8s.OnError(backoff, apierrors.IsBadRequest, func() error { req, err := kubectx.GetJobLogs(job.Name, k8s.DefaultLogOptions) if err != nil { @@ -88,3 +94,7 @@ var ( }, } ) + +func init() { + runCmd.Flags().BoolVarP(&follow, "follow", "f", false, "wait for job to start, then stream logs") +} From fb1d2cfe64d5db903be2916c7a13c5da9d723ec6 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 8 Jul 2020 22:58:29 +0200 Subject: [PATCH 39/58] Delete jobs using "foreground" policy --- internal/k8s/jobs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index cea5574..363b92a 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -40,7 +40,7 @@ func (kubectx *KubeContext) ListJobs() (*batchv1.JobList, error) { // DeleteJob deletes the job with the given name. func (kubectx *KubeContext) DeleteJob(name string) error { - deletePolicy := metav1.DeletePropagationBackground + deletePolicy := metav1.DeletePropagationForeground deleteOptions := &metav1.DeleteOptions{ GracePeriodSeconds: int64Ptr(0), PropagationPolicy: &deletePolicy, From 3e1d01bf1d4c33f283648c43fe44a221d9cfc9f9 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 8 Jul 2020 23:01:18 +0200 Subject: [PATCH 40/58] Increase wait timeout to 120 seconds --- internal/commands/run.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/commands/run.go b/internal/commands/run.go index 46a8768..ee3c50a 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -60,10 +60,9 @@ var ( } backoff := wait.Backoff{ - Steps: 30, Duration: 1 * time.Second, Factor: 1.0, - Jitter: 0.1, + Steps: 120, } // TODO: Ensure nil references are properly handled in this block. From 50021732e11969f2b8964f829db112f1d15ace62 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 8 Jul 2020 23:02:12 +0200 Subject: [PATCH 41/58] Attempt to handle nil reference crashes --- internal/commands/run.go | 5 +++++ internal/k8s/jobs.go | 1 + 2 files changed, 6 insertions(+) diff --git a/internal/commands/run.go b/internal/commands/run.go index ee3c50a..100354b 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -72,6 +72,11 @@ var ( return errors.Unwrap(err) } + if req == nil { + // TODO: Inform user we did not get any logs? + return fmt.Errorf("unable to get logs: request not returned (nil)") + } + stream, err := req.Stream() if err != nil { return err diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 363b92a..74a4d73 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -72,6 +72,7 @@ func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) } if len(pods.Items) == 0 { + // TODO: Treat this as an error scenario? return nil, nil } From eb154a8b4baa260c1b5303529971fefcf4c905f7 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 11 Jul 2020 13:32:31 +0200 Subject: [PATCH 42/58] Update dependencies --- go.mod | 14 +++--- go.sum | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 128 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 3a09226..2c5a6bd 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/uitml/frink -go 1.13 +go 1.14 require ( github.com/dustin/go-humanize v1.0.0 - github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 - github.com/spf13/cobra v0.0.5 - k8s.io/api v0.17.0 - k8s.io/apimachinery v0.17.0 - k8s.io/client-go v0.17.0 - sigs.k8s.io/yaml v1.1.0 + github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 + github.com/spf13/cobra v1.0.0 + k8s.io/api v0.17.8 + k8s.io/apimachinery v0.17.8 + k8s.io/client-go v0.17.8 + sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 343e13c..d948996 100644 --- a/go.sum +++ b/go.sum @@ -11,42 +11,63 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -57,16 +78,26 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4 h1:60gBOooTSmNtrqNaRvrDbi8VAne0REaek2agjnITKSw= -github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9 h1:IEhIezS5kcD4ZzOwVl8dAyJ9JCi4Xo6tg44Vj/z7UsI= +github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= +github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ= +github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -74,14 +105,22 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -90,6 +129,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= @@ -102,27 +142,45 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -130,38 +188,51 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -173,13 +244,17 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -188,21 +263,25 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= @@ -210,27 +289,54 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/api v0.17.8 h1:8JHlbqJ3A6sGhoacXfu/sASSD+HWWqVq67qt9lyB0kU= +k8s.io/api v0.17.8/go.mod h1:N++Llhs8kCixMUoCaXXAyMMPbo8dDVnh+IQ36xZV2/0= +k8s.io/api v0.18.5 h1:fKbCxr+U3fu7k6jB+QeYPD/c6xKYeSJ2KVWmyUypuWM= +k8s.io/api v0.18.5/go.mod h1:tN+e/2nbdGKOAH55NMV8oGrMG+3uRlA9GaRfvnCCSNk= k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.17.8 h1:zXvd8rYMAjRJXpILP9tdAiUnFIENM9EmHuE81apIoms= +k8s.io/apimachinery v0.17.8/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= +k8s.io/apimachinery v0.18.5 h1:Lh6tgsM9FMkC12K5T5QjRm7rDs6aQN5JHkA0JomULDM= +k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/client-go v0.17.8 h1:cuZSfjqVrNjoZ3wViQHljFPyWMOcgxUjjmQs5Rifbxk= +k8s.io/client-go v0.17.8/go.mod h1:SJsDS64AAtt9VZyeaQMb4Ck5etCitZ/FwajWdzua5eY= +k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= +k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19 h1:7Nu2dTj82c6IaWvL7hImJzcXoTPz1MsSCH7r+0m6rfo= +k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From 2e8dcf95b66c35b7edcce5884f166e8892b7afe4 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 11 Jul 2020 13:33:45 +0200 Subject: [PATCH 43/58] Tweak and refactor job listing --- internal/commands/list.go | 61 +++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/internal/commands/list.go b/internal/commands/list.go index ef908ee..2f731d8 100644 --- a/internal/commands/list.go +++ b/internal/commands/list.go @@ -47,31 +47,63 @@ var ( headerNames = []string{ "NAME", "STATUS", + "COMPLETIONS", "DURATION", "AGE", } ) func header() string { - return strings.Join(headerNames, "\t") + "\t" // TODO: Check if the trailing tab is required + return strings.Join(headerNames, "\t") + "\t" } -func underlinedHeader() string { - var rules []string - for _, name := range headerNames { - rules = append(rules, strings.Repeat("-", len(name))) +func row(job batchv1.Job) string { + columns := []string{ + job.Name, + status(job), + completions(job), + duration(job), + age(job), } - head := header() - rule := strings.Join(rules, "\t") + return strings.Join(columns, "\t") + "\t" +} + +func status(job batchv1.Job) string { + switch { + case job.Status.Active > 0: + return "Active" + case job.Spec.Completions == nil || *job.Spec.Completions == job.Status.Succeeded: + return "Succeeded" + case job.Status.Failed > 0: + return "Failed" + } - return fmt.Sprintf("%s\n%s\t", head, rule) + return "Stopped" } -func row(job batchv1.Job) string { - total := job.Status.Active + job.Status.Succeeded + job.Status.Failed +func completions(job batchv1.Job) string { succeeded := job.Status.Succeeded + total := succeeded + job.Status.Active + job.Status.Failed + + return fmt.Sprintf("%d/%d", succeeded, total) +} + +func duration(job batchv1.Job) string { + _, duration := timing(job) + humanized := durafmt.Parse(duration).LimitFirstN(2).String() + return humanized +} + +func age(job batchv1.Job) string { + start, _ := timing(job) + humanized := humanize.Time(start) + + return humanized +} + +func timing(job batchv1.Job) (time.Time, time.Duration) { var start time.Time duration := time.Duration(0) if job.Status.StartTime != nil { @@ -83,12 +115,5 @@ func row(job batchv1.Job) string { duration = end.Sub(start) } - data := []string{ - job.Name, - fmt.Sprintf("%d/%d", succeeded, total), - durafmt.Parse(duration).LimitFirstN(2).String(), - humanize.Time(start), - } - - return strings.Join(data, "\t") + "\t" // TODO: Check if the trailing tab is required + return start, duration } From 00d23ada265f017d6b477e42adbd500d527fc5f7 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 11 Jul 2020 13:44:13 +0200 Subject: [PATCH 44/58] Mark CLI arguments as required --- internal/commands/logs.go | 2 +- internal/commands/remove.go | 2 +- internal/commands/run.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/commands/logs.go b/internal/commands/logs.go index 4e880f8..6eb1a6a 100644 --- a/internal/commands/logs.go +++ b/internal/commands/logs.go @@ -13,7 +13,7 @@ import ( var ( logsCmd = &cobra.Command{ - Use: "logs [name]", + Use: "logs ", Short: "Fetch the logs of a job", RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/commands/remove.go b/internal/commands/remove.go index d58817c..92afcd6 100644 --- a/internal/commands/remove.go +++ b/internal/commands/remove.go @@ -8,7 +8,7 @@ import ( var ( removeCmd = &cobra.Command{ - Use: "rm [name]", + Use: "rm ", Short: "Remove job from cluster", RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/commands/run.go b/internal/commands/run.go index 100354b..faa0dd4 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -18,7 +18,7 @@ var ( follow bool runCmd = &cobra.Command{ - Use: "run [file]", + Use: "run ", Short: "Schedule a job on the cluster", RunE: func(cmd *cobra.Command, args []string) error { From cbf9e28ec441c52595e16abe61cab25302690efc Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 11 Jul 2020 16:07:54 +0200 Subject: [PATCH 45/58] Add package documentation --- internal/cli/visitors.go | 1 + internal/commands/frink.go | 1 + internal/k8s/client.go | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/internal/cli/visitors.go b/internal/cli/visitors.go index 56157f3..36c6360 100644 --- a/internal/cli/visitors.go +++ b/internal/cli/visitors.go @@ -1,3 +1,4 @@ +// Package cli contains various CLI-focused utility functions. package cli import "github.com/spf13/cobra" diff --git a/internal/commands/frink.go b/internal/commands/frink.go index 405c93e..de1460f 100644 --- a/internal/commands/frink.go +++ b/internal/commands/frink.go @@ -1,3 +1,4 @@ +// Package commands provides implementations of CLI commands. package commands import ( diff --git a/internal/k8s/client.go b/internal/k8s/client.go index 6c399ef..9f5fc36 100644 --- a/internal/k8s/client.go +++ b/internal/k8s/client.go @@ -1,3 +1,7 @@ +// Package k8s provides abstractions of some parts of the k8s API. +// +// The primary abstractions are built to simplify interactions with the batch API, +// specifically around managing jobs. package k8s import ( From 4b494d7931a6ffb1a012eecd9f3e3886a43e1f3a Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 11 Jul 2020 16:09:42 +0200 Subject: [PATCH 46/58] Move retry package from k8s package to util package --- internal/{k8s => util/retry}/retry.go | 60 +++++++-------------------- 1 file changed, 15 insertions(+), 45 deletions(-) rename internal/{k8s => util/retry}/retry.go (61%) diff --git a/internal/k8s/retry.go b/internal/util/retry/retry.go similarity index 61% rename from internal/k8s/retry.go rename to internal/util/retry/retry.go index fbeba5e..2fe97d6 100644 --- a/internal/k8s/retry.go +++ b/internal/util/retry/retry.go @@ -1,66 +1,38 @@ -package k8s - -// TODO: Remove this module once we upgrade to client-go v16. +// Package retry is a very minimal wrapper around the "k8s.io/client-go/util/retry" package. +package retry import ( - "time" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" + retry "k8s.io/client-go/util/retry" ) // DefaultRetry is the recommended retry for a conflict where multiple clients // are making changes to the same resource. -var DefaultRetry = wait.Backoff{ - Steps: 5, - Duration: 10 * time.Millisecond, - Factor: 1.0, - Jitter: 0.1, -} +var DefaultRetry = retry.DefaultRetry // DefaultBackoff is the recommended backoff for a conflict where a client // may be attempting to make an unrelated modification to a resource under // active management by one or more controllers. -var DefaultBackoff = wait.Backoff{ - Steps: 4, - Duration: 10 * time.Millisecond, - Factor: 5.0, - Jitter: 0.1, -} +var DefaultBackoff = retry.DefaultBackoff // OnError allows the caller to retry fn in case the error returned by fn is retriable // according to the provided function. backoff defines the maximum retries and the wait // interval between two retries. func OnError(backoff wait.Backoff, retriable func(error) bool, fn func() error) error { - var lastErr error - err := wait.ExponentialBackoff(backoff, func() (bool, error) { - err := fn() - switch { - case err == nil: - return true, nil - case retriable(err): - lastErr = err - return false, nil - default: - return false, err - } - }) - if err == wait.ErrWaitTimeout { - err = lastErr - } - return err + return retry.OnError(backoff, retriable, fn) } -// RetryOnConflict is used to make an update to a resource when you have to worry about +// OnConflict is used to make an update to a resource when you have to worry about // conflicts caused by other code making unrelated updates to the resource at the same // time. fn should fetch the resource to be modified, make appropriate changes to it, try // to update it, and return (unmodified) the error from the update function. On a -// successful update, RetryOnConflict will return nil. If the update function returns a -// "Conflict" error, RetryOnConflict will wait some amount of time as described by +// successful update, OnConflict will return nil. If the update function returns a +// "Conflict" error, OnConflict will wait some amount of time as described by // backoff, and then try again. On a non-"Conflict" error, or if it retries too many times -// and gives up, RetryOnConflict will return an error to the caller. +// and gives up, OnConflict will return an error to the caller. // -// err := retry.RetryOnConflict(retry.DefaultRetry, func() error { +// err := retry.OnConflict(retry.DefaultRetry, func() error { // // Fetch the resource here; you need to refetch it on every try, since // // if you got a conflict on the last update attempt then you need to get // // the current version before making your own changes. @@ -75,7 +47,7 @@ func OnError(backoff wait.Backoff, retriable func(error) bool, fn func() error) // // Try to update // _, err = c.Pods("mynamespace").UpdateStatus(pod) // // You have to return err itself here (not wrapped inside another error) -// // so that RetryOnConflict can identify it correctly. +// // so that OnConflict can identify it correctly. // return err // }) // if err != nil { @@ -84,14 +56,12 @@ func OnError(backoff wait.Backoff, retriable func(error) bool, fn func() error) // return err // } // ... -// -// TODO: Make Backoff an interface? -func RetryOnConflict(backoff wait.Backoff, fn func() error) error { +func OnConflict(backoff wait.Backoff, fn func() error) error { return OnError(backoff, errors.IsConflict, fn) } -// RetryOnExists can be used to recreate a resource immediately after the previous copy +// OnExists can be used to recreate a resource immediately after the previous copy // has been deleted, and thus might still be in a state of being removed. -func RetryOnExists(backoff wait.Backoff, fn func() error) error { +func OnExists(backoff wait.Backoff, fn func() error) error { return OnError(backoff, errors.IsAlreadyExists, fn) } From ec52edac752e4ce2bb7d2080ed2f12907cecf11f Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Sat, 11 Jul 2020 16:11:31 +0200 Subject: [PATCH 47/58] Attempt to implement more robust retry logic --- internal/commands/run.go | 43 ++++++++++++++++++++++++++++++---------- internal/k8s/jobs.go | 19 +++++++++++++++++- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/internal/commands/run.go b/internal/commands/run.go index faa0dd4..c318e1c 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -10,6 +10,8 @@ import ( "github.com/spf13/cobra" "github.com/uitml/frink/internal/k8s" + "github.com/uitml/frink/internal/util/retry" + batchv1 "k8s.io/api/batch/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" ) @@ -17,6 +19,12 @@ import ( var ( follow bool + backoff = wait.Backoff{ + Duration: 100 * time.Millisecond, + Factor: 1.0, + Steps: 1200, + } + runCmd = &cobra.Command{ Use: "run ", Short: "Schedule a job on the cluster", @@ -43,14 +51,20 @@ var ( // TODO: Reconsider this? Many reasons to avoid this; should be challenged. k8s.OverrideJobSpec(job) + fmt.Println("Deleting existing job...") err = kubectx.DeleteJob(job.Name) - if err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("unable to previous job: %w", err) + if err != nil { + return fmt.Errorf("unable to delete previous job: %w", err) + } + + if err := waitUntilDeleted(job); err != nil { + return err } - // Try to create the job using retry with backoff. + // Try to create the job using retry. // This handles scenarios where an existing job is still being terminated, etc. - err = k8s.RetryOnExists(k8s.DefaultBackoff, func() error { return kubectx.CreateJob(job) }) + fmt.Println("Creating job...") + err = retry.OnExists(backoff, func() error { return kubectx.CreateJob(job) }) if err != nil { return fmt.Errorf("unable to create job: %w", err) } @@ -59,14 +73,8 @@ var ( return nil } - backoff := wait.Backoff{ - Duration: 1 * time.Second, - Factor: 1.0, - Steps: 120, - } - // TODO: Ensure nil references are properly handled in this block. - err = k8s.OnError(backoff, apierrors.IsBadRequest, func() error { + err = retry.OnError(backoff, apierrors.IsBadRequest, func() error { req, err := kubectx.GetJobLogs(job.Name, k8s.DefaultLogOptions) if err != nil { return errors.Unwrap(err) @@ -99,6 +107,19 @@ var ( } ) +func waitUntilDeleted(job *batchv1.Job) error { + err := wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) { + oldJob, err := kubectx.GetJob(job.Name) + if err != nil { + return false, err + } + + return oldJob == nil, nil + }) + + return err +} + func init() { runCmd.Flags().BoolVarP(&follow, "follow", "f", false, "wait for job to start, then stream logs") } diff --git a/internal/k8s/jobs.go b/internal/k8s/jobs.go index 74a4d73..5f6dc21 100644 --- a/internal/k8s/jobs.go +++ b/internal/k8s/jobs.go @@ -7,6 +7,7 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" @@ -38,6 +39,17 @@ func (kubectx *KubeContext) ListJobs() (*batchv1.JobList, error) { return jobs, nil } +// GetJob returns the job with the given name. +func (kubectx *KubeContext) GetJob(name string) (*batchv1.Job, error) { + getOptions := metav1.GetOptions{} + job, err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Get(name, getOptions) + if err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + + return job, nil +} + // DeleteJob deletes the job with the given name. func (kubectx *KubeContext) DeleteJob(name string) error { deletePolicy := metav1.DeletePropagationForeground @@ -47,7 +59,11 @@ func (kubectx *KubeContext) DeleteJob(name string) error { } err := kubectx.Client.BatchV1().Jobs(kubectx.Namespace).Delete(name, deleteOptions) - return err + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + return nil } // CreateJob creates a job with the given specification. @@ -79,6 +95,7 @@ func (kubectx *KubeContext) GetJobLogs(name string, opts *corev1.PodLogOptions) // TODO: Add support for multiple pods? pod := pods.Items[0] req := kubectx.Client.CoreV1().Pods(kubectx.Namespace).GetLogs(pod.Name, opts) + return req, nil } From 157528853686f5c70cdaf60f27a3abe0ae7b20e0 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 15 Jul 2020 11:22:22 +0200 Subject: [PATCH 48/58] Attempt to simplify project layout --- cmd/frink/main.go | 13 --- {internal/commands => cmd}/list.go | 43 ++++---- cmd/logs.go | 48 +++++++++ cmd/remove.go | 25 +++++ internal/commands/frink.go => cmd/root.go | 50 ++++----- cmd/run.go | 124 +++++++++++++++++++++ internal/commands/logs.go | 50 --------- internal/commands/remove.go | 27 ----- internal/commands/run.go | 125 ---------------------- internal/{util => k8s}/retry/retry.go | 0 main.go | 7 ++ 11 files changed, 251 insertions(+), 261 deletions(-) delete mode 100644 cmd/frink/main.go rename {internal/commands => cmd}/list.go (74%) create mode 100644 cmd/logs.go create mode 100644 cmd/remove.go rename internal/commands/frink.go => cmd/root.go (52%) create mode 100644 cmd/run.go delete mode 100644 internal/commands/logs.go delete mode 100644 internal/commands/remove.go delete mode 100644 internal/commands/run.go rename internal/{util => k8s}/retry/retry.go (100%) create mode 100644 main.go diff --git a/cmd/frink/main.go b/cmd/frink/main.go deleted file mode 100644 index 19bac9f..0000000 --- a/cmd/frink/main.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "os" - - "github.com/uitml/frink/internal/commands" -) - -func main() { - if err := commands.Execute(); err != nil { - os.Exit(1) - } -} diff --git a/internal/commands/list.go b/cmd/list.go similarity index 74% rename from internal/commands/list.go rename to cmd/list.go index 2f731d8..59fcac7 100644 --- a/internal/commands/list.go +++ b/cmd/list.go @@ -1,4 +1,4 @@ -package commands +package cmd import ( "fmt" @@ -13,34 +13,33 @@ import ( batchv1 "k8s.io/api/batch/v1" ) -var ( - showAll bool +var showAll bool - listCmd = &cobra.Command{ - Use: "ls", - Short: "List jobs", +var listCmd = &cobra.Command{ + Use: "ls", + Short: "List jobs", - RunE: func(cmd *cobra.Command, args []string) error { - jobs, err := kubectx.ListJobs() - if err != nil { - return fmt.Errorf("could not list jobs: %w", err) - } + RunE: func(cmd *cobra.Command, args []string) error { + jobs, err := kubectx.ListJobs() + if err != nil { + return fmt.Errorf("could not list jobs: %w", err) + } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - defer w.Flush() + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + defer w.Flush() - fmt.Fprintln(w, header()) - for _, job := range jobs.Items { - fmt.Fprintln(w, row(job)) - } + fmt.Fprintln(w, header()) + for _, job := range jobs.Items { + fmt.Fprintln(w, row(job)) + } - return nil - }, - } -) + return nil + }, +} func init() { - listCmd.Flags().BoolVarP(&showAll, "all", "a", false, "show all jobs; active and terminated") + flags := listCmd.Flags() + flags.BoolVarP(&showAll, "all", "a", false, "show all jobs; active and terminated") } var ( diff --git a/cmd/logs.go b/cmd/logs.go new file mode 100644 index 0000000..d7141d2 --- /dev/null +++ b/cmd/logs.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "github.com/uitml/frink/internal/k8s" +) + +var logsCmd = &cobra.Command{ + Use: "logs ", + Short: "Fetch the logs of a job", + + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job name must be specified") + } + + name := args[0] + req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) + if err != nil { + return fmt.Errorf("unable to get logs: %w", errors.Unwrap(err)) + } + + if req == nil { + return fmt.Errorf("unable to get logs: request not returned (nil)") + } + + stream, err := req.Stream() + if err != nil { + return fmt.Errorf("unable to stream logs: %w", err) + } + defer stream.Close() + + reader := bufio.NewReader(stream) + if _, err := io.Copy(os.Stdout, reader); err != nil { + return fmt.Errorf("unable to write output: %w", err) + } + + return nil + }, + + Aliases: []string{"watch"}, +} diff --git a/cmd/remove.go b/cmd/remove.go new file mode 100644 index 0000000..7156e24 --- /dev/null +++ b/cmd/remove.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "rm ", + Short: "Remove job from cluster", + + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job name must be specified") + } + + name := args[0] + if err := kubectx.DeleteJob(name); err != nil { + return fmt.Errorf("unable to delete job: %w", err) + } + + return nil + }, +} diff --git a/internal/commands/frink.go b/cmd/root.go similarity index 52% rename from internal/commands/frink.go rename to cmd/root.go index de1460f..645c9c8 100644 --- a/internal/commands/frink.go +++ b/cmd/root.go @@ -1,8 +1,9 @@ -// Package commands provides implementations of CLI commands. -package commands +// Package cmd provides implementations of CLI commands. +package cmd import ( "fmt" + "os" "github.com/spf13/cobra" "github.com/uitml/frink/internal/cli" @@ -10,32 +11,30 @@ import ( ) // NOTE: Global package state; bad idea, but works for the time being. -var ( - kubectx *k8s.KubeContext -) +var kubectx *k8s.KubeContext var ( context string namespace string - - rootCmd = &cobra.Command{ - Use: "frink", - Short: "Frink simplifies your Springfield workflows", - - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - ctx, err := k8s.Client(context, namespace) - if err != nil { - return fmt.Errorf("unable to get kube client: %w", err) - } - kubectx = ctx - return nil - }, - - // Do not display usage when an error occurs. - SilenceUsage: true, - } ) +var rootCmd = &cobra.Command{ + Use: "frink", + Short: "Frink simplifies your Springfield workflows", + + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + ctx, err := k8s.Client(context, namespace) + if err != nil { + return fmt.Errorf("unable to get kube client: %w", err) + } + kubectx = ctx + return nil + }, + + // Do not display usage when an error occurs. + SilenceUsage: true, +} + func init() { pflags := rootCmd.PersistentFlags() pflags.StringVar(&context, "context", "", "name of the kubeconfig context to use") @@ -50,6 +49,9 @@ func init() { } // Execute executes the root command. -func Execute() error { - return rootCmd.Execute() +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } } diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000..8eabd96 --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,124 @@ +package cmd + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/uitml/frink/internal/k8s" + "github.com/uitml/frink/internal/k8s/retry" + batchv1 "k8s.io/api/batch/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/wait" +) + +var backoff = wait.Backoff{ + Duration: 100 * time.Millisecond, + Factor: 1.0, + Steps: 1200, +} + +var follow bool + +var runCmd = &cobra.Command{ + Use: "run ", + Short: "Schedule a job on the cluster", + + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("job specification file must be specified") + } + + file := args[0] + if _, err := os.Stat(file); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("specified file does not exist: %v", file) + } + + return fmt.Errorf("unable to access file: %w", err) + } + + job, err := k8s.ParseJob(file) + if err != nil { + return fmt.Errorf("unable to parse job: %w", err) + } + + // TODO: Reconsider this? Many reasons to avoid this; should be challenged. + k8s.OverrideJobSpec(job) + + fmt.Println("Deleting existing job...") + err = kubectx.DeleteJob(job.Name) + if err != nil { + return fmt.Errorf("unable to delete previous job: %w", err) + } + + if err := waitUntilDeleted(job); err != nil { + return err + } + + // Try to create the job using retry. + // This handles scenarios where an existing job is still being terminated, etc. + fmt.Println("Creating job...") + err = retry.OnExists(backoff, func() error { return kubectx.CreateJob(job) }) + if err != nil { + return fmt.Errorf("unable to create job: %w", err) + } + + if !follow { + return nil + } + + // TODO: Ensure nil references are properly handled in this block. + err = retry.OnError(backoff, apierrors.IsBadRequest, func() error { + req, err := kubectx.GetJobLogs(job.Name, k8s.DefaultLogOptions) + if err != nil { + return errors.Unwrap(err) + } + + if req == nil { + // TODO: Inform user we did not get any logs? + return fmt.Errorf("unable to get logs: request not returned (nil)") + } + + stream, err := req.Stream() + if err != nil { + return err + } + defer stream.Close() + + reader := bufio.NewReader(stream) + if _, err := io.Copy(os.Stdout, reader); err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + return nil + }, +} + +func init() { + flags := runCmd.Flags() + flags.BoolVarP(&follow, "follow", "f", false, "wait for job to start, then stream logs") +} + +func waitUntilDeleted(job *batchv1.Job) error { + err := wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) { + oldJob, err := kubectx.GetJob(job.Name) + if err != nil { + return false, err + } + + return oldJob == nil, nil + }) + + return err +} diff --git a/internal/commands/logs.go b/internal/commands/logs.go deleted file mode 100644 index 6eb1a6a..0000000 --- a/internal/commands/logs.go +++ /dev/null @@ -1,50 +0,0 @@ -package commands - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - - "github.com/spf13/cobra" - "github.com/uitml/frink/internal/k8s" -) - -var ( - logsCmd = &cobra.Command{ - Use: "logs ", - Short: "Fetch the logs of a job", - - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("job name must be specified") - } - - name := args[0] - req, err := kubectx.GetJobLogs(name, k8s.DefaultLogOptions) - if err != nil { - return fmt.Errorf("unable to get logs: %w", errors.Unwrap(err)) - } - - if req == nil { - return fmt.Errorf("unable to get logs: request not returned (nil)") - } - - stream, err := req.Stream() - if err != nil { - return fmt.Errorf("unable to stream logs: %w", err) - } - defer stream.Close() - - reader := bufio.NewReader(stream) - if _, err := io.Copy(os.Stdout, reader); err != nil { - return fmt.Errorf("unable to write output: %w", err) - } - - return nil - }, - - Aliases: []string{"watch"}, - } -) diff --git a/internal/commands/remove.go b/internal/commands/remove.go deleted file mode 100644 index 92afcd6..0000000 --- a/internal/commands/remove.go +++ /dev/null @@ -1,27 +0,0 @@ -package commands - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var ( - removeCmd = &cobra.Command{ - Use: "rm ", - Short: "Remove job from cluster", - - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("job name must be specified") - } - - name := args[0] - if err := kubectx.DeleteJob(name); err != nil { - return fmt.Errorf("unable to delete job: %w", err) - } - - return nil - }, - } -) diff --git a/internal/commands/run.go b/internal/commands/run.go deleted file mode 100644 index c318e1c..0000000 --- a/internal/commands/run.go +++ /dev/null @@ -1,125 +0,0 @@ -package commands - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "time" - - "github.com/spf13/cobra" - "github.com/uitml/frink/internal/k8s" - "github.com/uitml/frink/internal/util/retry" - batchv1 "k8s.io/api/batch/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/wait" -) - -var ( - follow bool - - backoff = wait.Backoff{ - Duration: 100 * time.Millisecond, - Factor: 1.0, - Steps: 1200, - } - - runCmd = &cobra.Command{ - Use: "run ", - Short: "Schedule a job on the cluster", - - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("job specification file must be specified") - } - - file := args[0] - if _, err := os.Stat(file); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("specified file does not exist: %v", file) - } - - return fmt.Errorf("unable to access file: %w", err) - } - - job, err := k8s.ParseJob(file) - if err != nil { - return fmt.Errorf("unable to parse job: %w", err) - } - - // TODO: Reconsider this? Many reasons to avoid this; should be challenged. - k8s.OverrideJobSpec(job) - - fmt.Println("Deleting existing job...") - err = kubectx.DeleteJob(job.Name) - if err != nil { - return fmt.Errorf("unable to delete previous job: %w", err) - } - - if err := waitUntilDeleted(job); err != nil { - return err - } - - // Try to create the job using retry. - // This handles scenarios where an existing job is still being terminated, etc. - fmt.Println("Creating job...") - err = retry.OnExists(backoff, func() error { return kubectx.CreateJob(job) }) - if err != nil { - return fmt.Errorf("unable to create job: %w", err) - } - - if !follow { - return nil - } - - // TODO: Ensure nil references are properly handled in this block. - err = retry.OnError(backoff, apierrors.IsBadRequest, func() error { - req, err := kubectx.GetJobLogs(job.Name, k8s.DefaultLogOptions) - if err != nil { - return errors.Unwrap(err) - } - - if req == nil { - // TODO: Inform user we did not get any logs? - return fmt.Errorf("unable to get logs: request not returned (nil)") - } - - stream, err := req.Stream() - if err != nil { - return err - } - defer stream.Close() - - reader := bufio.NewReader(stream) - if _, err := io.Copy(os.Stdout, reader); err != nil { - return err - } - - return nil - }) - if err != nil { - return err - } - - return nil - }, - } -) - -func waitUntilDeleted(job *batchv1.Job) error { - err := wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) { - oldJob, err := kubectx.GetJob(job.Name) - if err != nil { - return false, err - } - - return oldJob == nil, nil - }) - - return err -} - -func init() { - runCmd.Flags().BoolVarP(&follow, "follow", "f", false, "wait for job to start, then stream logs") -} diff --git a/internal/util/retry/retry.go b/internal/k8s/retry/retry.go similarity index 100% rename from internal/util/retry/retry.go rename to internal/k8s/retry/retry.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..7cf30c1 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/uitml/frink/cmd" + +func main() { + cmd.Execute() +} From 6c1e3a0746728995e83dae05ab668e7ad3185fd1 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 15 Jul 2020 14:39:10 +0200 Subject: [PATCH 49/58] Remove unnecessary print statement --- cmd/root.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 645c9c8..e13694a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,7 +51,6 @@ func init() { // Execute executes the root command. func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, err) os.Exit(1) } } From 699bc4860053b135d298c4c771e56fa277ccf22d Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Wed, 15 Jul 2020 18:23:10 +0200 Subject: [PATCH 50/58] Add TODO comment about truncation --- cmd/list.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/list.go b/cmd/list.go index 59fcac7..22f77a0 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -90,6 +90,7 @@ func completions(job batchv1.Job) string { func duration(job batchv1.Job) string { _, duration := timing(job) + // TODO: Implement "smart" truncation scheme. humanized := durafmt.Parse(duration).LimitFirstN(2).String() return humanized From a3e96ed3676a1dcf489189d25c68b684543b8d84 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 16 Jul 2020 15:21:33 +0200 Subject: [PATCH 51/58] Add config file support --- cmd/root.go | 17 ++++++++------- go.mod | 2 ++ go.sum | 47 ++---------------------------------------- internal/cli/config.go | 44 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 52 deletions(-) create mode 100644 internal/cli/config.go diff --git a/cmd/root.go b/cmd/root.go index e13694a..d689d23 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/uitml/frink/internal/cli" "github.com/uitml/frink/internal/k8s" ) @@ -13,20 +14,19 @@ import ( // NOTE: Global package state; bad idea, but works for the time being. var kubectx *k8s.KubeContext -var ( - context string - namespace string -) - var rootCmd = &cobra.Command{ Use: "frink", Short: "Frink simplifies your Springfield workflows", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + context := viper.GetString("context") + namespace := viper.GetString("namespace") + ctx, err := k8s.Client(context, namespace) if err != nil { return fmt.Errorf("unable to get kube client: %w", err) } + kubectx = ctx return nil }, @@ -36,9 +36,12 @@ var rootCmd = &cobra.Command{ } func init() { + cobra.OnInitialize(cli.InitConfig) + pflags := rootCmd.PersistentFlags() - pflags.StringVar(&context, "context", "", "name of the kubeconfig context to use") - pflags.StringVarP(&namespace, "namespace", "n", "", "cluster namespace to use") + pflags.String("context", "", "name of the kubeconfig context to use") + pflags.StringP("namespace", "n", "", "cluster namespace to use") + viper.BindPFlags(pflags) rootCmd.AddCommand(listCmd) rootCmd.AddCommand(logsCmd) diff --git a/go.mod b/go.mod index 2c5a6bd..7cd0817 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.14 require ( github.com/dustin/go-humanize v1.0.0 github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 + github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.0.0 + github.com/spf13/viper v1.4.0 k8s.io/api v0.17.8 k8s.io/apimachinery v0.17.8 k8s.io/client-go v0.17.8 diff --git a/go.sum b/go.sum index d948996..6f35abf 100644 --- a/go.sum +++ b/go.sum @@ -27,7 +27,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -37,7 +36,6 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -57,8 +55,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -75,27 +71,20 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9 h1:IEhIezS5kcD4ZzOwVl8dAyJ9JCi4Xo6tg44Vj/z7UsI= -github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ= github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -105,12 +94,9 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= -github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -137,7 +123,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -148,14 +133,12 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -188,10 +171,10 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -210,8 +193,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -253,8 +234,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -275,6 +254,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -294,48 +274,25 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM= -k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.17.8 h1:8JHlbqJ3A6sGhoacXfu/sASSD+HWWqVq67qt9lyB0kU= k8s.io/api v0.17.8/go.mod h1:N++Llhs8kCixMUoCaXXAyMMPbo8dDVnh+IQ36xZV2/0= -k8s.io/api v0.18.5 h1:fKbCxr+U3fu7k6jB+QeYPD/c6xKYeSJ2KVWmyUypuWM= -k8s.io/api v0.18.5/go.mod h1:tN+e/2nbdGKOAH55NMV8oGrMG+3uRlA9GaRfvnCCSNk= -k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo= -k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.8 h1:zXvd8rYMAjRJXpILP9tdAiUnFIENM9EmHuE81apIoms= k8s.io/apimachinery v0.17.8/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= -k8s.io/apimachinery v0.18.5 h1:Lh6tgsM9FMkC12K5T5QjRm7rDs6aQN5JHkA0JomULDM= -k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= -k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.17.8 h1:cuZSfjqVrNjoZ3wViQHljFPyWMOcgxUjjmQs5Rifbxk= k8s.io/client-go v0.17.8/go.mod h1:SJsDS64AAtt9VZyeaQMb4Ck5etCitZ/FwajWdzua5eY= -k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E= -k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= -k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19 h1:7Nu2dTj82c6IaWvL7hImJzcXoTPz1MsSCH7r+0m6rfo= -k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= diff --git a/internal/cli/config.go b/internal/cli/config.go new file mode 100644 index 0000000..baf1060 --- /dev/null +++ b/internal/cli/config.go @@ -0,0 +1,44 @@ +package cli + +import ( + "fmt" + "os" + "path" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +) + +func configPath() string { + configPath := os.Getenv("XDG_CONFIG_HOME") + if configPath == "" { + home, err := homedir.Dir() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + configPath = path.Join(home, ".config") + } + + frinkPath := path.Join(configPath, "frink") + return frinkPath +} + +// InitConfig wires up the default configuration backend. +func InitConfig() { + viper.AddConfigPath(configPath()) + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err != nil { + // TODO: Log ConfigFileNotFoundError when/if we implement logging? + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } else { + fmt.Println(viper.ConfigFileUsed()) + } +} From 7dcce4e9f76d84043d8403822329ed0fa1cf6a86 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 16 Jul 2020 15:30:24 +0200 Subject: [PATCH 52/58] Remove leftover debug output --- internal/cli/config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/cli/config.go b/internal/cli/config.go index baf1060..54281ce 100644 --- a/internal/cli/config.go +++ b/internal/cli/config.go @@ -38,7 +38,5 @@ func InitConfig() { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - } else { - fmt.Println(viper.ConfigFileUsed()) } } From d6dea9458c820d93dee18e0c58cbd8d36a10bb23 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 16 Jul 2020 15:32:51 +0200 Subject: [PATCH 53/58] Move AddCommand calls to each subcommand --- cmd/list.go | 2 ++ cmd/logs.go | 4 ++++ cmd/remove.go | 4 ++++ cmd/root.go | 5 ----- cmd/run.go | 2 ++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd/list.go b/cmd/list.go index 22f77a0..2488777 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -38,6 +38,8 @@ var listCmd = &cobra.Command{ } func init() { + rootCmd.AddCommand(listCmd) + flags := listCmd.Flags() flags.BoolVarP(&showAll, "all", "a", false, "show all jobs; active and terminated") } diff --git a/cmd/logs.go b/cmd/logs.go index d7141d2..16d32d8 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -46,3 +46,7 @@ var logsCmd = &cobra.Command{ Aliases: []string{"watch"}, } + +func init() { + rootCmd.AddCommand(logsCmd) +} diff --git a/cmd/remove.go b/cmd/remove.go index 7156e24..4b14af9 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -23,3 +23,7 @@ var removeCmd = &cobra.Command{ return nil }, } + +func init() { + rootCmd.AddCommand(removeCmd) +} diff --git a/cmd/root.go b/cmd/root.go index d689d23..a264b0d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -43,11 +43,6 @@ func init() { pflags.StringP("namespace", "n", "", "cluster namespace to use") viper.BindPFlags(pflags) - rootCmd.AddCommand(listCmd) - rootCmd.AddCommand(logsCmd) - rootCmd.AddCommand(removeCmd) - rootCmd.AddCommand(runCmd) - cli.DisableFlagsInUseLine(rootCmd) } diff --git a/cmd/run.go b/cmd/run.go index 8eabd96..0b02c9b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -106,6 +106,8 @@ var runCmd = &cobra.Command{ } func init() { + rootCmd.AddCommand(runCmd) + flags := runCmd.Flags() flags.BoolVarP(&follow, "follow", "f", false, "wait for job to start, then stream logs") } From 782aa64379b5a9255f906112de1e350f623c8ec7 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 16 Jul 2020 15:38:03 +0200 Subject: [PATCH 54/58] Use prefix on env variables --- internal/cli/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/cli/config.go b/internal/cli/config.go index 54281ce..7aec690 100644 --- a/internal/cli/config.go +++ b/internal/cli/config.go @@ -30,6 +30,8 @@ func InitConfig() { viper.AddConfigPath(configPath()) viper.SetConfigName("config") viper.SetConfigType("yaml") + + viper.SetEnvPrefix("frink") viper.AutomaticEnv() if err := viper.ReadInConfig(); err != nil { From 1c40dcf4d151cfb7c435dcc3671c24edb1f2e20f Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Thu, 16 Jul 2020 16:26:25 +0200 Subject: [PATCH 55/58] Add initial GoReleaser config --- .gitignore | 1 + .goreleaser.yaml | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 .goreleaser.yaml diff --git a/.gitignore b/.gitignore index 8fdbb7b..c076693 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ .envrc # Project. +/dist/ /out/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..52b8204 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,28 @@ +before: + hooks: + - go mod download +builds: +- env: + - CGO_ENABLED=0 + goos: + - darwin + - linux + goarch: + - amd64 +archives: +- name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" + replacements: + darwin: Darwin + linux: Linux + amd64: x86_64 +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + skip: true + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' From 3280dd5237772b48a694fbe635d02f3cca83b104 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 17 Jul 2020 14:20:24 +0200 Subject: [PATCH 56/58] Add GoReleaser action --- .github/workflows/release.yaml | 28 ++++++++++++++++++++++++++++ .goreleaser.yaml | 27 +++++++++++++++++++-------- main.go | 10 +++++++++- 3 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..3ae52b1 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + tags: + - "v*" + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch_depth: 0 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.14 + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 52b8204..3d15a9b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,6 +1,13 @@ +project_name: frink + before: hooks: - - go mod download + - go mod tidy + - go mod download + +release: + prerelease: auto + builds: - env: - CGO_ENABLED=0 @@ -9,20 +16,24 @@ builds: - linux goarch: - amd64 + archives: -- name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" +- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}' replacements: darwin: Darwin linux: Linux amd64: x86_64 + checksum: name_template: 'checksums.txt' + snapshot: - name_template: "{{ .Tag }}-next" + name_template: '{{ replace .Tag "v" "" }}-{{ .ShortCommit }}' + changelog: skip: true - sort: asc - filters: - exclude: - - '^docs:' - - '^test:' + # sort: asc + # filters: + # exclude: + # - '^docs:' + # - '^test:' diff --git a/main.go b/main.go index 7cf30c1..2ef5e21 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,14 @@ package main -import "github.com/uitml/frink/cmd" +import ( + "github.com/uitml/frink/cmd" +) + +var ( + version string + commit string + date string +) func main() { cmd.Execute() From 8f07136a356daa061cc12c77b869dfb152aac778 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 17 Jul 2020 17:57:47 +0200 Subject: [PATCH 57/58] Change release format to binary --- .goreleaser.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 3d15a9b..3b41945 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -18,9 +18,10 @@ builds: - amd64 archives: -- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}' +- format: binary + name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}' replacements: - darwin: Darwin + darwin: macOS linux: Linux amd64: x86_64 @@ -32,8 +33,3 @@ snapshot: changelog: skip: true - # sort: asc - # filters: - # exclude: - # - '^docs:' - # - '^test:' From 71497b9c57054c769a576fb0a09f2f955eae2a04 Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 17 Jul 2020 18:01:24 +0200 Subject: [PATCH 58/58] Use legacy branch in install script Temporary change until automated releases using GoReleaser are working. --- docs/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.sh b/docs/install.sh index 00a6cb3..341489e 100644 --- a/docs/install.sh +++ b/docs/install.sh @@ -3,7 +3,7 @@ set -eu umask 077 -api_url="https://raw.githubusercontent.com/uitml/frink/master" +api_url="https://raw.githubusercontent.com/uitml/frink/legacy" mkdir -p /tmp/frink && cd /tmp/frink curl -LO "${api_url}/bin/kubectl-job-kill"