Code Monkey home page Code Monkey logo

operator-utils's Introduction

Operator Utility Library

build status GoDoc reference Go Report Card GitHub go.mod Go version

This library layers on top of the Operator SDK and with the objective of helping writing better and more consistent operators.

NOTICE versions of this library up to v0.3.7 are compatible with operator-sdk 0.x, starting from version v0.4.0 this library will be compatible only with operator-sdk 1.x.

Scope of this library

This library covers three main areas:

  1. Utility Methods Utility methods that are callable by any operator.
  2. Idempotent methods to manipulate resources and arrays of resources
  3. Basic operator lifecycle needs (validation, initialization, status and error management, finalization)
  4. Enforcing resources operator support. For those operators which calculate a set of resources that need to exist and then enforce them, generalized support for the enforcing phase is provided.

Utility Methods

Prior to version v1.3.x the general philosophy of this library was that new operator would inherit from ReconcilerBase and in doing so they would have access to a bunch of utility methods. With release v1.3.0 a new approach is available. Utility methods are callable by any operator without having to inherit. This makes it easier to use this library and does not conflict with autogenerate code from kube-builder and operator-sdk. Most of the Utility methods receive a context.Context parameter. Normally this context must be initialized with a logr.Logger and a rest.Config. Some utility methods may require more, see each individual documentation.

Utility methods are currently organized in the following folders:

  1. crud: idempotent create/update/delete functions.
  2. discoveryclient: methods related to the discovery client, typically used to load apiResource objects.
  3. dynamicclient: methods related to building client based on object whose type is not known at compile time.
  4. templates: utility methods for dealing with templates whose output is an object or a list of objects.

Idempotent Methods to Manipulate Resources

The following idempotent methods are provided (and their corresponding array version):

  1. createIfNotExists
  2. createOrUpdate
  3. deleteIfExists

Also there are utility methods to manage finalizers, test ownership and process templates of resources.

Basic Operator Lifecycle Management


Note

This part of the library is largely deprecated. For initialization and defaulting a MutatingWebHook should be used. For validation a Validating WebHook should be used. The part regarding the finalization is still relevant.


To get started with this library do the following:

Change your reconciler initialization as exemplified below to add a set of utility methods to it

import "github.com/redhat-cop/operator-utils/pkg/util"

...
type MyReconciler struct {
  util.ReconcilerBase
  Log logr.Logger
  ... other optional fields ...
}

in main.go change like this

  if err = (&controllers.MyReconciler{
    ReconcilerBase: util.NewReconcilerBase(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetEventRecorderFor("My_controller"), mgr.GetAPIReader()),
    Log:            ctrl.Log.WithName("controllers").WithName("My"),
  }).SetupWithManager(mgr); err != nil {
    setupLog.Error(err, "unable to create controller", "controller", "My")
    os.Exit(1)
  }

Also make sure to create the manager with configmap as the lease option for leader election:

  mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme:                     scheme,
    MetricsBindAddress:         metricsAddr,
    Port:                       9443,
    LeaderElection:             enableLeaderElection,
    LeaderElectionID:           "dcb036b8.redhat.io",
    LeaderElectionResourceLock: "configmaps",
  })

If you want status management, add this to your CRD:

  // +patchMergeKey=type
  // +patchStrategy=merge
  // +listType=map
  // +listMapKey=type
  Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

func (m *MyCRD) GetConditions() []metav1.Condition {
  return m.Status.Conditions
}

func (m *MyCRD) SetConditions(conditions []metav1.Condition) {
  m.Status.Conditions = conditions
}

At this point your controller is able to leverage the utility methods of this library:

  1. managing CR validation
  2. managing CR initialization
  3. managing status and error conditions
  4. managing CR finalization
  5. high-level object manipulation functions such as:
    • createOrUpdate, createIfNotExists, deleteIfExists
    • same functions on an array of objects
    • go template processing of objects

A full example is provided here

Managing CR validation

