Reflow text to 80 chars per line.

Signed-off-by: Travis Mattera <travis@mattera.io>
pull/4271/head
Travis Mattera 1 year ago
parent 0135f48d87
commit cb4db08949
No known key found for this signature in database
GPG Key ID: 6E9A8A6A0A74CA52

@ -9,227 +9,227 @@
## Summary ## Summary
This RFC proposes an alternative method to indicate the suspended state of suspendable resources to flux controllers through object metadata. It presents an annotation key that can be used to suspend a resource from reconciliation as an alternative to the `.spec.suspend` field. It does not address the deprecation of the the `.spec.suspend` field from the resource apis. This annotation can optionally act as a vehicle for communicating contextual information about the suspended resource to users. This RFC proposes an alternative method to indicate the suspended state of
suspendable resources to flux controllers through object metadata. It presents
an annotation key that can be used to suspend a resource from reconciliation as
an alternative to the `.spec.suspend` field. It does not address the
deprecation of the the `.spec.suspend` field from the resource apis. This
annotation can optionally act as a vehicle for communicating contextual
information about the suspended resource to users.
## Motivation ## Motivation
The current implementation of suspending a resource from reconciliation uses the `.spec.suspend` field. A change to this field results in a generation number increase which can be confusing when diffing. The current implementation of suspending a resource from reconciliation uses
the `.spec.suspend` field. A change to this field results in a generation
number increase which can be confusing when diffing.
Teams may wish to communicate information about the suspended resource, such as the reason for the suspension, in the object itself. Teams may wish to communicate information about the suspended resource, such as
the reason for the suspension, in the object itself.
### Goals ### Goals
The flux reconciliation loop will support recognizing a resource's suspend status based on either the `.spec.suspend` field or a specific metadata annotation key. The flux cli will similarly recognize this state with `get` commands and but will only manipulate the annotation key under `suspend` and `resume` commands. The flux cli will support optionally setting the suspend metadata annotation value with a user supplied string for a contextual message. The flux reconciliation loop will support recognizing a resource's suspend
status based on either the `.spec.suspend` field or a specific metadata
annotation key. The flux cli will similarly recognize this state with `get`
commands and but will only manipulate the annotation key under `suspend` and
`resume` commands. The flux cli will support optionally setting the suspend
metadata annotation value with a user supplied string for a contextual message.
### Non-Goals ### Non-Goals
The deprecation plan for the `.spec.suspend` field is out of scope for this RFC. The deprecation plan for the `.spec.suspend` field is out of scope for this
RFC.
## Proposal ## Proposal
Register a flux resource metadata key `reconcile.fluxcd.io/suspended` with a suspend semantic to be interpreted by controllers and manipulated by the cli. The presence of the annotation key is an alternative to the `.spec.suspend: true` api field setting when considering if a resource is suspended or not. The annotation key is set by a `flux suspend` command and removed by a `flux resume` command. The annotation key value is open for communicating a message or reason for the object's suspension. The value can be set using a `--message` flag to the `suspend` command. Register a flux resource metadata key `reconcile.fluxcd.io/suspended` with a
suspend semantic to be interpreted by controllers and manipulated by the cli.
The presence of the annotation key is an alternative to the `.spec.suspend:
true` api field setting when considering if a resource is suspended or not.
The annotation key is set by a `flux suspend` command and removed by a `flux
resume` command. The annotation key value is open for communicating a message
or reason for the object's suspension. The value can be set using a
`--message` flag to the `suspend` command.
### User Stories ### User Stories
#### Suspend/Resume without Generation Roll #### Suspend/Resume without Generation Roll
Currently when a resource is set to suspended or resumed the `.spec.suspend` field is mutated which results in a roll of the generation number in the `.metadata.generation` and fields `.status.observedGeneration` number. The community believes that the generation change for this reason is not in alignment with gitops principles. Currently when a resource is set to suspended or resumed the `.spec.suspend`
field is mutated which results in a roll of the generation number in the
`.metadata.generation` and fields `.status.observedGeneration` number. The
community believes that the generation change for this reason is not in
alignment with gitops principles.
The flux controllers should recognize that a resource is suspended or unsuspended from the presence of a special metadata key -- this key can be added, removed or changed without patching the object and rolling the generation number. The flux controllers should recognize that a resource is suspended or
unsuspended from the presence of a special metadata key -- this key can be
added, removed or changed without patching the object and rolling the
generation number.
#### Seeing Suspend State #### Seeing Suspend State
Users should be able to see the effective suspend state of the resource with a `flux get` command. The display should mirror what the controllers interpret the suspend state to be. This story is included to capture current functionality that should be preserved. Users should be able to see the effective suspend state of the resource with a
`flux get` command. The display should mirror what the controllers interpret
the suspend state to be. This story is included to capture current
functionality that should be preserved.
#### Suspend with a Reason #### Suspend with a Reason
Often there is a purpose behind suspending a resource with the flux cli, whether it be during incident response, source manifest cutovers, or various other scenarios. The `flux diff` command provides a great UX for determining what will change if a suspended resource is resumed, but it doesn't help explain _why_ something is paused or when it would be ok to resume reconciliation. On distributed teams this can become a point of friction as it needs to be communicated among group stakeholders. Often there is a purpose behind suspending a resource with the flux cli,
whether it be during incident response, source manifest cutovers, or various
other scenarios. The `flux diff` command provides a great UX for determining
what will change if a suspended resource is resumed, but it doesn't help
explain _why_ something is paused or when it would be ok to resume
reconciliation. On distributed teams this can become a point of friction as it
needs to be communicated among group stakeholders.
Flux users should have a way to succinctly signal to other users why a resource is suspended on the resource itself. Flux users should have a way to succinctly signal to other users why a resource
is suspended on the resource itself.
### Alternatives ### Alternatives
#### More `.spec` #### More `.spec`
The existing `.spec.suspend` could be expanded with fields for the above semantics. This would drive more generation number changes and would require a change to the apis. The existing `.spec.suspend` could be expanded with fields for the above
semantics. This would drive more generation number changes and would require a
change to the apis.
## Design Details ## Design Details
Implementing this RFC would involve the controllers and the cli. Implementing this RFC would involve the controllers and the cli.
This feature would create an alternate path to suspending an object and would not violate the current apis. This feature would create an alternate path to suspending an object and would
not violate the current apis.
### Common ### Common
The `reconcile.fluxcd.io/suspended` annotation key string and a getter function would be made avaiable for controllers the cli to recognize and manipulate the suspend object metadata. The `reconcile.fluxcd.io/suspended` annotation key string and a getter function
would be made avaiable for controllers the cli to recognize and manipulate the
suspend object metadata.
``` ``` # github.com/fluxcd/pkg/apis/meta
# github.com/fluxcd/pkg/apis/meta
// SuspendAnnotation is the annotation used to indicate an object is suspended and optionally to store metadata about why a resource // SuspendAnnotation is the annotation used to indicate an object is suspended
// was set to suspended. and optionally to store metadata about why a resource // was set to suspended.
const SuspendedAnnotation string = "reconcile.fluxcd.io/suspended" const SuspendedAnnotation string = "reconcile.fluxcd.io/suspended"
// SuspendAnnotationValue returns a value for the suspended annotation, which can be used to detect // SuspendAnnotationValue returns a value for the suspended annotation, which
// changes; and, a boolean indicating whether the annotation was set. can be used to detect // changes; and, a boolean indicating whether the
func SuspendedAnnotationValue(annotations map[string]string) (string, bool) { annotation was set. func SuspendedAnnotationValue(annotations
suspend, ok := annotations[SuspendedAnnotation] map[string]string) (string, bool) { suspend, ok :=
return suspend, ok annotations[SuspendedAnnotation] return suspend, ok }
}
// SetSuspendedAnnotation sets a suspend key with a supplied string value in an object's metadata annotations // SetSuspendedAnnotation sets a suspend key with a supplied string value in an
func SetSuspendedAnnotation(annotations *map[string]string, token string) { object's metadata annotations func SetSuspendedAnnotation(annotations
if annotations == nil { *map[string]string, token string) { if annotations == nil { annotations =
annotations = &map[string]string{} &map[string]string{} } if token != "" { (*annotations)[SuspendedAnnotation] =
} token } else { (*annotations)[SuspendedAnnotation] = "true" } }
if token != "" {
(*annotations)[SuspendedAnnotation] = token
} else {
(*annotations)[SuspendedAnnotation] = "true"
}
}
// UnsetSuspendedAnnotation removes a suspend key from an object's metadata annotations // UnsetSuspendedAnnotation removes a suspend key from an object's metadata
func UnsetSuspendedAnnotation(annotations *map[string]string) { annotations func UnsetSuspendedAnnotation(annotations *map[string]string) {
delete(*annotations, SuspendedAnnotation) delete(*annotations, SuspendedAnnotation) } ```
}
```
An event predicate would skip attempting to reconcile a resource with the suspend annotation set. An event predicate would skip attempting to reconcile a resource with the
suspend annotation set.
``` ``` # github.com/fluxcd/pkg/apis/predicates
# github.com/fluxcd/pkg/apis/predicates
# suspended.go # suspended.go
// Update implements the default UpdateEvent filter for filtering by meta.SuspendedAnnotation existence // Update implements the default UpdateEvent filter for filtering by
func (SuspendedPredicate) Update(e event.UpdateEvent) bool { meta.SuspendedAnnotation existence func (SuspendedPredicate) Update(e
if e.ObjectOld == nil || e.ObjectNew == nil { event.UpdateEvent) bool { if e.ObjectOld == nil || e.ObjectNew == nil { return
return false false }
}
if _, ok := metav1.SuspendedAnnotationValue(e.ObjectNew.GetAnnotations()); !ok { if _, ok := metav1.SuspendedAnnotationValue(e.ObjectNew.GetAnnotations());
return true !ok { return true } return false } ```
}
return false
}
```
### Controllers ### Controllers
Flux controllers would skip reconciling a resource based on an `OR` of (1) the api `.spec.suspend` and (2) the existence of the suspend metadata annotation key. Flux controllers would skip reconciling a resource based on an `OR` of (1) the
api `.spec.suspend` and (2) the existence of the suspend metadata annotation
key.
``` ``` # github.com/fluxcd/kustomize-controller/api/v1 # kustomization_types.go
# github.com/fluxcd/kustomize-controller/api/v1
# kustomization_types.go
// IsSuspended returns the effective suspend state of the object. // IsSuspended returns the effective suspend state of the object. func (in
func (in Kustomization) IsSuspended() bool { Kustomization) IsSuspended() bool { if in.Spec.Suspend { return true } if _, ok
if in.Spec.Suspend { := meta.SuspendedAnnotationValue(in.Annotations); ok { return true } return
return true false }
}
if _, ok := meta.SuspendedAnnotationValue(in.Annotations); ok {
return true
}
return false
}
# github.com/fluxcd/kustomize-controller/controller # github.com/fluxcd/kustomize-controller/controller #
# kustomization_controller.go kustomization_controller.go
// Skip reconciliation if the object is suspended. // Skip reconciliation if the object is suspended. // if obj.Spec.Suspend { //
// if obj.Spec.Suspend { // no longer using `.spec.suspend` directly no longer using `.spec.suspend` directly if obj.Status.IsSuspended {
if obj.Status.IsSuspended { log.Info("Reconciliation is suspended for this object") return ctrl.Result{},
log.Info("Reconciliation is suspended for this object") nil } ```
return ctrl.Result{}, nil
}
```
### cli ### cli
The flux cli would recognize the suspend state from the suspend object status. The flux cli would recognize the suspend state from the suspend object status.
``` ``` # github.com/fluxcd/flux2/main
# github.com/fluxcd/flux2/main
# get_source_git.go # get_source_git.go
func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool,
... includeKind bool) []string { ... return append(nameColumns(&item,
return append(nameColumns(&item, includeNamespace, includeKind), includeNamespace, includeKind), // revision,
// revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) revision,
revision, strings.Title(strconv.FormatBool(item.IsSuspended)), status, msg) strings.Title(strconv.FormatBool(item.IsSuspended)), status, msg) } ```
}
```
The flux cli would manipulate the suspend metadata but forgo toggling the `.spec.suspend` setting. An optional `--message|-m` flag would support setting the suspended annotation value to a user-specified string. The flux cli would manipulate the suspend metadata but forgo toggling the
`.spec.suspend` setting. An optional `--message|-m` flag would support setting
the suspended annotation value to a user-specified string.
``` ``` # github.com/fluxcd/flux2/main
# github.com/fluxcd/flux2/main
# suspend.go # suspend.go
type SuspendFlags struct { type SuspendFlags struct { ... message string }
...
message string
}
func init() { func init() { ... suspendCmd.PersistentFlags().StringVarP(&suspendArgs.message,
... "message", "m", "", "set a message about why the resource is suspended, the
suspendCmd.PersistentFlags().StringVarP(&suspendArgs.message, "message", "m", "", message will show up under the reconcile.fluxcd.io/suspended annotation") ... }
"set a message about why the resource is suspended, the message will show up under the reconcile.fluxcd.io/suspended annotation")
...
}
type suspendable interface { type suspendable interface { ... // setSuspended() setSuspended(message string)
...
// setSuspended()
setSuspended(message string)
} }
type suspendCommand struct { type suspendCommand struct { ... // obj.setSuspended()
... obj.setSuspended(suspendArgs.message) }
// obj.setSuspended()
obj.setSuspended(suspendArgs.message)
}
# suspend_helmrelease.go # suspend_helmrelease.go
func (obj helmReleaseAdapter) isSuspended() bool { func (obj helmReleaseAdapter) isSuspended() bool { return
return obj.HelmRelease.IsSuspended() // use the resource api spec method obj.HelmRelease.IsSuspended() // use the resource
} api spec method }
func (obj helmReleaseAdapter) setSuspended(message string) { func (obj helmReleaseAdapter) setSuspended(message string) {
meta.SetSuspendedAnnotation(&obj.HelmRelease.Annotations, message) // use the common annotation setter meta.SetSuspendedAnnotation(&obj.HelmRelease.Annotations, message) // use the
} common annotation setter }
# resume_helmrelease.go # resume_helmrelease.go
func (obj helmReleaseAdapter) setUnsuspended() { func (obj helmReleaseAdapter) setUnsuspended() {
meta.UnsetSuspendedAnnotation(&obj.HelmRelease.Annotations) // use the common annotation unsetter meta.UnsetSuspendedAnnotation(&obj.HelmRelease.Annotations) // use the
} common annotation unsetter } ```
```
### Metrics ### Metrics
Custom metrics can be [configured under kube-state-metrics](https://fluxcd.io/flux/monitoring/custom-metrics/#adding-custom-metrics) using the object metadata annotation path. Custom metrics can be [configured under
kube-state-metrics](https://fluxcd.io/flux/monitoring/custom-metrics/#adding-custom-metrics)
using the object metadata annotation path.
``` ```
- name: "resource_info" - name: "resource_info" help: "The current state of a GitOps Toolkit resource."
help: "The current state of a GitOps Toolkit resource." each: type: Info info: labelsFromPath: name: [metadata, name] labelsFromPath:
each: ... suspendedAnnotation: [ metadata, annotations,
type: Info reconcile.fluxcd.io/suspended ] ```
info:
labelsFromPath:
name: [metadata, name]
labelsFromPath:
...
suspendedAnnotation: [ metadata, annotations, reconcile.fluxcd.io/suspended ]
```
## Implementation History ## Implementation History

Loading…
Cancel
Save