Skip to content

Commit

Permalink
pkg/action: add module
Browse files Browse the repository at this point in the history
examples:

	err = action.New(c).
		WithMatcher(matcher).
		WithAction(actionUpdate).
		Exec(ctx, d...)
	if err != nil {
		return err
	}

	return action.New(c).
		WithAction(action.Delete).
		ExecWithRetry(ctx, action.IfAnyLeft(action.DefaultMatcher), d...)

Signed-off-by: Yauheni Kaliuta <[email protected]>
  • Loading branch information
ykaliuta committed Apr 18, 2024
1 parent 6711ab3 commit 61280c6
Showing 1 changed file with 212 additions and 0 deletions.
212 changes: 212 additions & 0 deletions pkg/action/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package action

import (
"context"
"errors"
"fmt"
"time"

"github.com/hashicorp/go-multierror"

Check failure on line 10 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not `gci`-ed with -s standard -s default -s prefix(github.com/opendatahub-io/opendatahub-operator) -s blank -s dot --custom-order (gci)
"sigs.k8s.io/controller-runtime/pkg/client"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"

Check failure on line 16 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not `gci`-ed with -s standard -s default -s prefix(github.com/opendatahub-io/opendatahub-operator) -s blank -s dot --custom-order (gci)
)

type ResourceSpec struct {
Gvk schema.GroupVersionKind
Namespace string
// path to the field, like "metadata", "name"
Path []string
// set of values for the field to match object, any one matches
Values []string
}

type MatcherFunc func(r ResourceSpec, obj *unstructured.Unstructured) (bool, error)
type ActionFunc func(ctx context.Context, c client.Client, r ResourceSpec, obj *unstructured.Unstructured) error

Check failure on line 29 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

type name will be used as action.ActionFunc by other packages, and that stutters; consider calling this Func (golint)
type RetryCheckFunc func(ctx context.Context, c client.Client, resources ...ResourceSpec) (bool, error)

type Action struct {
client client.Client

matcher MatcherFunc
actions []ActionFunc
}

// shouldn't just return false on error?
func DefaultMatcher(r ResourceSpec, obj *unstructured.Unstructured) (bool, error) {
if len(r.Path) == 0 || len(r.Values) == 0 {
return true, nil
}

v, ok, err := unstructured.NestedString(obj.Object, r.Path...)
if err != nil {
return false, fmt.Errorf("failed to get field %v for %s %s/%s: %w", r.Path, r.Gvk.Kind, r.Namespace, obj.GetName(), err)
}

if !ok {
return false, fmt.Errorf("unexisting path to handle: %v", r.Path)
}

for _, toDelete := range r.Values {
if v == toDelete {
return true, nil
}
}

return false, nil
}

func New(c client.Client) *Action {
return &Action{
client: c,

Check failure on line 65 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not `gci`-ed with -s standard -s default -s prefix(github.com/opendatahub-io/opendatahub-operator) -s blank -s dot --custom-order (gci)
matcher: DefaultMatcher,
}
}

func (o *Action) Or(m1, m2 MatcherFunc) MatcherFunc {
return func(r ResourceSpec, obj *unstructured.Unstructured) (bool, error) {
res, err := m1(r, obj)
if err != nil {
return false, err
}
if res {
return true, err
}
return m2(r, obj)
}
}

func (o *Action) And(m1, m2 MatcherFunc) MatcherFunc {
return func(r ResourceSpec, obj *unstructured.Unstructured) (bool, error) {
res, err := m1(r, obj)
if err != nil {
return false, err
}
if !res {
return false, err
}
return m2(r, obj)
}
}

func (o *Action) WithMatcher(m MatcherFunc) *Action {
o.matcher = m
return o
}

func (o *Action) WithAction(a ActionFunc) *Action {
o.actions = append(o.actions, a)
return o
}

func (o *Action) execOneResource(ctx context.Context, r ResourceSpec, objs []*unstructured.Unstructured) error {
for _, item := range objs {
for _, a := range o.actions {
err := a(ctx, o.client, r, item)
if err != nil {
return err
}
}
}

return nil
}

func ListMatched(ctx context.Context, c client.Client, matcher MatcherFunc, resources ...ResourceSpec) (map[*ResourceSpec][]*unstructured.Unstructured, error) {
ret := make(map[*ResourceSpec][]*unstructured.Unstructured)

for _, r := range resources {
r := r
var items []*unstructured.Unstructured

list := &unstructured.UnstructuredList{}
list.SetGroupVersionKind(r.Gvk)

err := c.List(ctx, list, client.InNamespace(r.Namespace))
if err != nil {
if errors.Is(err, &meta.NoKindMatchError{}) {
fmt.Printf("Could not list %v: CRD not found\n", r.Gvk)
continue
} else {

Check warning on line 134 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

superfluous-else: if block ends with a continue statement, so drop this else and outdent its block (revive)
return ret, fmt.Errorf("failed to list %s: %w", r.Gvk.Kind, err)
}
}

for _, item := range list.Items {
item := item

matched, err := matcher(r, &item)
if err != nil {
return ret, err
}

if !matched {
continue
}

items = append(items, &item)
}

if len(items) > 0 {
ret[&r] = items
}
}

return ret, nil
}

func (o *Action) Exec(ctx context.Context, resources ...ResourceSpec) error {
var errors *multierror.Error

matched, err := ListMatched(ctx, o.client, o.matcher, resources...)
if err != nil {
return err
}

for r, objs := range matched {
err := o.execOneResource(ctx, *r, objs)
errors = multierror.Append(errors, err)
}

return errors.ErrorOrNil()
}

func (o *Action) ExecWithRetry(ctx context.Context, shouldRetry RetryCheckFunc, resources ...ResourceSpec) error {
return wait.ExponentialBackoffWithContext(ctx, wait.Backoff{
// 5, 10, ,20, 40 then timeout
Duration: 5 * time.Second,
Factor: 2.0,
Jitter: 0.1,
Steps: 4,
Cap: 1 * time.Minute,
}, func(ctx context.Context) (bool, error) {
err := o.Exec(ctx, resources...)
if err != nil {
return false, err
}
return shouldRetry(ctx, o.client, resources...)
})
}

func (o *Action) DryRun(_ context.Context, _ ...ResourceSpec) error {
return nil
}

func Delete(ctx context.Context, c client.Client, r ResourceSpec, obj *unstructured.Unstructured) error {

Check warning on line 199 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
return c.Delete(ctx, obj)
}

func IfAnyLeft(matcher MatcherFunc) RetryCheckFunc {
return func(ctx context.Context, c client.Client, resources ...ResourceSpec) (bool, error) {
matched, err := ListMatched(ctx, c, matcher, resources...)
if err != nil {
return false, nil

Check failure on line 207 in pkg/action/action.go

View workflow job for this annotation

GitHub Actions / golangci-lint

error is not nil (line 205) but it returns nil (nilerr)
}

return len(matched) == 0, nil
}
}

0 comments on commit 61280c6

Please sign in to comment.