To enable CR validation add this to your controller:

if ok, err := r.IsValid(instance); !ok {
 return r.ManageError(ctx, instance, err)
}

The implement the following function:

func (r *ReconcileMyCRD) IsValid(obj metav1.Object) (bool, error) {
 mycrd, ok := obj.(*examplev1alpha1.MyCRD)
 ...
}

Managing CR Initialization

To enable CR initialization, add this to your controller:

if ok := r.IsInitialized(instance); !ok {
 err := r.GetClient().Update(context.TODO(), instance)
 if err != nil {
  log.Error(err, "unable to update instance", "instance", instance)
  return r.ManageError(ctx, instance, err)
 }
 return reconcile.Result{}, nil
}

Then implement the following function:

func (r *ReconcileMyCRD) IsInitialized(obj metav1.Object) bool {
 mycrd, ok := obj.(*examplev1alpha1.MyCRD)
}

Managing Status and Error Conditions

To update the status with success and return from the reconciliation cycle, code the following:

return r.ManageSuccess(ctx, instance)

To update the status with failure, record an event and return from the reconciliation cycle, code the following:

return r.ManageError(ctx, instance, err)

notice that this function will reschedule a reconciliation cycle with increasingly longer wait time up to six hours.

There are also variants of these calls to allow for requeuing after a given delay. Requeuing is handy when reconciliation depends on a cluster-external state which is not observable from within the api-server.

return r.ManageErrorWithRequeue(ctx, instance, err, 3*time.Second)
return r.ManageSuccessWithRequeue(ctx, instance, 3*time.Second)

or simply using the convenience function:

return r.ManageOutcomeWithRequeue(ctx, instance, err, 3*time.Second)

which will delegate to the error or success variant depending on err being nil or not.

Managing CR Finalization

to enable CR finalization add this to your controller:

if util.IsBeingDeleted(instance) {
 if !util.HasFinalizer(instance, controllerName) {
  return reconcile.Result{}, nil
 }
 err := r.manageCleanUpLogic(instance)
 if err != nil {
  log.Error(err, "unable to delete instance", "instance", instance)
  return r.ManageError(ctx, instance, err)
 }
 util.RemoveFinalizer(instance, controllerName)
 err = r.GetClient().Update(context.TODO(), instance)
 if err != nil {
  log.Error(err, "unable to update instance", "instance", instance)
  return r.ManageError(ctx, instance, err)
 }
 return reconcile.Result{}, nil
}

Then implement this method:

func (r *ReconcileMyCRD) manageCleanUpLogic(mycrd *examplev1alpha1.MyCRD) error {
  ...
}

Support for operators that need to enforce a set of resources to a defined state

Many operators have the following logic:

  1. Phase 1: based on the CR and potentially additional status, a set of resources that need to exist is calculated.
  2. Phase 2: These resources are then created or updated against the master API.
  3. Phase 3: A well written operator also ensures that these resources stay in place and are not accidentally or maliciously changed by third parties.

These phases are of increasing difficulty to implement. It's also true that phase 2 and 3 can be generalized.

Operator-utils offers some scaffolding to assist in writing these kinds of operators.

Similarly to the BaseReconciler class, we have a base type to extend called: EnforcingReconciler. This class extends from BaseReconciler, so you have all the same facilities as above.

When initializing the EnforcingReconciler, one must chose whether watchers will be created at the cluster level or at the namespace level.

  • if cluster level is chosen a watch per CR and type defined in it will be created. This will require the operator to have cluster level access.

  • if namespace level watchers is chosen a watch per CR, type and namespace will be created. This will minimize the needed permissions, but depending on what the operator needs to do may open a very high number of connections to the API server.

The body of the reconciler function will look something like this:

validation...
initialization...
(optional) finalization...
Phase1 ... calculate a set of resources to be enforced -> LockedResources

  err = r.UpdateLockedResources(context,instance, lockedResources, ...)
  if err != nil {
    log.Error(err, "unable to update locked resources")
    return r.ManageError(ctx, instance, err)
 }

  return r.ManageSuccess(ctx, instance)

