Skip to content

Commit

Permalink
Merge pull request #110 from UKHomeOffice/replace-flag-fixes
Browse files Browse the repository at this point in the history
kd replace command updates
  • Loading branch information
KashifSaadat committed Aug 8, 2018
1 parent afe813b commit d7e98f0
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 46 deletions.
44 changes: 31 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
77 changes: 44 additions & 33 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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") {
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit d7e98f0

Please sign in to comment.