Skip to content

Refactors the kstatus functions and adds unstructured support in the version check #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 16, 2020
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
54 changes: 24 additions & 30 deletions pkg/patterns/addon/pkg/status/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,42 +74,32 @@ func (a *aggregator) Reconciled(ctx context.Context, src declarative.Declarative

unstruct, ok := src.(*unstructured.Unstructured)
instance, commonOkay := src.(addonv1alpha1.CommonObject)

unstructStatus := make(map[string]interface{})
var status addonv1alpha1.CommonStatus

if ok {
unstructStatus["Healthy"] = true
} else if commonOkay {
status = addonv1alpha1.CommonStatus{Healthy: true}
} else {
return fmt.Errorf("object %T was not an addonv1alpha1.CommonObject", src)
}
changed := false

if commonOkay {
var status = instance.GetCommonStatus()
status.Errors = statusErrors
status.Healthy = statusHealthy

if !reflect.DeepEqual(status, instance.GetCommonStatus()) {
instance.SetCommonStatus(status)
status.Healthy = statusHealthy

log.WithValues("name", instance.GetName()).WithValues("status", status).Info("updating status")

err := a.client.Status().Update(ctx, instance)
if err != nil {
log.Error(err, "updating status")
return err
}
changed = true
}
} else {
unstructStatus["Healthy"] = true
unstructStatus["Errors"] = statusErrors
} else if ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for a future PR: I wonder if we can refactor this into our helper methods ... e.g. GetCommonStatus and SetCommonStatus

unstructStatus := make(map[string]interface{})

s, _, err := unstructured.NestedMap(unstruct.Object, "status")
if err != nil {
log.Error(err, "getting status")
return fmt.Errorf("unable to get status from unstructured: %v", err)
}
if !reflect.DeepEqual(status, s) {

unstructStatus = s
unstructStatus["Healthy"] = statusHealthy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it healthy or Healthy? It should be the JSON form I believe

unstructStatus["Errors"] = statusErrors
if !reflect.DeepEqual(unstruct, s) {
err = unstructured.SetNestedField(unstruct.Object, statusHealthy, "status", "healthy")
if err != nil {
log.Error(err, "updating status")
Expand All @@ -121,22 +111,26 @@ func (a *aggregator) Reconciled(ctx context.Context, src declarative.Declarative
log.Error(err, "updating status")
return fmt.Errorf("unable to set status in unstructured: %v", err)
}
changed = true
}
} else {
return fmt.Errorf("instance %T was not an addonsv1alpha1.CommonObject or unstructured.Unstructured", src)
}

log.WithValues("name", unstruct.GetName()).WithValues("status", status).Info("updating status")

err = a.client.Status().Update(ctx, unstruct)
if err != nil {
log.Error(err, "updating status")
return err
}
if changed == true {
log.WithValues("name", src.GetName()).WithValues("status", statusHealthy).Info("updating status")
err := a.client.Status().Update(ctx, src)
if err != nil {
log.Error(err, "updating status")
return err
}
}

return nil
}

func (a *aggregator) deployment(ctx context.Context, src declarative.DeclarativeObject, name string) (bool, error) {
key := client.ObjectKey{Namespace: src.GetNamespace(), Name: name}
key := client.ObjectKey{Name: name}
dep := &appsv1.Deployment{}

if err := a.client.Get(ctx, key, dep); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions pkg/patterns/addon/pkg/status/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative"
)

// Deprecated: This function exists for backward compatibility, please use NewKstatusCheck

// NewBasic provides an implementation of declarative.Status that
// performs no preflight checks.
func NewBasic(client client.Client) declarative.Status {
Expand All @@ -44,3 +46,9 @@ func NewBasicVersionChecks(client client.Client, version string) (declarative.St
// no preflight checks
}, nil
}

func NewKstatusCheck(client client.Client, d *declarative.Reconciler) declarative.Status {
return &declarative.StatusBuilder{
ReconciledImpl: NewKstatusAgregator(client, d),
}
}
109 changes: 109 additions & 0 deletions pkg/patterns/addon/pkg/status/kstatus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package status

import (
"context"
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
addonsv1alpha1 "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/apis/v1alpha1"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/manifest"
)

type kstatusAggregator struct {
client client.Client
reconciler *declarative.Reconciler
}

func NewKstatusAgregator(c client.Client, reconciler *declarative.Reconciler) *kstatusAggregator {
return &kstatusAggregator{client: c, reconciler: reconciler}
}

func (k *kstatusAggregator) Reconciled(ctx context.Context, src declarative.DeclarativeObject,
objs *manifest.Objects) error {
log := log.Log

statusMap := make(map[status.Status]bool)
for _, object := range objs.Items {

unstruct, err := declarative.GetObjectFromCluster(object, k.reconciler)
if err != nil {
log.WithValues("object", object.Kind+"/"+object.Name).Error(err, "Unable to get status of object")
return err
}

res, err := status.Compute(unstruct)
if err != nil {
log.WithValues("kind", object.Kind).WithValues("name", object.Name).WithValues("status", res.Status).WithValues(
"message", res.Message).Info("Got status of resource:")
statusMap[status.NotFoundStatus] = true
}
if res != nil {
log.WithValues("kind", object.Kind).WithValues("name", object.Name).WithValues("status", res.Status).WithValues("message", res.Message).Info("Got status of resource:")
statusMap[res.Status] = true
}
}

addonObject, ok := src.(addonsv1alpha1.CommonObject)
unstruct, unstructOk := src.(*unstructured.Unstructured)
aggregatedPhase := string(aggregateStatus(statusMap))
changed := false
if ok {
addonStatus := addonObject.GetCommonStatus()
if addonStatus.Phase != aggregatedPhase {
addonStatus.Phase = aggregatedPhase
addonObject.SetCommonStatus(addonStatus)
changed = true
}
} else if unstructOk {
statusPhase, _, err := unstructured.NestedString(unstruct.Object, "status", "phase")
if err != nil {
log.Error(err, "error retrieving status")
return err
}

if statusPhase != aggregatedPhase {
err := unstructured.SetNestedField(unstruct.Object, aggregatedPhase, "status", "phase")
if err != nil {
log.Error(err, "error retrieving status")
return err
}
changed = true
}
} else {
return fmt.Errorf("instance %T was not an addonsv1alpha1.CommonObject or unstructured."+
"Unstructured",
src)
}

if changed == true {
log.WithValues("name", src.GetName()).WithValues("phase", aggregatedPhase).Info("updating status")
err := k.client.Status().Update(ctx, src)
if err != nil {
log.Error(err, "error updating status")
return fmt.Errorf("error error status: %v", err)
}
}

return nil
}

func aggregateStatus(m map[status.Status]bool) status.Status {
inProgress := m[status.InProgressStatus]
terminating := m[status.TerminatingStatus]

failed := m[status.FailedStatus]

if inProgress || terminating {
return status.InProgressStatus
}

if failed {
return status.FailedStatus
}

return status.CurrentStatus
}
52 changes: 42 additions & 10 deletions pkg/patterns/addon/pkg/status/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package status
import (
"context"
"fmt"
"reflect"

"github.com/blang/semver"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
addonsv1alpha1 "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/apis/v1alpha1"
Expand Down Expand Up @@ -58,19 +60,49 @@ func (p *versionCheck) VersionCheck(
return true, nil
}

addonObject, ok := src.(addonsv1alpha1.CommonObject)
if !ok {
return false, fmt.Errorf("object %T was not an addonsv1alpha1.CommonObject", src)
errors := []string{
fmt.Sprintf("manifest needs operator version >= %v, this operator is version %v", minOperatorVersion.String(),
p.operatorVersion.String()),
}
unstruct, ok := src.(*unstructured.Unstructured)
addonObject, commonOkay := src.(addonsv1alpha1.CommonObject)

status := addonsv1alpha1.CommonStatus{
Healthy: false,
Errors: []string{
fmt.Sprintf("manifest needs operator version >= %v, this operator is version %v", minOperatorVersion.String(), p.operatorVersion.String()),
},
if ok {
unstructStatus := make(map[string]interface{})

s, _, err := unstructured.NestedMap(unstruct.Object, "status")
if err != nil {
log.Error(err, "getting status")
return false, fmt.Errorf("unable to get status from unstructured: %v", err)
}

unstructStatus = s
unstructStatus["Healthy"] = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should be healthy (and errors) also

unstructStatus["Errors"] = errors

if !reflect.DeepEqual(unstruct, s) {
err = unstructured.SetNestedField(unstruct.Object, false, "status", "healthy")
if err != nil {
log.Error(err, "unable to updating status in unstructured")
}

err = unstructured.SetNestedStringSlice(unstruct.Object, errors, "status", "errors")
if err != nil {
log.Error(err, "unable to updating status in unstructured")
}
}
} else if commonOkay {
status := addonObject.GetCommonStatus()
status.Healthy = false
status.Errors = errors
if !reflect.DeepEqual(status, addonObject.GetCommonStatus()) {
status.Healthy = false
status.Errors = errors
addonObject.SetCommonStatus(status)
}
} else {
return false, fmt.Errorf("instance %T was not an addonsv1alpha1.CommonObject or unstructured.Unstructured", src)
}
log.WithValues("name", addonObject.GetName()).WithValues("status", status).Info("updating status")
addonObject.SetCommonStatus(status)

return false, fmt.Errorf("operator not qualified, manifest needs operator version >= %v", minOperatorVersion.String())
}
49 changes: 5 additions & 44 deletions pkg/patterns/declarative/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@ import (

apierrors "k8s.io/apimachinery/pkg/api/errors"

"k8s.io/client-go/dynamic"
addonsv1alpha1 "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/apis/v1alpha1"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
recorder "k8s.io/client-go/tools/record"
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
Expand Down Expand Up @@ -208,7 +206,7 @@ func (r *Reconciler) reconcileExists(ctx context.Context, name types.NamespacedN
var newItems []*manifest.Object
for _, obj := range objects.Items {

unstruct, err := getObjectFromCluster(obj, r)
unstruct, err := GetObjectFromCluster(obj, r)
if err != nil && !apierrors.IsNotFound(err) {
log.WithValues("name", obj.Name).Error(err, "Unable to get resource")
}
Expand Down Expand Up @@ -269,50 +267,12 @@ func (r *Reconciler) reconcileExists(ctx context.Context, name types.NamespacedN
return reconcile.Result{}, fmt.Errorf("error applying manifest: %v", err)
}

statusMap := make(map[status.Status]bool)
for _, object := range objects.Items {

unstruct, err := getObjectFromCluster(object, r)
if err != nil {
return reconcile.Result{}, err
}

res, err := status.Compute(unstruct)
if err != nil {
log.WithValues("unstuctured", unstruct).Error(err, "Unable to get status of unstructured")
statusMap[status.NotFoundStatus] = true
}
if res != nil {
log.WithValues("kind", object.Kind).WithValues("name", object.Name).WithValues("status", res.Status).WithValues(
"message", res.Message).Info("Got status of resource:")
statusMap[res.Status] = true
}
}

addonObject, ok := instance.(addonsv1alpha1.CommonObject)
if !ok {
return reconcile.Result{}, fmt.Errorf("instance %T was not an addonsv1alpha1.CommonObject", instance)
}
status := addonObject.GetCommonStatus()
if status.Phase != string(aggregateStatus(statusMap)) {
status.Phase = string(aggregateStatus(statusMap))
addonObject.SetCommonStatus(status)
log.WithValues("name", addonObject.GetName()).WithValues("status", status).Info("updating status")

err = r.client.Status().Update(ctx, instance)
if err != nil {
log.Error(err, "error updating status")
return reconcile.Result{}, err
}
}

if r.options.sink != nil {
if err := r.options.sink.Notify(ctx, instance, objects); err != nil {
log.Error(err, "notifying sink")
return reconcile.Result{}, err
}
}

return reconcile.Result{}, nil
}

Expand Down Expand Up @@ -599,16 +559,17 @@ func aggregateStatus(m map[status.Status]bool) status.Status {
failed := m[status.FailedStatus]

if inProgress || terminating {
return status.TerminatingStatus
return status.InProgressStatus
}

if failed {
return status.FailedStatus
}

return status.CurrentStatus
}

func getObjectFromCluster(obj *manifest.Object, r *Reconciler) (*unstructured.
func GetObjectFromCluster(obj *manifest.Object, r *Reconciler) (*unstructured.
Unstructured, error) {
getOptions := metav1.GetOptions{}
gvk := obj.GroupVersionKind()
Expand Down