this is all you have to do for basic functionality. For more details see the example the EnforcingReconciler will do the following:

  1. restore the resources to the desired stated if the are changed. Notice that you can exclude paths from being considered when deciding whether to restore a resource. As set of JSON Path can be passed together with the LockedResource. It is recommended to set these paths:

    1. .metadata
    2. .status
  2. restore resources when they are deleted.

The UpdateLockedResources will validate the input as follows:

  1. the passed resource must be defined in the current apiserver
  2. the passed resource must be syntactically compliant with the OpenAPI definition of the resource defined in the server.
  3. if the passed resource is namespaced, the namespace field must be initialized.

The finalization method will look like this:

func (r *ReconcileEnforcingCRD) manageCleanUpLogic(instance *examplev1alpha1.EnforcingCRD) error {
  err := r.Terminate(instance, true)
  if err != nil {
    log.Error(err, "unable to terminate enforcing reconciler for", "instance", instance)
    return err
  }
  ... additional finalization logic ...
  return nil
}

Convenience methods are also available for when resources are templated. See the templatedenforcingcrd controller as an example.

Support for operators that need to enforce a set of patches

For similar reasons stated in the previous paragraphs, operators might need to enforce patches. A patch modifies an object created by another entity. Because in this case the CR does not own the to-be-modified object a patch must be enforced against changes made on it. One must be careful not to create circular situations where an operator deletes the patch and this operator recreates the patch. In some situations, a patch must be parametric on some state of the cluster. For this reason, it's possible to monitor source objects that will be used as parameters to calculate the patch.

A patch is defined as follows:

type LockedPatch struct { 
  Name             string                           `json:"name,omitempty"`
  SourceObjectRefs []utilsapi.SourceObjectReference `json:"sourceObjectRefs,omitempty"`
  TargetObjectRef  utilsapi.TargetObjectReference   `json:"targetObjectRef,omitempty"`
  PatchType        types.PatchType                  `json:"patchType,omitempty"`
  PatchTemplate    string                           `json:"patchTemplate,omitempty"`
  Template         template.Template                `json:"-"`
}

the targetObjectRef and sourceObjectRefs are watched for changes by the reconciler.

targetObjectRef can select multiple objects, this is the logic

Namespaced Type Namespace Name Selection type
yes null null multiple selection across namespaces
yes null not null multiple selection across namespaces where the name corresponds to the passed name
yes not null null multiple selection within a namespace
yes not null not nul single selection
no N/A null multiple selection
no N/A not null single selection

Selection can be further narrowed down by filtering by labels and/or annotations. The patch will be applied to all of the selected instances.

Name and Namespace of sourceRefObjects are interpreted as golang templates with the current target instance and the only parameter. This allows to select different source object for each target object.

The relevant part of the operator code would look like this:

validation...
initialization...
Phase1 ... calculate a set of patches to be enforced -> LockedPatches

  err = r.UpdateLockedResources(context, instance, ..., lockedPatches...)
  if err != nil {
    log.Error(err, "unable to update locked resources")
    return r.ManageError(ctx, instance, err)
 }

  return r.ManageSuccess(ctx, instance)

The UpdateLockedResources will validate the input as follows:

  1. the passed patch target/source ObjectRef resource must be defined in the current apiserver
  2. if the passed patch target/source ObjectRef resources are namespaced the corresponding namespace field must be initialized.
  3. the ID must have a not null and unique value in the array of the passed patches.

Patches cannot be undone so there is no need to manage a finalizer.

Here you can find an example of how to implement an operator with this the ability to enforce patches.

Support for operators that need dynamic creation of locked resources using templates

Operators may also need to leverage locked resources created dynamically through templates. This can be done using go templates and leveraging the GetLockedResourcesFromTemplates function.

