Skip to content

Commit

Permalink
Enable inheritance of labels/annotations (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
stepro authored Jun 14, 2020
1 parent f2cbd40 commit fd96729
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 44 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,20 @@ These flags customize the pod and container that runs the command.
Flag | Default | Description
---- | ------- | -----------
`-c, --inherit` | `<none>` | inherit an existing configuration
`--label` | `[]` | set pod labels in the form `name=value`
`--annotate` | `[]` | set pod annotations in the form `name=value`
`-L, --inherit-labels` | `false` | inherit pod labels
`-A, --inherit-annotations` | `false` | inherit pod annotations
`--label` | `[]` | inherit, set or remove pod labels in the form `name[=[value]]`
`--annotate` | `[]` | inherit, set or remove pod annotations in the form `name[=[value]]`
`--no-lifecycle` | `false` | do not inherit lifecycle configuration
`--no-probes` | `false` | do not inherit probes configuration
`-e, --env` | `[]` | set container environment variables in the form `name=value`
`-R, --replace` | `false` | overlay inherited configuration's workload

The `-c, --inherit` flag inherits an existing configuration from a container specification identified in the form `[kind/]name[:container]`, where `kind` is a Kubernetes workload kind (`cronjob`, `daemonset`, `deployment`, `job`, `pod`, `replicaset`, `replicationcontroller` or `statefulset`) or `service` (default is `pod`). If the `kind` is not `pod`, the pod spec is based on the template in the outer workload spec, except in the case of `service`, when it is based on the workload that originally generated the first pod selected by the service. If `container` is not specified, the first container in the pod spec is selected. Init containers are not supported.

Note that even when inheriting an existing configuration, pod labels and annotations are *not* inherited to prevent the Kubernetes cluster from misunderstanding the role of the pod (for instance, automatically being added as an instance behind a service). The `--label` and `--annotate` flags can be used to re-add any labels and annotations that must be included for the pod to function properly.
By default, when inheriting an existing configuration, pod labels and annotations are *not* inherited to prevent the Kubernetes cluster from misunderstanding the role of the pod (for instance, automatically being added as an instance behind a service). The `--inherit-labels` and/or `--inherit-annotations` flags can be used to override this behavior.

Whether or not labels or annotations are inherited, the final set of label or annotation entries can be customized using the `--label` and `--annotate` flags. If a value is simply in the form `name`, then its entry is inherited. If a value is in the form `name=value`, it adds or overrides any existing entry. Lastly, if a value is in the form `name=`, it removes an entry that may otherwise be inherited.

When inheriting an existing configuration, there are cases when the existing pod lifecycle and probe configuration are not implemented, would cause problems, or are entirely irrelevant for the scenario. The `--no-lifecyle` and `--no-probes` flags can be used to ensure these properties are not inherited.

Expand Down
44 changes: 27 additions & 17 deletions cli/kdo/kdo.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ var flags struct {
target string
}
config struct {
inherit string
labels []string
annotations []string
noLifecycle bool
noProbes bool
env []string
replace bool
inherit string
inheritLabels bool
labels []string
inheritAnnotations bool
annotations []string
noLifecycle bool
noProbes bool
env []string
replace bool
}
session struct {
sync []string
Expand Down Expand Up @@ -163,10 +165,14 @@ func init() {
// Configuration flags
cmd.Flags().StringVarP(&flags.config.inherit,
"inherit", "c", "", "inherit an existing configuration")
cmd.Flags().BoolVarP(&flags.config.inheritLabels,
"inherit-labels", "L", false, "inherit pod labels")
cmd.Flags().BoolVarP(&flags.config.inheritAnnotations,
"inherit-annotations", "A", false, "inherit pod annotations")
cmd.Flags().StringArrayVar(&flags.config.labels,
"label", nil, "set pod labels (never inherited)")
"label", nil, "inherit, set or remove pod labels")
cmd.Flags().StringArrayVar(&flags.config.annotations,
"annotate", nil, "set pod annotations (never inherited)")
"annotate", nil, "inherit, set or remove pod annotations")
cmd.Flags().BoolVar(&flags.config.noLifecycle,
"no-lifecycle", false, "do not inherit lifecycle configuration")
cmd.Flags().BoolVar(&flags.config.noProbes,
Expand Down Expand Up @@ -448,14 +454,18 @@ func run(cmd *cobra.Command, args []string) error {
}

p, err := pod.Apply(k, hash, build, &pod.Settings{
Inherit: flags.config.inherit,
Image: image,
Env: flags.config.env,
Replace: flags.config.replace,
Listen: len(flags.session.listen) > 0,
Stdin: flags.command.stdin,
TTY: flags.command.tty,
Command: command,
Inherit: flags.config.inherit,
InheritLabels: flags.config.inheritLabels,
InheritAnnotations: flags.config.inheritAnnotations,
Labels: flags.config.labels,
Annotations: flags.config.annotations,
Image: image,
Env: flags.config.env,
Replace: flags.config.replace,
Listen: len(flags.session.listen) > 0,
Stdin: flags.command.stdin,
TTY: flags.command.tty,
Command: command,
}, out)
if err != nil {
return err
Expand Down
86 changes: 62 additions & 24 deletions pkg/pod/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ func Name(hash string) string {

// Settings represents settings for a pod
type Settings struct {
Inherit string
Labels []string
Annotations []string
NoLifecycle bool
NoProbes bool
Image string
Env []string
Replace bool
Listen bool
Stdin bool
TTY bool
Command []string
Inherit string
InheritLabels bool
InheritAnnotations bool
Labels []string
Annotations []string
NoLifecycle bool
NoProbes bool
Image string
Env []string
Replace bool
Listen bool
Stdin bool
TTY bool
Command []string
}

func parseInherit(inherit string) (kind, name, container string, err error) {
Expand Down Expand Up @@ -78,33 +80,33 @@ func parseInherit(inherit string) (kind, name, container string, err error) {
return
}

func baseline(k *kubectl.CLI, kind, name, container string) (object, int, string, error) {
func baseline(k *kubectl.CLI, kind, name string, labels, annotations bool, container string) (object, int, string, object, error) {
var manifest object
manifest = map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
}

if kind == "" {
return manifest, 0, "kdo", nil
return manifest, 0, "kdo", nil, nil
}

if kind == "service" {
pods, err := k.Lines("get", "endpoints", name, "-o", `go-template={{range .subsets}}{{range .addresses}}{{if .targetRef}}{{if eq .targetRef.kind "Pod"}}{{.targetRef.name}}`+"\n"+`{{end}}{{end}}{{end}}{{end}}`)
if err != nil {
return nil, 0, "", err
return nil, 0, "", nil, err
} else if len(pods) == 0 {
return nil, 0, "", fmt.Errorf(`Unable to determine pod from service "%s"`, name)
return nil, 0, "", nil, fmt.Errorf(`Unable to determine pod from service "%s"`, name)
}
kind = "pod"
name = pods[0]
}

var source object
if s, err := k.String("get", kind, name, "-o", "json"); err != nil {
return nil, 0, "", err
return nil, 0, "", nil, err
} else if err = json.Unmarshal([]byte(s), &source); err != nil {
return nil, 0, "", err
return nil, 0, "", nil, err
}

var replicas int
Expand All @@ -119,6 +121,15 @@ func baseline(k *kubectl.CLI, kind, name, container string) (object, int, string
source = source.obj("spec").obj("template")
}

manifest.with("metadata", func(metadata object) {
if labels {
metadata.set(source.obj("metadata"), "labels")
}
if annotations {
metadata.set(source.obj("metadata"), "annotations")
}
})

manifest.with("spec", func(spec object) {
spec.set(source.obj("spec"),
"activeDeadlineSeconds",
Expand Down Expand Up @@ -162,7 +173,7 @@ func baseline(k *kubectl.CLI, kind, name, container string) (object, int, string
}
}

return manifest, replicas, container, nil
return manifest, replicas, container, source, nil
}

// Process represents a process in a pod
Expand Down Expand Up @@ -222,6 +233,7 @@ func Apply(k *kubectl.CLI, hash string, build func(dockerPod string, op output.O

var selector string
if settings.Replace && kind == "service" {
op.Progress("identifying pod selector")
nameValues, err := k.Lines("get", "service", name, "-o", "go-template={{range $k, $v := .spec.selector}}{{$k}}={{$v}}\n{{end}}")
if err != nil {
return err
Expand All @@ -231,28 +243,54 @@ func Apply(k *kubectl.CLI, hash string, build func(dockerPod string, op output.O

var manifest object
var replicas int
if manifest, replicas, p.Container, err = baseline(k, kind, name, container); err != nil {
var source object
op.Progress("inheriting pod configuration")
if manifest, replicas, p.Container, source, err = baseline(k, kind, name, settings.InheritLabels, settings.InheritAnnotations, container); err != nil {
return err
}

op.Progress("generating manifest")
manifest.with("metadata", func(metadata object) {
metadata["name"] = p.Pod
metadata.with("labels", func(labels object) {
var sourceLabels object
if source != nil {
if sourceLabels = source.obj("metadata"); sourceLabels != nil {
sourceLabels = sourceLabels.obj("labels")
}
}
for _, label := range settings.Labels {
nameValue := strings.SplitN(label, "=", 2)
if len(nameValue) == 1 {
nameValue = []string{nameValue[0], ""}
if sourceLabels != nil && sourceLabels[nameValue[0]] != nil {
labels[nameValue[0]] = sourceLabels[nameValue[0]]
}
} else if nameValue[1] != "" {
labels[nameValue[0]] = nameValue[1]
} else {
delete(labels, nameValue[0])
}
labels[nameValue[0]] = nameValue[1]
}
labels["kdo-pod"] = "1"
labels["kdo-hash"] = hash
}).with("annotations", func(annotations object) {
var sourceAnnotations object
if source != nil {
if sourceAnnotations = source.obj("metadata"); sourceAnnotations != nil {
sourceAnnotations = sourceAnnotations.obj("annotations")
}
}
for _, annotation := range settings.Annotations {
nameValue := strings.SplitN(annotation, "=", 2)
if len(nameValue) == 1 {
nameValue = []string{nameValue[0], ""}
if sourceAnnotations != nil && sourceAnnotations[nameValue[0]] != nil {
annotations[nameValue[0]] = sourceAnnotations[nameValue[0]]
}
} else if nameValue[1] != "" {
annotations[nameValue[0]] = nameValue[1]
} else {
delete(annotations, nameValue[0])
}
annotations[nameValue[0]] = nameValue[1]
}
})
}).with("spec", func(spec object) {
Expand Down

0 comments on commit fd96729

Please sign in to comment.