From 99a92868b9a12fbdcfadada8bc54e1393b4d7a34 Mon Sep 17 00:00:00 2001 From: "Giau. Tran Minh" Date: Mon, 6 Jan 2025 13:34:15 +0700 Subject: [PATCH] internal: added helper for status update --- internal/result/result.go | 80 +++++++++++++++++++++++++++++++++++++++ internal/status/status.go | 58 ++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 internal/result/result.go create mode 100644 internal/status/status.go diff --git a/internal/result/result.go b/internal/result/result.go new file mode 100644 index 0000000..d88cf07 --- /dev/null +++ b/internal/result/result.go @@ -0,0 +1,80 @@ +// Copyright 2025 The Atlas Operator Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package result + +import ( + "errors" + "time" + + ctrl "sigs.k8s.io/controller-runtime" +) + +// Transient checks if the error is transient and returns a result +// that indicates whether the request should be retried. +func Transient(err error) (r ctrl.Result, _ error) { + if t := (*transientError)(nil); errors.As(err, &t) { + return Retry(t.after) + } + // Permanent errors are not returned as errors because they cause + // the controller to requeue indefinitely. Instead, they should be + // reported as a status condition. + return OK() +} + +// OK returns a successful result +func OK() (ctrl.Result, error) { + return ctrl.Result{}, nil +} + +// Failed returns a failed result +func Failed() (ctrl.Result, error) { + return Retry(0) +} + +// Retry requeues the request after the specified number of seconds +func Retry(after int) (ctrl.Result, error) { + return ctrl.Result{ + Requeue: true, + RequeueAfter: time.Second * time.Duration(after), + }, nil +} + +// transientError is an error that should be retried. +type transientError struct { + err error + after int +} + +func (t *transientError) Error() string { return t.err.Error() } +func (t *transientError) Unwrap() error { return t.err } + +// TransientError wraps an error to indicate that it should be retried. +func TransientError(err error) error { + return TransientErrorAfter(err, 5) +} + +// TransientErrorAfter wraps an error to indicate that it should be retried after +// the given duration. +func TransientErrorAfter(err error, after int) error { + if err == nil { + return nil + } + return &transientError{err: err, after: after} +} + +func isTransient(err error) bool { + var t *transientError + return errors.As(err, &t) +} diff --git a/internal/status/status.go b/internal/status/status.go new file mode 100644 index 0000000..e6d4364 --- /dev/null +++ b/internal/status/status.go @@ -0,0 +1,58 @@ +// Copyright 2025 The Atlas Operator Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package status + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ( + // OptionBuilder is an interface that can be implemented + // by any type that can provide a list of options + OptionBuilder[T any] interface { + GetOptions() []Option[T] + } + // Option is an interface that can be implemented by any type + // that can apply an option to a resource and return a result + Option[T any] interface { + ApplyOption(o T) + GetResult() (ctrl.Result, error) + } +) + +// Update takes the options provided by the given option builder, applies them all and then updates the resource +func Update[T client.Object](ctx context.Context, sw client.StatusWriter, obj T, b OptionBuilder[T]) (r ctrl.Result, err error) { + opts := b.GetOptions() + for _, o := range opts { + o.ApplyOption(obj) + } + if err := sw.Update(ctx, obj); err != nil { + return ctrl.Result{}, err + } + for _, o := range opts { + if r, err = o.GetResult(); err != nil { + return r, err + } + } + for _, o := range opts { + if r, _ := o.GetResult(); r.Requeue || r.RequeueAfter > 0 { + return r, nil + } + } + return ctrl.Result{}, nil +}