lockedResources, err := r.GetLockedResourcesFromTemplates(templates..., params...)
if err != nil {
  log.Error(err, "unable to process templates with param")
  return err
}

The GetLockedResourcesFromTemplates will validate the input as follows:

  1. check that the passed template is valid
  2. format the template using the properties of the passed object in the params parameter
  3. create an array of LockedResource objects based on parsed template

The example below shows how templating can be used to reference the name of the resource passed as the parameter and use it as a property in the creation of the LockedResource.

objectTemplate: |
  apiVersion: v1
  kind: Namespace
  metadata:
    name: {{ .Name }}

This functionality can leverage advanced features of go templating, such as loops, to generate more than one object following a set pattern. The below example will create an array of namespace LockedResources using the title of any key where the associated value matches the text devteam in the key/value pair of the Labels property of the resource passed in the params parameter.

objectTemplate: |
  {{range $key, $value := $.Labels}}
    {{if eq $value "devteam"}}
      - apiVersion: v1
        kind: Namespace
        metadata:
          name: {{ $key }}
    {{end}}
  {{end}}

Support for operators that need advanced templating functionality

Operators may need to utilize advanced templating functions not found in the base go templating library. This advanced template functionality matches the same available in the popular k8s management tool Helm. LockedPatch templates uses this functionality by default. To utilize these features when using LockedResources the following function is required,

lockedResources, err := r.GetLockedResourcesFromTemplatesWithRestConfig(templates..., rest.Config..., params...)
if err != nil {
  log.Error(err, "unable to process templates with param")
  return err
}

Deployment

Deploying with Helm

Here are the instructions to install the latest release with Helm.

oc new-project operator-utils
helm repo add operator-utils https://redhat-cop.github.io/operator-utils
helm repo update
helm install operator-utils operator-utils/operator-utils

This can later be updated with the following commands:

helm repo update
helm upgrade operator-utils operator-utils/operator-utils

Development

Running the operator locally

make install
oc new-project operator-utils-operator-local
kustomize build ./config/local-development | oc apply -f - -n operator-utils-operator-local
export token=$(oc serviceaccounts get-token 'operator-utils-operator-controller-manager' -n operator-utils-operator-local)
oc login --token ${token}
make run ENABLE_WEBHOOKS=false

testing

Patches

oc new-project patch-test
oc create sa test -n patch-test
oc adm policy add-cluster-role-to-user cluster-admin -z default -n patch-test
oc apply -f ./test/enforcing-patch.yaml -n patch-test
oc apply -f ./test/enforcing-patch-multiple.yaml -n patch-test
oc apply -f ./test/enforcing-patch-multiple-cluster-level.yaml -n patch-test

Building/Pushing the operator image

export repo=raffaelespazzoli #replace with yours
docker login quay.io/$repo
make docker-build IMG=quay.io/$repo/operator-utils:latest
make docker-push IMG=quay.io/$repo/operator-utils:latest

Deploy to OLM via bundle

make manifests
make bundle IMG=quay.io/$repo/operator-utils:latest
operator-sdk bundle validate ./bundle --select-optional name=operatorhub
make bundle-build BUNDLE_IMG=quay.io/$repo/operator-utils-bundle:latest
docker push quay.io/$repo/operator-utils-bundle:latest
operator-sdk bundle validate quay.io/$repo/operator-utils-bundle:latest --select-optional name=operatorhub
oc new-project operator-utils
oc label namespace operator-utils openshift.io/cluster-monitoring="true"
operator-sdk cleanup operator-utils -n operator-utils
operator-sdk run bundle --install-mode AllNamespaces -n operator-utils quay.io/$repo/operator-utils-bundle:latest

Releasing

git tag -a "<tagname>" -m "<commit message>"
git push upstream <tagname>

If you need to remove a release:

git tag -d <tagname>
git push upstream --delete <tagname>

If you need to "move" a release to the current main

git tag -f <tagname>
git push upstream -f <tagname>

Cleaning up

