Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ bin/*
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
cover.html
cover*.html
*.out

# Go workspace file
Expand Down
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,50 @@ The `pkg/collections` package contains multiple interfaces for collections, mode

The package also contains further packages that contain some auxiliary functions for working with slices and maps in golang, e.g. for filtering.

### conditions

The `pkg/conditions` package helps with managing condition lists.

The managed condition implementation must satisfy the `Condition[T comparable]` interface:
```go
type Condition[T comparable] interface {
GetType() string
SetType(conType string)
GetStatus() T
SetStatus(status T)
GetLastTransitionTime() time.Time
SetLastTransitionTime(timestamp time.Time)
GetReason() string
SetReason(reason string)
GetMessage() string
SetMessage(message string)
}
```

To manage conditions, use the `ConditionUpdater` function and pass in a constructor function for your condition implementation and the old list of conditions. The bool argument determines whether old conditions that are not updated remain in the returned list (`false`) or are removed, so that the returned list contains only the conditions that were touched (`true`).

```go
updater := conditions.ConditionUpdater(func() conditions.Condition[bool] { return &conImpl{} }, oldCons, false)
```

Note that the `ConditionUpdater` stores the current time upon initialization and will set each updated condition's timestamp to this value, if the status of that condition changed as a result of the update. To use a different timestamp, manually overwrite the `Now` field of the updater.

Use `UpdateCondition` or `UpdateConditionFromTemplate` to update a condition:
```go
updater.UpdateCondition("myCondition", true, "newReason", "newMessage")
```

If all conditions are updated, use the `Conditions` method to generate the new list of conditions. The originally passed in list of conditions is not modified by the updater.
The second return value is `true` if the updated list of conditions differs from the original one.
```go
updatedCons, changed := updater.Conditions()
```

For simplicity, all commands can be chained:
```go
updatedCons, changed := conditions.ConditionUpdater(func() conditions.Condition[bool] { return &conImpl{} }, oldCons, false).UpdateCondition("myCondition", true, "newReason", "newMessage").Conditions()
```

### controller

The `pkg/controller` package contains useful functions for setting up and running k8s controllers.
Expand All @@ -51,7 +95,6 @@ The `pkg/controller` package contains useful functions for setting up and runnin

### logging


This package contains the logging library from the [Landscaper controller-utils module](https://github.com/gardener/landscaper/tree/master/controller-utils/pkg/logging).

The library provides a wrapper around `logr.Logger`, exposing additional helper functions. The original `logr.Logger` can be retrieved by using the `Logr()` method. Also, it notices when multiple values are added to the logger with the same key - instead of simply overwriting the previous ones (like `logr.Logger` does it), it appends the key with a `_conflict(x)` suffix, where `x` is the number of times this conflict has occurred.
Expand Down
52 changes: 52 additions & 0 deletions pkg/conditions/conditions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package conditions

import "time"

// Condition represents a condition consisting of type, status, reason, message and a last transition timestamp.
type Condition[T comparable] interface {
// SetStatus sets the status of the condition.
SetStatus(status T)
// GetStatus returns the status of the condition.
GetStatus() T

// SetType sets the type of the condition.
SetType(conType string)
// GetType returns the type of the condition.
GetType() string

// SetLastTransitionTime sets the timestamp of the condition.
SetLastTransitionTime(timestamp time.Time)
// GetLastTransitionTime returns the timestamp of the condition.
GetLastTransitionTime() time.Time

// SetReason sets the reason of the condition.
SetReason(reason string)
// GetReason returns the reason of the condition.
GetReason() string

// SetMessage sets the message of the condition.
SetMessage(message string)
// GetMessage returns the message of the condition.
GetMessage() string
}

// GetCondition returns a pointer to the condition for the given type, if it exists.
// Otherwise, nil is returned.
func GetCondition[T comparable](ccl []Condition[T], t string) Condition[T] {
for i := range ccl {
if ccl[i].GetType() == t {
return ccl[i]
}
}
return nil
}

// AllConditionsHaveStatus returns true if all conditions have the specified status.
func AllConditionsHaveStatus[T comparable](status T, conditions ...Condition[T]) bool {
for _, con := range conditions {
if con.GetStatus() != status {
return false
}
}
return true
}
14 changes: 14 additions & 0 deletions pkg/conditions/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package conditions_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestConditions(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecs(t, "Conditions Test Suite")
}
123 changes: 123 additions & 0 deletions pkg/conditions/updater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package conditions

import (
"slices"
"strings"
"time"

"k8s.io/apimachinery/pkg/util/sets"
)

// conditionUpdater is a helper struct for updating a list of Conditions.
// Use the ConditionUpdater constructor for initializing.
type conditionUpdater[T comparable] struct {
Now time.Time
conditions map[string]Condition[T]
updated sets.Set[string]
constructor func() Condition[T]
changed bool
}

// ConditionUpdater creates a builder-like helper struct for updating a list of Conditions.
// The 'constructor' argument is a function that returns a new (empty) instance of the condition implementation type.
// The 'conditions' argument contains the old condition list.
// If removeUntouched is true, the condition list returned with Conditions() will have all conditions removed that have not been updated.
// If false, all conditions will be kept.
// Note that calling this function stores the current time as timestamp that is used as timestamp if a condition's status changed.
// To overwrite this timestamp, modify the 'Now' field of the returned struct manually.
//
// The given condition list is not modified.
//
// Usage example:
// status.conditions = ConditionUpdater(status.conditions, true).UpdateCondition(...).UpdateCondition(...).Conditions()
func ConditionUpdater[T comparable](constructor func() Condition[T], conditions []Condition[T], removeUntouched bool) *conditionUpdater[T] {
res := &conditionUpdater[T]{
Now: time.Now(),
conditions: make(map[string]Condition[T], len(conditions)),
constructor: constructor,
changed: false,
}
for _, con := range conditions {
res.conditions[con.GetType()] = con
}
if removeUntouched {
res.updated = sets.New[string]()
}
return res
}

// UpdateCondition updates or creates the condition with the specified type.
// All fields of the condition are updated with the values given in the arguments, but the condition's LastTransitionTime is only updated (with the timestamp contained in the receiver struct) if the status changed.
// Returns the receiver for easy chaining.
func (c *conditionUpdater[T]) UpdateCondition(conType string, status T, reason, message string) *conditionUpdater[T] {
con := c.constructor()
con.SetType(conType)
con.SetStatus(status)
con.SetReason(reason)
con.SetMessage(message)
con.SetLastTransitionTime(c.Now)
old, ok := c.conditions[conType]
if ok && old.GetStatus() == con.GetStatus() {
// update LastTransitionTime only if status changed
con.SetLastTransitionTime(old.GetLastTransitionTime())
}
if !c.changed {
if ok {
c.changed = old.GetStatus() != con.GetStatus() || old.GetReason() != con.GetReason() || old.GetMessage() != con.GetMessage()
} else {
c.changed = true
}
}
c.conditions[conType] = con
if c.updated != nil {
c.updated.Insert(conType)
}
return c
}

// UpdateConditionFromTemplate is a convenience wrapper around UpdateCondition which allows it to be called with a preconstructed ComponentCondition.
func (c *conditionUpdater[T]) UpdateConditionFromTemplate(con Condition[T]) *conditionUpdater[T] {
return c.UpdateCondition(con.GetType(), con.GetStatus(), con.GetReason(), con.GetMessage())
}

// HasCondition returns true if a condition with the given type exists in the updated condition list.
func (c *conditionUpdater[T]) HasCondition(conType string) bool {
_, ok := c.conditions[conType]
return ok && (c.updated == nil || c.updated.Has(conType))
}

// RemoveCondition removes the condition with the given type from the updated condition list.
func (c *conditionUpdater[T]) RemoveCondition(conType string) *conditionUpdater[T] {
if !c.HasCondition(conType) {
return c
}
delete(c.conditions, conType)
if c.updated != nil {
c.updated.Delete(conType)
}
c.changed = true
return c
}

// Conditions returns the updated condition list.
// If the condition updater was initialized with removeUntouched=true, this list will only contain the conditions which have been updated
// in between the condition updater creation and this method call. Otherwise, it will potentially also contain old conditions.
// The conditions are returned sorted by their type.
func (c *conditionUpdater[T]) Conditions() ([]Condition[T], bool) {
res := make([]Condition[T], 0, len(c.conditions))
for _, con := range c.conditions {
if c.updated == nil {
res = append(res, con)
continue
}
if c.updated.Has(con.GetType()) {
res = append(res, con)
} else {
c.changed = true
}
}
slices.SortStableFunc(res, func(a, b Condition[T]) int {
return strings.Compare(a.GetType(), b.GetType())
})
return res, c.changed
}
Loading
Loading