From c7c2f14439283d386d42573180758ea7e3248457 Mon Sep 17 00:00:00 2001 From: Eoin Gallinagh Date: Wed, 3 Jan 2024 17:34:07 +0000 Subject: [PATCH 1/2] add: Hypershift Functionality --- controllers/appwrapper_controller.go | 115 ++++++++++++++++------ controllers/machinepools.go | 82 +--------------- controllers/nodepools.go | 139 +++++++++++++++++++++++++++ controllers/ocm_manager.go | 77 +++++++++++++++ controllers/utils.go | 19 +++- 5 files changed, 317 insertions(+), 115 deletions(-) create mode 100644 controllers/nodepools.go create mode 100644 controllers/ocm_manager.go diff --git a/controllers/appwrapper_controller.go b/controllers/appwrapper_controller.go index baa991e..5c751e5 100644 --- a/controllers/appwrapper_controller.go +++ b/controllers/appwrapper_controller.go @@ -18,10 +18,11 @@ package controllers import ( "context" - "fmt" "strings" "time" + ocmsdk "github.com/openshift-online/ocm-sdk-go" + "github.com/project-codeflare/instascale/pkg/config" arbv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1" corev1 "k8s.io/api/core/v1" @@ -37,15 +38,19 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) +type MachineType string + // AppWrapperReconciler reconciles a AppWrapper object type AppWrapperReconciler struct { client.Client - Scheme *runtime.Scheme - Config config.InstaScaleConfiguration - kubeClient *kubernetes.Clientset - ocmClusterID string - ocmToken string - useMachineSets bool + Scheme *runtime.Scheme + Config config.InstaScaleConfiguration + kubeClient *kubernetes.Clientset + ocmClusterID string + ocmToken string + ocmConnection *ocmsdk.Connection + MachineType MachineType + machineCheck bool } var ( @@ -54,9 +59,12 @@ var ( ) const ( - namespaceToList = "openshift-machine-api" - minResyncPeriod = 10 * time.Minute - finalizerName = "instascale.codeflare.dev/finalizer" + namespaceToList = "openshift-machine-api" + minResyncPeriod = 10 * time.Minute + finalizerName = "instascale.codeflare.dev/finalizer" + MachineTypeMachineSet MachineType = "MachineSet" + MachineTypeMachinePool MachineType = "MachinePool" + MachineTypeNodePool MachineType = "NodePool" ) // +kubebuilder:rbac:groups=workload.codeflare.dev,resources=appwrappers,verbs=get;list;watch;create;update;patch;delete @@ -81,14 +89,17 @@ const ( // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) - // todo: Move the getOCMClusterID call out of reconcile loop. + // todo: Move the setMachineType call out of reconcile loop. // Only reason we are calling it here is that the client is not able to make // calls until it is started, so SetupWithManager is not working. - if !r.useMachineSets && r.ocmClusterID == "" { - if err := r.getOCMClusterID(ctx); err != nil { - return ctrl.Result{Requeue: true, RequeueAfter: timeFiveSeconds}, err + if r.machineCheck == false && r.MachineType != MachineTypeMachineSet { + if err := r.setMachineType(ctx); err != nil { + return ctrl.Result{}, err } } + + r.machineCheck = true + var appwrapper arbv1.AppWrapper if err := r.Get(ctx, req.NamespacedName, &appwrapper); err != nil { @@ -125,8 +136,14 @@ func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request) if status == "Pending" && containsInsufficientCondition(allconditions) { demandPerInstanceType := r.discoverInstanceTypes(ctx, &appwrapper) if ocmSecretRef := r.Config.OCMSecretRef; ocmSecretRef != nil { - return r.scaleMachinePool(ctx, &appwrapper, demandPerInstanceType) + switch r.MachineType { + case MachineTypeNodePool: + return r.scaleNodePool(ctx, &appwrapper, demandPerInstanceType) + case MachineTypeMachinePool: + return r.scaleMachinePool(ctx, &appwrapper, demandPerInstanceType) + } } else { + // use MachineSets switch strings.ToLower(r.Config.MachineSetsStrategy) { case "reuse": return r.reconcileReuseMachineSet(ctx, &appwrapper, demandPerInstanceType) @@ -146,7 +163,8 @@ func (r *AppWrapperReconciler) finalizeScalingDownMachines(ctx context.Context, } else { deletionMessage = "deleted" } - if r.useMachineSets { + switch r.MachineType { + case MachineTypeMachineSet: switch strings.ToLower(r.Config.MachineSetsStrategy) { case "reuse": matchedAw := r.findExactMatch(ctx, appwrapper) @@ -158,6 +176,9 @@ func (r *AppWrapperReconciler) finalizeScalingDownMachines(ctx context.Context, "newAppWrapper", matchedAw, ) if err := r.swapNodeLabels(ctx, appwrapper, matchedAw); err != nil { + logger.Error(err, "Error swapping node labels for AppWrapper", + "appwrapper", appwrapper, + ) return err } } else { @@ -167,6 +188,9 @@ func (r *AppWrapperReconciler) finalizeScalingDownMachines(ctx context.Context, "deletionMessage", deletionMessage, ) if err := r.annotateToDeleteMachine(ctx, appwrapper); err != nil { + logger.Error(err, "Error annotating to delete machine for AppWrapper", + "appwrapper", appwrapper, + ) return err } } @@ -177,19 +201,53 @@ func (r *AppWrapperReconciler) finalizeScalingDownMachines(ctx context.Context, "deletionMessage", deletionMessage, ) if err := r.deleteMachineSet(ctx, appwrapper); err != nil { + logger.Error(err, "Error deleting MachineSet for AppWrapper", + "appwrapper", appwrapper) return err } } - } else { + case MachineTypeNodePool: + logger.Info( + "AppWrapper deleted, scaling down nodepool", + "appWrapper", appwrapper, + "deletionMessage", deletionMessage, + ) + if _, err := r.deleteNodePool(ctx, appwrapper); err != nil { + logger.Error(err, "Error deleting NodePool for AppWrapper", + "appwrapper", appwrapper) + return err + } + + case MachineTypeMachinePool: logger.Info( "AppWrapper deleted, scaling down machine pool", "appWrapper", appwrapper, "deletionMessage", deletionMessage, ) if _, err := r.deleteMachinePool(ctx, appwrapper); err != nil { + logger.Error(err, "Error deleting MachinePool for AppWrapper", + "appwrapper", appwrapper) + return err + } + } + return nil +} + +func (r *AppWrapperReconciler) setMachineType(ctx context.Context) error { + logger := ctrl.LoggerFrom(ctx) + if r.ocmClusterID == "" { + if err := r.getOCMClusterID(ctx); err != nil { return err } } + hypershiftEnabled, err := r.checkHypershiftEnabled(ctx) + if err != nil { + logger.Error(err, "error checking if hypershift is enabled") + return err + } + if hypershiftEnabled { + r.MachineType = MachineTypeNodePool + } return nil } @@ -206,20 +264,19 @@ func (r *AppWrapperReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma } maxScaleNodesAllowed = int(r.Config.MaxScaleoutAllowed) - r.useMachineSets = true + r.MachineType = MachineTypeMachineSet // default to MachineSet if ocmSecretRef := r.Config.OCMSecretRef; ocmSecretRef != nil { - r.useMachineSets = false if ocmSecret, err := r.getOCMSecret(ctx, ocmSecretRef); err != nil { - return fmt.Errorf("error reading OCM Secret from ref %q: %w", ocmSecretRef, err) + logger.Error(err, "error reading OCM Secret from ref", + "ref", ocmSecretRef) + return err } else if token := ocmSecret.Data["token"]; len(token) > 0 { r.ocmToken = string(token) + r.MachineType = MachineTypeMachinePool } else { - return fmt.Errorf("token is missing from OCM Secret %q", ocmSecretRef) - } - if ok, err := r.machinePoolExists(); err != nil { + logger.Error(err, "token is missing from OCM Secret", + "ref", ocmSecretRef) return err - } else if ok { - logger.Info("Using machine pools for cluster auto-scaling") } } @@ -235,12 +292,8 @@ func (r *AppWrapperReconciler) getOCMSecret(ctx context.Context, secretRef *core func (r *AppWrapperReconciler) discoverInstanceTypes(ctx context.Context, aw *arbv1.AppWrapper) map[string]int { logger := ctrl.LoggerFrom(ctx) demandMapPerInstanceType := make(map[string]int) - var instanceRequired []string - for k, v := range aw.Labels { - if k == "orderedinstance" { - instanceRequired = strings.Split(v, "_") - } - } + + instanceRequired := getInstanceRequired(aw.Labels) if len(instanceRequired) < 1 { logger.Info( diff --git a/controllers/machinepools.go b/controllers/machinepools.go index dabd90a..95e3f86 100644 --- a/controllers/machinepools.go +++ b/controllers/machinepools.go @@ -2,45 +2,14 @@ package controllers import ( "context" - "fmt" "strings" - ocmsdk "github.com/openshift-online/ocm-sdk-go" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" - configv1 "github.com/openshift/api/config/v1" arbv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" ) -func (r *AppWrapperReconciler) createOCMConnection() (*ocmsdk.Connection, error) { - logger, err := ocmsdk.NewGoLoggerBuilder(). - Debug(false). - Build() - if err != nil { - return nil, fmt.Errorf("can't build logger: %v", err) - } - - connection, err := ocmsdk.NewConnectionBuilder(). - Logger(logger). - Tokens(r.ocmToken). - Build() - if err != nil { - return nil, fmt.Errorf("can't build connection: %v", err) - } - - return connection, nil -} - -func hasAwLabel(machinePool *cmv1.MachinePool, aw *arbv1.AppWrapper) bool { - value, ok := machinePool.Labels()[aw.Name] - if ok && value == aw.Name { - return true - } - return false -} - func (r *AppWrapperReconciler) scaleMachinePool(ctx context.Context, aw *arbv1.AppWrapper, demandPerInstanceType map[string]int) (ctrl.Result, error) { logger := ctrl.LoggerFrom(ctx) connection, err := r.createOCMConnection() @@ -61,7 +30,7 @@ func (r *AppWrapperReconciler) scaleMachinePool(ctx context.Context, aw *arbv1.A numberOfMachines := 0 response.Items().Each(func(machinePool *cmv1.MachinePool) bool { - if machinePool.InstanceType() == userRequestedInstanceType && hasAwLabel(machinePool, aw) { + if machinePool.InstanceType() == userRequestedInstanceType && hasAwLabel(machinePool.Labels(), aw) { numberOfMachines = machinePool.Replicas() return false } @@ -133,52 +102,3 @@ func (r *AppWrapperReconciler) deleteMachinePool(ctx context.Context, aw *arbv1. }) return ctrl.Result{Requeue: false}, nil } - -func (r *AppWrapperReconciler) machinePoolExists() (bool, error) { - connection, err := r.createOCMConnection() - if err != nil { - return false, fmt.Errorf("error creating OCM connection: %w", err) - } - defer connection.Close() - - machinePools := connection.ClustersMgmt().V1().Clusters().Cluster(r.ocmClusterID).MachinePools() - return machinePools != nil, nil -} - -// getOCMClusterID determines the internal clusterID to be used for OCM API calls -func (r *AppWrapperReconciler) getOCMClusterID(ctx context.Context) error { - logger := ctrl.LoggerFrom(ctx) - cv := &configv1.ClusterVersion{} - err := r.Get(ctx, types.NamespacedName{Name: "version"}, cv) - if err != nil { - return fmt.Errorf("can't get clusterversion: %v", err) - } - - internalClusterID := string(cv.Spec.ClusterID) - - connection, err := r.createOCMConnection() - if err != nil { - logger.Error(err, "Error creating OCM connection") - } - defer connection.Close() - - // Get the client for the resource that manages the collection of clusters: - collection := connection.ClustersMgmt().V1().Clusters() - - response, err := collection.List().Search(fmt.Sprintf("external_id = '%s'", internalClusterID)).Size(1).Page(1).SendContext(ctx) - if err != nil { - logger.Error(err, "Error getting cluster id") - } - - response.Items().Each(func(cluster *cmv1.Cluster) bool { - r.ocmClusterID = cluster.ID() - logger.Info( - "Cluster Info", - "clusterId", cluster.ID(), - "clusterName", cluster.Name(), - "clusterState", cluster.State(), - ) - return true - }) - return nil -} diff --git a/controllers/nodepools.go b/controllers/nodepools.go new file mode 100644 index 0000000..0e862bc --- /dev/null +++ b/controllers/nodepools.go @@ -0,0 +1,139 @@ +package controllers + +import ( + "context" + "strings" + + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + arbv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1" + + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *AppWrapperReconciler) scaleNodePool(ctx context.Context, aw *arbv1.AppWrapper, demandPerInstanceType map[string]int) (ctrl.Result, error) { + logger := ctrl.LoggerFrom(ctx) + connection, err := r.createOCMConnection() + if err != nil { + logger.Error(err, "Error creating OCM connection") + return ctrl.Result{}, err + } + defer connection.Close() + for userRequestedInstanceType := range demandPerInstanceType { + replicas := demandPerInstanceType[userRequestedInstanceType] + + clusterNodePools := connection.ClustersMgmt().V1().Clusters().Cluster(r.ocmClusterID).NodePools() + + response, err := clusterNodePools.List().SendContext(ctx) + if err != nil { + return ctrl.Result{}, err + } + + numberOfMachines := 0 + response.Items().Each(func(nodePool *cmv1.NodePool) bool { + if nodePool.AWSNodePool().InstanceType() == userRequestedInstanceType && hasAwLabel(nodePool.Labels(), aw) { + numberOfMachines = nodePool.Replicas() + return false + } + return true + }) + + if numberOfMachines != replicas { + m := make(map[string]string) + m[aw.Name] = aw.Name + logger.Info("The instanceRequired array", + "InstanceRequired", userRequestedInstanceType) + + nodePoolID := strings.ReplaceAll(aw.Name+"-"+userRequestedInstanceType, ".", "-") + + createNodePool, err := cmv1.NewNodePool().AWSNodePool(cmv1.NewAWSNodePool().InstanceType(userRequestedInstanceType)).ID(nodePoolID).Replicas(replicas).Labels(m).Build() + if err != nil { + logger.Error( + err, "Error building NodePool", + "userRequestedInstanceType", userRequestedInstanceType, + ) + } + logger.Info( + "Sending NodePool creation request", + "instanceType", userRequestedInstanceType, + "nodePoolName", createNodePool.ID(), + ) + response, err := clusterNodePools.Add().Body(createNodePool).SendContext(ctx) + if err != nil { + logger.Error(err, "Error creating NodePool") + } else { + logger.Info( + "Successfully created NodePool", + "nodePoolName", createNodePool.ID(), + "response", response, + ) + } + } + } + return ctrl.Result{Requeue: false}, nil +} + +func (r *AppWrapperReconciler) deleteNodePool(ctx context.Context, aw *arbv1.AppWrapper) (ctrl.Result, error) { + logger := ctrl.LoggerFrom(ctx) + connection, err := r.createOCMConnection() + if err != nil { + logger.Error(err, "Error creating OCM connection") + return ctrl.Result{}, err + } + defer connection.Close() + + nodePoolsConnection := connection.ClustersMgmt().V1().Clusters().Cluster(r.ocmClusterID).NodePools().List() + + nodePoolsListResponse, _ := nodePoolsConnection.Send() + nodePoolsList := nodePoolsListResponse.Items() + nodePoolsList.Range(func(index int, item *cmv1.NodePool) bool { + id, _ := item.GetID() + if strings.Contains(id, aw.Name) { + targetNodePool, err := connection.ClustersMgmt().V1().Clusters().Cluster(r.ocmClusterID).NodePools().NodePool(id).Delete().SendContext(ctx) + if err != nil { + logger.Error( + err, "Error deleting nodepool", + "nodePool", targetNodePool, + ) + } else { + logger.Info( + "Successfully scaled down target nodepool", + "nodePool", targetNodePool, + ) + } + } + return true + }) + return ctrl.Result{Requeue: false}, nil +} + +func (r *AppWrapperReconciler) checkHypershiftEnabled(ctx context.Context) (bool, error) { + logger := ctrl.LoggerFrom(ctx) + connection, err := r.createOCMConnection() + if err != nil { + logger.Error(err, "Error creating OCM connection") + return false, err + } + defer connection.Close() + + clusterResource := connection.ClustersMgmt().V1().Clusters().Cluster(r.ocmClusterID) + + response, err := clusterResource.Get().SendContext(ctx) + if err != nil { + logger.Error(err, "error fetching cluster details") + return false, err + } + + body := response.Body() + if body == nil { + logger.Error(err, "Empty resource body when checking Hypershift enabled status") + return false, err + } + + hypershiftEnabled := false + if body.Hypershift() != nil { + hypershiftEnabled = body.Hypershift().Enabled() + } + + logger.Info("Checked Hypershift enabled status", "status", hypershiftEnabled) + return hypershiftEnabled, nil +} diff --git a/controllers/ocm_manager.go b/controllers/ocm_manager.go new file mode 100644 index 0000000..c4f8789 --- /dev/null +++ b/controllers/ocm_manager.go @@ -0,0 +1,77 @@ +package controllers + +import ( + "context" + "fmt" + ocmsdk "github.com/openshift-online/ocm-sdk-go" + cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + configv1 "github.com/openshift/api/config/v1" + "k8s.io/apimachinery/pkg/types" + + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *AppWrapperReconciler) createOCMConnection() (*ocmsdk.Connection, error) { + logger, err := ocmsdk.NewGoLoggerBuilder(). + Debug(false). + Build() + if err != nil { + return nil, fmt.Errorf("can't build logger: %v", err) + } + + connection, err := ocmsdk.NewConnectionBuilder(). + Logger(logger). + Tokens(r.ocmToken). + Build() + if err != nil { + return nil, fmt.Errorf("can't build connection: %v", err) + } + + r.ocmConnection = connection + return r.ocmConnection, nil +} + +// getOCMClusterID determines the internal clusterID to be used for OCM API calls +func (r *AppWrapperReconciler) getOCMClusterID(ctx context.Context) error { + logger := ctrl.LoggerFrom(ctx) + cv := &configv1.ClusterVersion{} + err := r.Get(ctx, types.NamespacedName{Name: "version"}, cv) + if err != nil { + logger.Error(err, "can't get clusterversion") + return err + } + + internalClusterID := string(cv.Spec.ClusterID) + + connection, err := r.createOCMConnection() + if err != nil { + logger.Error(err, "Error creating OCM connection") + } + defer connection.Close() + + // Get the client for the resource that manages the collection of clusters: + collection := connection.ClustersMgmt().V1().Clusters() + + response, err := collection.List().Search(fmt.Sprintf("external_id = '%s'", internalClusterID)).Size(1).Page(1).SendContext(ctx) + if err != nil { + logger.Error(err, "Error getting cluster id") + } + + response.Items().Each(func(cluster *cmv1.Cluster) bool { + r.ocmClusterID = cluster.ID() + logger.Info( + "Cluster Info", + "clusterId", cluster.ID(), + "clusterName", cluster.Name(), + "clusterState", cluster.State(), + ) + return true + }) + return nil +} + +func (r *AppWrapperReconciler) Close() { + if r.ocmConnection != nil { + r.ocmConnection.Close() + } +} diff --git a/controllers/utils.go b/controllers/utils.go index ceb3c52..02be8d5 100644 --- a/controllers/utils.go +++ b/controllers/utils.go @@ -4,16 +4,24 @@ import ( "context" "encoding/json" "fmt" - "math/rand" - "strings" - "time" machinev1 "github.com/openshift/api/machine/v1beta1" arbv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" + + "math/rand" + "strings" + "time" ) +func getInstanceRequired(labels map[string]string) []string { + if value, exists := labels["orderedinstance"]; exists { + return strings.Split(value, "_") + } + return []string{} +} + func resyncPeriod() func() time.Duration { return func() time.Duration { factor := rand.Float64() + 1 @@ -58,3 +66,8 @@ func contains(s []string, str string) bool { return false } + +func hasAwLabel(labels map[string]string, aw *arbv1.AppWrapper) bool { + value, ok := labels[aw.Name] + return ok && value == aw.Name +} From 76dd2e23b686c07e6323fd66ae73c3f5ce3f5fe0 Mon Sep 17 00:00:00 2001 From: Eoin Gallinagh Date: Mon, 8 Jan 2024 11:55:45 +0000 Subject: [PATCH 2/2] add: license headers to controllers package. --- controllers/appWrapper_controller_test.go | 16 ++++++++++++++++ controllers/machinepools.go | 16 ++++++++++++++++ controllers/machineset.go | 16 ++++++++++++++++ controllers/nodepools.go | 16 ++++++++++++++++ controllers/ocm_manager.go | 16 ++++++++++++++++ controllers/utils.go | 16 ++++++++++++++++ controllers/utils_test.go | 16 ++++++++++++++++ 7 files changed, 112 insertions(+) diff --git a/controllers/appWrapper_controller_test.go b/controllers/appWrapper_controller_test.go index b0bcc07..1753017 100644 --- a/controllers/appWrapper_controller_test.go +++ b/controllers/appWrapper_controller_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import ( diff --git a/controllers/machinepools.go b/controllers/machinepools.go index 95e3f86..db740df 100644 --- a/controllers/machinepools.go +++ b/controllers/machinepools.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import ( diff --git a/controllers/machineset.go b/controllers/machineset.go index c47ff10..f2e3a2c 100644 --- a/controllers/machineset.go +++ b/controllers/machineset.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import ( diff --git a/controllers/nodepools.go b/controllers/nodepools.go index 0e862bc..f3fbe0f 100644 --- a/controllers/nodepools.go +++ b/controllers/nodepools.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import ( diff --git a/controllers/ocm_manager.go b/controllers/ocm_manager.go index c4f8789..e105a6d 100644 --- a/controllers/ocm_manager.go +++ b/controllers/ocm_manager.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import ( diff --git a/controllers/utils.go b/controllers/utils.go index 02be8d5..85852df 100644 --- a/controllers/utils.go +++ b/controllers/utils.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import ( diff --git a/controllers/utils_test.go b/controllers/utils_test.go index 414c253..f98bb1c 100644 --- a/controllers/utils_test.go +++ b/controllers/utils_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +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 controllers import (