operator-sdk cleanup operator-utils -n operator-utils
oc delete operatorgroup operator-sdk-og
oc delete catalogsource operator-utils-catalog

operator-utils's People

Contributors

brianwilkinson avatar davgordo avatar davidkarlsen avatar garrettthomaskth avatar grdryn avatar hrobertson avatar iaincalderfh avatar nakamasato avatar raffaelespazzoli avatar roivaz avatar sabre1041 avatar shirsa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

operator-utils's Issues

Fix handling non-cluster level watchers' delete events

When setting clusterWatchers to false, the resource-reconciler's client will be initialized with MultiNamespacedCacheBuilder.
Thus, when querying for the object's namespace in Delete event predicate, it will always fail (since Namespace key is empty).
A fix for this problem can be using non-cached client for this query.

Create Programatic ServiceMonitor Resource

Most CoP operators currently include a ServiceMonitor to enable monitoring of the operator. This capability functions appropriately if the operator is deployed to the suggested namespace. However, due to limitations in the Operator Framework, additional resources packaged within an Operator Bundle can only be statically defined ad cannot take advantage of adapting to the current operating environment.

The suggested approach is to include logic within an operaator to create a ServiceMonitor resource where it will then be able to take advantage of properly manage the configuration of the resource.

  • Develop a reusable function that can be invoked from consuming operators to create ServiceMonitor resources

prevent possible race condition

when reading a writing the status of a locked resource there is a chance for a race condition. Add a semaphore to prevent it.

Build errors when adding operator-utils to a new operator-sdk v1.2 generated project

I generated a new operator with operator-sdk init and used operator-sdk create api
and built it with make with no errors

I am using operator-sdk v1.2

But once I add github.com/redhat-cop/operator-utils v0.3.7 to go.mod I get these build errors

k8s.io/client-go/tools/clientcmd/api/v1 /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:29:15: scheme.AddConversionFuncs undefined (type *runtime.Scheme has no field or method AddConversionFuncs) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:31:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:34:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:37:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:40:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:43:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:46:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:49:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) /home/avrahams/go/pkg/mod/k8s.io/[email protected]+incompatible/tools/clientcmd/api/v1/conversion.go:52:12: s.DefaultConvert undefined (type conversion.Scope has no field or method DefaultConvert) sigs.k8s.io/controller-runtime/pkg/metrics /home/avrahams/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/metrics/client_go_adapter.go:133:24: not enough arguments in call to metrics.Register /home/avrahams/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/metrics/client_go_adapter.go:133:25: undefined: metrics.RegisterOpts

The original go.mod that was generated by the operator-sdk was
`require (

github.com/go-logr/logr v0.1.0

github.com/onsi/ginkgo v1.12.1

github.com/onsi/gomega v1.10.1

k8s.io/apimachinery v0.18.6

k8s.io/client-go v0.18.6

sigs.k8s.io/controller-runtime v0.6.3

)`

ManageErrorWithRequeue Requeue is ignored

Hi !

I am using your library as it helps a lot.

github.com/redhat-cop/operator-utils v1.1.2

However I face an issue with ManageErrorWithRequeue

It does re queue but not it does not take into account the re queue after which seems "normal" considering the code of the reconcile function.

If there is an error it re schedule into the loop immediately.

	// RunInformersAndControllers the syncHandler, passing it the namespace/Name string of the
	// resource to be synced.
	if result, err := c.Do.Reconcile(ctx, req); err != nil {
		c.Queue.AddRateLimited(req)
		ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()
		ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "error").Inc()
		log.Error(err, "Reconciler error")
		return
	} else if result.RequeueAfter > 0 {
		// The result.RequeueAfter request will be lost, if it is returned
		// along with a non-nil error. But this is intended as
		// We need to drive to stable reconcile loops before queuing due
		// to result.RequestAfter
		c.Queue.Forget(obj)
		c.Queue.AddAfter(req, result.RequeueAfter)
		ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "requeue_after").Inc()
		return
	} else if result.Requeue {
		c.Queue.AddRateLimited(req)
		ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, "requeue").Inc()
		return
	}

