api/v1/composition.go (131 lines of code) (raw):
package v1
import (
"fmt"
"strconv"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
type CompositionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Composition `json:"items"`
}
// Compositions represent a collection of related, synthesized resources.
//
// For example: when managing Postgres with Eno, one would create a composition
// per distinct instance of Postgres, all referencing a single synthesizer resource.
//
// Changing the spec of a composition will result in re-synthesis.
//
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Synthesizer",type=string,JSONPath=`.spec.synthesizer.name`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.status.currentSynthesis.synthesized`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.simplified.status`
// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.simplified.error`
type Composition struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CompositionSpec `json:"spec,omitempty"`
Status CompositionStatus `json:"status,omitempty"`
}
type CompositionSpec struct {
// Compositions are synthesized by a Synthesizer, referenced by name.
Synthesizer SynthesizerRef `json:"synthesizer,omitempty"`
// Synthesizers can accept Kubernetes resources as inputs.
// Bindings allow compositions to specify which resource to use for a particular input "reference".
// Declaring extra bindings not (yet) supported by the synthesizer is valid.
Bindings []Binding `json:"bindings,omitempty"`
// SynthesisEnv
// A set of environment variables that will be made available inside the synthesis Pod.
// +kubebuilder:validation:MaxItems:=500
SynthesisEnv []EnvVar `json:"synthesisEnv,omitempty"`
}
type CompositionStatus struct {
Simplified *SimplifiedStatus `json:"simplified,omitempty"`
InFlightSynthesis *Synthesis `json:"inFlightSynthesis,omitempty"`
CurrentSynthesis *Synthesis `json:"currentSynthesis,omitempty"`
PreviousSynthesis *Synthesis `json:"previousSynthesis,omitempty"`
InputRevisions []InputRevisions `json:"inputRevisions,omitempty"`
}
type SimplifiedStatus struct {
Status string `json:"status,omitempty"`
Error string `json:"error,omitempty"`
}
func (s *SimplifiedStatus) String() string {
if s == nil {
return "Nil"
}
if s.Error == "" {
return s.Status
}
return fmt.Sprintf("%s (error: %s)", s.Status, s.Error)
}
// A synthesis is the result of synthesizing a composition.
// In other words: it's a collection of resources returned from a synthesizer.
type Synthesis struct {
// A random UUID scoped to this particular synthesis operation.
// Used internally for strict ordering semantics.
UUID string `json:"uuid,omitempty"`
// The value of the composition's metadata.generation at the time the synthesis began.
// This is a min i.e. a newer composition may have been used.
ObservedCompositionGeneration int64 `json:"observedCompositionGeneration,omitempty"`
// The value of the synthesizer's metadata.generation at the time the synthesis began.
// This is a min i.e. a newer composition may have been used.
ObservedSynthesizerGeneration int64 `json:"observedSynthesizerGeneration,omitempty"`
// Initialized is set when the synthesis process is initiated.
Initialized *metav1.Time `json:"initialized,omitempty"`
// Time at which the most recent synthesizer pod was created.
PodCreation *metav1.Time `json:"podCreation,omitempty"`
// Time at which the synthesis completed i.e. resourceSlices was written
Synthesized *metav1.Time `json:"synthesized,omitempty"`
// Time at which the synthesis's resources were reconciled into real Kubernetes resources.
Reconciled *metav1.Time `json:"reconciled,omitempty"`
// Time at which the synthesis's reconciled resources became ready.
Ready *metav1.Time `json:"ready,omitempty"`
// Canceled signals that any running synthesis pods should be deleted,
// and new synthesis pods should never be created for this synthesis UUID.
Canceled *metav1.Time `json:"canceled,omitempty"`
// Counter used internally to calculate back off when retrying failed syntheses.
Attempts int `json:"attempts,omitempty"`
// References to every resource slice that contains the resources comprising this synthesis.
// Immutable.
ResourceSlices []*ResourceSliceRef `json:"resourceSlices,omitempty"`
// Results are passed through opaquely from the synthesizer's KRM function.
Results []Result `json:"results,omitempty"`
// InputRevisions contains the versions of the input resources that were used for this synthesis.
InputRevisions []InputRevisions `json:"inputRevisions,omitempty"`
// Deferred is true when this synthesis was caused by a change to either the synthesizer
// or an input with a ref that sets `Defer == true`.
Deferred bool `json:"deferred,omitempty"`
}
type Result struct {
Message string `json:"message,omitempty"`
Severity string `json:"severity,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
}
type InputRevisions struct {
Key string `json:"key,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
Revision *int `json:"revision,omitempty"`
SynthesizerGeneration *int64 `json:"synthesizerGeneration,omitempty"`
}
func (i *InputRevisions) Less(b InputRevisions) bool {
if i.Key != b.Key {
panic(fmt.Sprintf("cannot compare input revisions for different keys: %s != %s", i.Key, b.Key))
}
if i.Revision != nil && b.Revision != nil {
return *i.Revision < *b.Revision
}
if i.ResourceVersion == b.ResourceVersion {
return false
}
iInt, iErr := strconv.Atoi(i.ResourceVersion)
bInt, bErr := strconv.Atoi(b.ResourceVersion)
if iErr != nil || bErr != nil {
return true // effectively fall back to equality comparison if they aren't ints (shouldn't be possible)
}
return iInt < bInt
}
func (s *CompositionStatus) GetCurrentSynthesisUUID() string {
if s.CurrentSynthesis == nil {
return ""
}
return s.CurrentSynthesis.UUID
}
func (c *Composition) ShouldIgnoreSideEffects() bool {
return c.Annotations["eno.azure.io/ignore-side-effects"] == "true"
}
func (c *Composition) Synthesizing() bool {
return c.Status.InFlightSynthesis != nil && c.Status.InFlightSynthesis.Canceled == nil
}
func (c *Composition) EnableIgnoreSideEffects() {
anno := c.GetAnnotations()
if anno == nil {
anno = map[string]string{}
}
anno["eno.azure.io/ignore-side-effects"] = "true"
c.SetAnnotations(anno)
}
const forceResynthesisAnnotation = "eno.azure.io/force-resynthesis"
func (c *Composition) ForceResynthesis() {
anno := c.GetAnnotations()
if anno == nil {
anno = map[string]string{}
}
anno[forceResynthesisAnnotation] = c.Status.getLatestSynthesisUUID()
c.SetAnnotations(anno)
}
func (c *Composition) ShouldForceResynthesis() bool {
val, ok := c.GetAnnotations()[forceResynthesisAnnotation]
return ok && val == c.Status.getLatestSynthesisUUID()
}
func (c *Composition) ShouldOrphanResources() bool {
return c.Annotations["eno.azure.io/deletion-strategy"] == "orphan"
}
func (s *CompositionStatus) getLatestSynthesisUUID() string {
if s.InFlightSynthesis != nil {
return s.InFlightSynthesis.UUID
}
if s.CurrentSynthesis != nil {
return s.CurrentSynthesis.UUID
}
return ""
}