From d870eba034312112157bbfcb015471b1285dfba2 Mon Sep 17 00:00:00 2001 From: Kashif Saadat Date: Tue, 7 Aug 2018 23:08:04 +0100 Subject: [PATCH] - Update the replace flag, use create when resource does not exist - Do not pass extra kubectl args when running kubectl get commands --- README.md | 44 +++++++++++++++++++++---------- main.go | 77 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 75 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 7d97f47..a4635d2 100644 --- a/README.md +++ b/README.md @@ -85,27 +85,45 @@ You can fail an ongoing deployment if there's been a new deployment by adding `- kd will use the `apply` verb to create / update resources which is [appropriate in most cases](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#in-place-updates-of-resources). -The flag `--replace` can be used to override this behaviour can be useful in -some very specific scenarios but the result is a [disruptive update](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#disruptive-updates) -which should not be the default. - -To have the desired affect when updating objects, `--force` is used to enable -creation of objects created with replace. **NOTE** history of an object is lost -with `--force`. +The flag `--replace` can be used to override this behaviour and may be useful in +some very specific scenarios however the result can be a [disruptive update](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#disruptive-updates) +if extra kubectl flags are applied (such as `-- --force`). Additionally, the last-applied-configuration is not saved when using this flag. #### Cronjobs -When a cronjob object is created and only updated, any old jobs will continue -and some fields are imutable so use of the force option may be required. +When a cronjob object is created and only updated, any old jobs will continue and some +fields are immutable, so use of the replace command and force option may be required. -E.g. to update a large cron job use `kd --replace -f cronjob.yml`. +```bash +# The cronjob resource does not yet exist, and so a create action is performed +$ kd --replace -f cronjob.yml -- --force +[INFO] 2018/08/07 22:54:00 main.go:724: resource does not exist, dropping --force flag for create action +[INFO] 2018/08/07 22:54:00 main.go:466: deploying cronjob/etcd-backup +[INFO] 2018/08/07 22:54:00 main.go:473: cronjob "etcd-backup" created + +# The resource now exists, so a kubectl replace is performed with the extra --force arg +$ kd --replace -f cronjob.yml -- --force +[INFO] 2018/08/07 22:54:02 main.go:466: deploying cronjob/etcd-backup +[INFO] 2018/08/07 22:54:03 main.go:473: cronjob "etcd-backup" deleted +cronjob "etcd-backup" replaced +``` -#### Large Objects e.g. Configmaps +#### Large Objects As an apply uses 'patch' internally, there is a limit to the size of objects -that can be updated this way. +that can be updated this way and you may receive an error such as: `metadata.annotations: Too long: must have at most 262144 characters` -E.g. to update a large config map use `kd --replace -f myconfigmap.yml`. +Below is an example for updating a large ConfigMap: +```bash +# 859KB ConfigMap resource +$ kd --replace -f configmap.yaml +[INFO] 2018/08/07 23:02:39 main.go:466: deploying configmap/bundle +[INFO] 2018/08/07 23:02:40 main.go:473: configmap "bundle" created + +$ kd --replace -f configmap.yaml +[INFO] 2018/08/07 23:02:41 main.go:466: deploying configmap/bundle +[INFO] 2018/08/07 23:02:42 main.go:473: configmap "bundle" replaced +``` ### Run command diff --git a/main.go b/main.go index b88feea..078c706 100644 --- a/main.go +++ b/main.go @@ -256,7 +256,7 @@ func runKubectl(c *cli.Context) error { } // Allow the lib to render args and then create array - cmd, err := newKubeCmdSub(c.Parent(), c.Args(), true) + cmd, err := newKubeCmdSub(c.Parent(), c.Args(), true, true) if err != nil { return err } @@ -404,26 +404,34 @@ func splitYamlDocs(data string) []string { func deploy(c *cli.Context, r *ObjectResource) error { exists := false - if r.CreateOnly { + if r.CreateOnly || c.Bool(FlagReplace) { var err error exists, err = checkResourceExist(c, r) if err != nil { return fmt.Errorf( "problem checking if resource %s/%s exists", r.Kind, r.Name) } - if exists { - log.Printf( - "skipping deploy for resource (%s/%s) marked as create only.", - r.Kind, - r.Name) - return nil + + if r.CreateOnly { + if exists { + log.Printf( + "skipping deploy for resource (%s/%s) marked as create only.", + r.Kind, + r.Name) + return nil + } } } name := r.Name command := "apply" + if c.Bool(FlagReplace) { - command = "replace" + if exists { + command = "replace" + } else { + command = "create" + } } if r.GenerateName != "" { @@ -433,7 +441,7 @@ func deploy(c *cli.Context, r *ObjectResource) error { logDebug.Printf("about to deploy resource %s/%s (from file:%q)", r.Kind, name, r.FileName) args := []string{command, "-f", "-"} - cmd, err := newKubeCmd(c, args) + cmd, err := newKubeCmd(c, args, true) if err != nil { return err } @@ -594,7 +602,7 @@ func watchResource(c *cli.Context, r *ObjectResource) error { func updateResourceStatus(c *cli.Context, r *ObjectResource) error { args := []string{"get", r.Kind + "/" + r.Name, "-o", "yaml"} - cmd, err := newKubeCmd(c, args) + cmd, err := newKubeCmd(c, args, false) if err != nil { return err } @@ -615,7 +623,8 @@ func updateResourceStatus(c *cli.Context, r *ObjectResource) error { func checkResourceExist(c *cli.Context, r *ObjectResource) (bool, error) { args := []string{"get", r.Kind + "/" + r.Name, "-o", "custom-columns=:.metadata.name", "--no-headers"} - cmd, err := newKubeCmd(c, args) + + cmd, err := newKubeCmd(c, args, false) if err != nil { return false, err } @@ -636,16 +645,16 @@ func checkResourceExist(c *cli.Context, r *ObjectResource) (bool, error) { } if strings.TrimSpace(string(data[:])) == r.Name { return true, nil - } else { - return false, nil } + + return false, nil } -func newKubeCmd(c *cli.Context, args []string) (*exec.Cmd, error) { - return newKubeCmdSub(c, args, false) +func newKubeCmd(c *cli.Context, args []string, addExtraFlags bool) (*exec.Cmd, error) { + return newKubeCmdSub(c, args, false, addExtraFlags) } -func newKubeCmdSub(c *cli.Context, args []string, subCommand bool) (*exec.Cmd, error) { +func newKubeCmdSub(c *cli.Context, args []string, subCommand bool, addExtraFlags bool) (*exec.Cmd, error) { kube := "kubectl" if c.IsSet("namespace") { @@ -684,25 +693,27 @@ func newKubeCmdSub(c *cli.Context, args []string, subCommand bool) (*exec.Cmd, e args = append([]string{"--server=" + c.String("kube-server")}, args...) } - flags, err := extraFlags(c, subCommand) - if err != nil { - return nil, err - } - // If we've been asked to replace and we haven't provided the '-- --force' - // extra args, add it here (a update will fail if the object doesn't exist) - if c.Bool(FlagReplace) { - forceSet := false - for _, flag := range flags { - if strings.Contains(flag, "--force") { - forceSet = true - break - } + if addExtraFlags { + flags, err := extraFlags(c, subCommand) + if err != nil { + return nil, err } - if !forceSet { - flags = append(flags, "--force") + + // If the --replace flag is given but the resource doesn't yet exist, the create + // command is used. Providing the --force flag is invalid here and so should be + // removed if it is present. + if c.Bool(FlagReplace) && args[0] == "create" { + for i, flag := range flags { + if strings.Contains(flag, "--force") { + logInfo.Printf("resource does not exist, dropping --force flag for create action") + flags = append(flags[:i], flags[i+1:]...) + break + } + } } + + args = append(args, flags...) } - args = append(args, flags...) return exec.Command(kube, args...), nil }