Am I missing something?

Regards,

Allow for custom tempating by consuming operator

An operator consuming this library may want to add additional templating functions that are currently not covered with the go/sprig templating. Add a design pattern that allows a consumer to add to the template functionality, or override the current.

add locked resource validation

when a locked resource is created, it should be validated:

  1. the unstructured object is a valid Kube resources
  2. the resource type is actually installed in the current kube api server

Vulnerability with protobuf v.1.3.1

Hi !

There is a vulnerability with the version of gogo protobuf v1.3.1.

From what I saw the controller runtime is aware and will fix it in version v0.9.0.

kubernetes-sigs/controller-runtime#1473

Are you planning on updating this repo when its out ?

I can do a PR once they release it to bump the version if you don't see any conflict with your library.

Regards,

Ayrton

ManageSuccess and ManageError reset ALL conditions in Status?!

OK, this is super unexpected.
I've been trying for a long time to figure out why the conditions I've been using to record the state of my reconciliation process always seem to go missing. Eventually I realised that ManageSuccess and ManageError have the effect of wiping all conditions, via a call to SetReconcileStatus. This should be using things like status.RemoveCondition, status.SetCondition and so on to ensure that other condition instances don't get altered.

EnforcingReconciler doesn't update the existing resources according to the new lockedresources

I encountered this issue when I attempted reconciling by modifying the CR instance, which in turn changed the LockedResource.
For an understanding of this particular situation, suppose one of the lockedresource is StatefulSet and the operator increased the number of replicas in its spec.
Now, it's expected that on reconcilation, the LockedResourceManager will know there's some difference in the locked resource and do the needed change. However, that doesn't actually happen, rather it considers the new set of resources to be same here (sameResources is true):

sameResources, leftDifference, _, _ := lockedResourceManager.IsSameResources(lockedResources)

It's probably since it uses the GroupVersionKind, Namespace and Name as part of the key in the lockedresourceset:


Using hash of the Unstructured object as the key of the set would fix it, but I'm not sure if it is supposed to be a part of the current operator-utils design?

feature: add optional `requeueAfter` to manageError/manageSuccess

Sometimes one would want to requeue after a given internal, typically when reconciliation depends on cluster-external resources.
Could a requeueAfter parameter be added to these methods to support that?
That would avoid boilerplate like:

func (r *GithubActionRunnerReconciler) manageOutcome(ctx context.Context, instance* garov1alpha1.GithubActionRunner, issue error) (reconcile.Result, error) {
	var result reconcile.Result
	var err error
	if issue != nil {
		result, err = r.ManageError(ctx, instance, issue)
	} else {
		result, err = r.ManageSuccess(ctx, instance)
	}
	result.RequeueAfter = instance.Spec.GetReconciliationPeriod()

	return result, err
}

Add a license to make its open source terms explicit

I believe that without being made available under the terms of a given license, code is not really open source. I'll happily create a PR to add it if that saves you some effort, but I'm not going to do anything until I know what license you want to make it available it under. I'd suggest Apache 2.0. ๐Ÿ™‚

E2E Tests with CI/CD

This library is used by some critical operators in the operatorhub library. With that, I'm suggesting we have a set of E2E tests linked to a CI/CD pipeline that can validate changes to the operator, and can also run when new versions of k8s/ocp are released to validate and test for failures early in platform life cycle.

Please assign to me

refactor to use runtime.object as opposed to metav1.object

runtime.object is more correct and should have been used since the beginning to as a type variable for a generic resource on which actions are being taken. Many methods here erroneously use metav1.object.
Also review and normalize the api for operations on array of objects

test looks strange, clarify

this part of the tests looks strange, could you clarify on the intention of the code?

err = operatorutilsv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = operatorutilsv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = operatorutilsv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = operatorutilsv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

/kind documentation

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.