diff --git a/api/v1alpha1/resourcemanager_types.go b/api/v1alpha1/resourcemanager_types.go index e2736c3..4732aa5 100644 --- a/api/v1alpha1/resourcemanager_types.go +++ b/api/v1alpha1/resourcemanager_types.go @@ -38,21 +38,29 @@ type ResourceManagerSpec struct { // TODO: add validation + enum Action string `json:"action"` - Condition []ExpiryCondition `json:"condition"` + Condition []Condition `json:"condition"` } type Condition struct { + ExpiryCondition `json:",inline,omitempty"` + TimeframeCondition `json:",inline,omitempty"` + BaseCondition `json:",inline"` +} + +type BaseCondition struct { Type string `json:"type"` } type ExpiryCondition struct { - Condition `json:",inline"` - After string `json:"after"` + BaseCondition `json:",inline"` + After string `json:"after,omitempty"` } -// type IntervalCondition struct { -// Condition -// } +type TimeframeCondition struct { + BaseCondition `json:",inline"` + Timeframe string `json:"time, omitempty"` + //Timezone string `json:"timeZone"` +} // ResourceManagerStatus defines the observed state of ResourceManager type ResourceManagerStatus struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 77a1abc..bc9d3ae 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -16,9 +16,27 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BaseCondition) DeepCopyInto(out *BaseCondition) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseCondition. +func (in *BaseCondition) DeepCopy() *BaseCondition { + if in == nil { + return nil + } + out := new(BaseCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in + out.ExpiryCondition = in.ExpiryCondition + out.TimeframeCondition = in.TimeframeCondition + out.BaseCondition = in.BaseCondition } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. @@ -34,7 +52,7 @@ func (in *Condition) DeepCopy() *Condition { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExpiryCondition) DeepCopyInto(out *ExpiryCondition) { *out = *in - out.Condition = in.Condition + out.BaseCondition = in.BaseCondition } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpiryCondition. @@ -116,7 +134,7 @@ func (in *ResourceManagerSpec) DeepCopyInto(out *ResourceManagerSpec) { } if in.Condition != nil { in, out := &in.Condition, &out.Condition - *out = make([]ExpiryCondition, len(*in)) + *out = make([]Condition, len(*in)) copy(*out, *in) } } @@ -145,3 +163,19 @@ func (in *ResourceManagerStatus) DeepCopy() *ResourceManagerStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TimeframeCondition) DeepCopyInto(out *TimeframeCondition) { + *out = *in + out.BaseCondition = in.BaseCondition +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeframeCondition. +func (in *TimeframeCondition) DeepCopy() *TimeframeCondition { + if in == nil { + return nil + } + out := new(TimeframeCondition) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/resource-management.tikalk.com_resourcemanagers.yaml b/config/crd/bases/resource-management.tikalk.com_resourcemanagers.yaml index 0f07cb7..06444cc 100644 --- a/config/crd/bases/resource-management.tikalk.com_resourcemanagers.yaml +++ b/config/crd/bases/resource-management.tikalk.com_resourcemanagers.yaml @@ -45,10 +45,11 @@ spec: properties: after: type: string + time: + type: string type: type: string required: - - after - type type: object type: array diff --git a/config/samples/resource-managment_v1alpha1_resourcemanager.yaml b/config/samples/resource-managment_v1alpha1_resourcemanager.yaml index b0abcf4..781eba1 100644 --- a/config/samples/resource-managment_v1alpha1_resourcemanager.yaml +++ b/config/samples/resource-managment_v1alpha1_resourcemanager.yaml @@ -13,8 +13,3 @@ spec: condition: - type: expiry after: "1m" - # OR - # - type: time - # interval: daily - # time: "20:59:59" -#status: diff --git a/config/samples/resource-managmr-timeframe.yaml b/config/samples/resource-managmr-timeframe.yaml new file mode 100644 index 0000000..492d3ff --- /dev/null +++ b/config/samples/resource-managmr-timeframe.yaml @@ -0,0 +1,17 @@ +apiVersion: resource-management.tikalk.com/v1alpha1 +kind: ResourceManager +metadata: + name: resource-manager-sample +spec: + active: true + dry-run: false + resources: "namespace" + selector: + matchLabels: + name: managed-namespace1 + action: "delete" + condition: + - type: timeframe + time: "12:20" +# timeZone: "Asia/Jerusalem" + diff --git a/controllers/handlers/namespace.go b/controllers/handlers/namespace.go index da9d55f..a2fe29b 100644 --- a/controllers/handlers/namespace.go +++ b/controllers/handlers/namespace.go @@ -2,58 +2,51 @@ package handlers import ( "fmt" - utils "github.com/tikalk/resource-manager/controllers/utils" + "time" + + "github.com/tikalk/resource-manager/controllers/utils" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" ) -// keep everything in a struct - // HandleNamespaceObj handle namespace objects that related to the resource-manager controller func (o Obj) HandleNamespaceObj() { // get all the namespaces with the desired selector labels - namespaces, err := GetNamespacesByLabel(o) + namespacesToHandle, err := o.GetNamespacesByLabel() if err != nil { - o.L.Error(err, fmt.Sprintf("%s: cannot list namespaces\n", o.Name)) + o.l.Error(err, fmt.Sprintf("%s: cannot list namespaces\n", o.Name)) + return } - if len(namespaces) <= 0 { - fmt.Printf("%s: did not found any namespaces with the requested label\n", o.Name) + if len(namespacesToHandle) <= 0 { + fmt.Printf("%s: did not found any namespace with the requested label\n", o.Name) + time.Sleep(10 * time.Second) return } - fmt.Printf("found %d namespaces with the requested label\n", len(namespaces)) - - for _, namespace := range namespaces { - expired, secondsUntilExpire := utils.IsObjExpired(namespace.CreationTimestamp, o.Spec.Condition[0].After) - if expired { - switch o.Spec.Action { - case "delete": - fmt.Printf("namespace '%s' has been expired and will be deleted \n", namespace.Name) - err := o.C.Delete(o.Ctx, namespace.DeepCopy(), &client.DeleteOptions{}) - if err != nil { - o.L.Error(err, fmt.Sprintf("cannot delete namespaces\n")) - } - fmt.Printf("%s: namespace '%s' has been deleted \n", o.Name, namespace.Name) - } - } else { - fmt.Printf("%s: %d seconds has left to namespace '%s' \n", o.Name, secondsUntilExpire, namespace.Name) - + for _, ns := range namespacesToHandle { + switch o.rm.Spec.Condition[0].Type { + case "expiry": + o.handleExpiry(ns) + case "timeframe": + o.handleTimeframe(ns) } + } + time.Sleep(10 * time.Second) } // GetNamespacesByLabel get only namespaces that contains a specific label -func GetNamespacesByLabel(o Obj) ([]v1.Namespace, error) { +func (o Obj) GetNamespacesByLabel() ([]v1.Namespace, error) { var listOfNamespaces []v1.Namespace nsListObj := &v1.NamespaceList{} - if err := o.C.List(o.Ctx, nsListObj, &client.ListOptions{ - LabelSelector: labels.SelectorFromSet(o.Spec.Selector.MatchLabels), + if err := o.c.List(o.ctx, nsListObj, &client.ListOptions{ + LabelSelector: labels.SelectorFromSet(o.rm.Spec.Selector.MatchLabels), }); err != nil { - o.L.Error(err, fmt.Sprintf("%s: unable to fetch namespaces", o.Name)) + o.l.Error(err, fmt.Sprintf("%s: unable to fetch namespaces", o.Name)) return nil, err } @@ -62,3 +55,46 @@ func GetNamespacesByLabel(o Obj) ([]v1.Namespace, error) { } return listOfNamespaces, nil } + +// deleteNamespace delete namespace obj +func (o Obj) deleteNamespace(namespace v1.Namespace) { + err := o.c.Delete(o.ctx, namespace.DeepCopy(), &client.DeleteOptions{}) + if err != nil { + o.l.Error(err, fmt.Sprintf("cannot delete namespaces\n")) + } + time.Sleep(5 * time.Second) + fmt.Printf("%s: namespace '%s' has been deleted \n", o.Name, namespace.Name) + +} + +// handleTimeframe handle timeframe type +func (o Obj) handleTimeframe(namespace v1.Namespace) { + fmt.Printf("namespace '%s' will be deleted at timeframe: %s \n", namespace.Name, o.rm.Spec.Condition[0].Timeframe) + err, doesIntervalOccurred := utils.IsIntervalOccurred(o.rm.Spec.Condition[0].Timeframe) + if err != nil { + o.l.Error(err, fmt.Sprintf("cannot calculate timeframe\n")) + return + } + if doesIntervalOccurred { + switch o.rm.Spec.Action { + case "delete": + fmt.Printf("namespace '%s' is in timeframe and will be deleted \n", namespace.Name) + o.deleteNamespace(namespace) + } + } +} + +// handleExpiry handle expiry type +func (o Obj) handleExpiry(namespace v1.Namespace) { + expired, secondsUntilExpire := utils.IsObjExpired(namespace.CreationTimestamp, o.rm.Spec.Condition[0].After) + if expired { + switch o.rm.Spec.Action { + case "delete": + fmt.Printf("namespace '%s' has been expired and will be deleted \n", namespace.Name) + o.deleteNamespace(namespace) + fmt.Printf("%s: namespace '%s' has been deleted \n", o.Name, namespace.Name) + } + } else { + fmt.Printf("%s: %d seconds has left to namespace '%s' \n", o.Name, secondsUntilExpire, namespace.Name) + } +} diff --git a/controllers/handlers/types.go b/controllers/handlers/types.go index ed68303..8fabc9d 100644 --- a/controllers/handlers/types.go +++ b/controllers/handlers/types.go @@ -2,6 +2,8 @@ package handlers import ( "context" + "fmt" + "github.com/go-logr/logr" resourcemanagmentv1alpha1 "github.com/tikalk/resource-manager/api/v1alpha1" "k8s.io/apimachinery/pkg/types" @@ -12,8 +14,49 @@ import ( type Obj struct { Name types.NamespacedName - C client.Client - Ctx context.Context - L logr.Logger - Spec resourcemanagmentv1alpha1.ResourceManagerSpec + c client.Client + ctx context.Context + l logr.Logger + rm resourcemanagmentv1alpha1.ResourceManager + + stop chan bool +} + +func InitObj(rm resourcemanagmentv1alpha1.ResourceManager, c client.Client, ctx context.Context, l logr.Logger) Obj { + stop := make(chan bool) + + name := types.NamespacedName{ + Name: rm.Name, + Namespace: rm.Namespace, + } + + o := Obj{ + rm: rm, + Name: name, + c: c, + ctx: ctx, + l: l, + stop: stop, + } + return o +} + +func (o Obj) Stop() { + o.stop <- true +} + +func (o Obj) Run() { + fmt.Printf("Processing object: %s \n", o.Name) + for { + select { + case <-o.stop: + o.l.Info(fmt.Sprintf("%s Got stop signal!\n", o.Name)) + return + default: + switch o.rm.Spec.Resources { // here we decide which handler to use + case "namespace": + o.HandleNamespaceObj() + } + } + } } diff --git a/controllers/resourcemanager_controller.go b/controllers/resourcemanager_controller.go index 8b832f7..2db12df 100644 --- a/controllers/resourcemanager_controller.go +++ b/controllers/resourcemanager_controller.go @@ -19,7 +19,6 @@ package controllers import ( "context" "fmt" - "time" "github.com/tikalk/resource-manager/api/v1alpha1" "github.com/tikalk/resource-manager/controllers/handlers" @@ -34,7 +33,8 @@ import ( // ResourceManagerReconciler reconciles a ResourceManager object type ResourceManagerReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + collection map[types.NamespacedName]handlers.Obj } //+kubebuilder:rbac:groups=resource-management.tikalk.com,resources=resourcemanagers,verbs=get;list;watch;create;update;patch;delete @@ -54,13 +54,6 @@ type ResourceManagerReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile -var collection map[types.NamespacedName]FHandler - -type FHandler struct { - F func(stop chan bool) - Stop chan bool -} - func (r *ResourceManagerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx) @@ -72,10 +65,10 @@ func (r *ResourceManagerReconciler) Reconcile(ctx context.Context, req ctrl.Requ if err != nil { if errors.IsNotFound(err) { l.Info(fmt.Sprintf("ResourceManager object %s has Not Found!!! \n", req.NamespacedName)) - collection[req.NamespacedName].Stop <- true + r.collection[req.NamespacedName].Stop() // delete the key from collection map - delete(collection, req.NamespacedName) + delete(r.collection, req.NamespacedName) return ctrl.Result{}, nil } @@ -85,74 +78,26 @@ func (r *ResourceManagerReconciler) Reconcile(ctx context.Context, req ctrl.Requ fmt.Printf("found ResourceManager object: %s \n", resourceManagerObj.Name) // config handler object - h := handlers.Obj{ - Name: req.NamespacedName, - C: r.Client, - Ctx: ctx, - L: l, - Spec: resourceManagerObj.Spec, - } - - l.Info(fmt.Sprintf( - "\n"+ - " ResourceType: %s \n"+ - " selectorLables %s \n"+ - " action: %s \n"+ - " condition: %s \n"+ - " type: %s \n", - h.Spec.Resources, - h.Spec.Selector.MatchLabels, - h.Spec.Action, - h.Spec.Condition[0].After, - h.Spec.Condition[0].Type)) + h := handlers.InitObj(*resourceManagerObj, r.Client, ctx, l) // check if resource exists in our collection, if so, delete - if _, ok := collection[h.Name]; ok { + if _, ok := r.collection[h.Name]; ok { l.Info(fmt.Sprintf("Stopping loop for %s\n", h.Name)) - collection[h.Name].Stop <- true // delete the key from collection map - delete(collection, h.Name) + delete(r.collection, h.Name) } - switch h.Spec.Resources { - case "namespace": - // add the function and its stop-channel to collection - collection[h.Name] = FHandler{ - F: func(stop chan bool) { - for { - select { - case <-stop: - l.Info(fmt.Sprintf("%s Got stop signal!\n", h.Name)) - return - default: - h.HandleNamespaceObj() - time.Sleep(5 * time.Second) - } - } - }, - Stop: make(chan bool), - } - - // export to new var - c := collection[h.Name] + // add handler to collection + r.collection[req.NamespacedName] = h - // execute in a new thread - - go c.F(c.Stop) - } - // - //deploy := &appsv1.DeploymentList{} - //err = r.Client.List(ctx, deploy, &client.ListOptions{}) - //fmt.Printf("There are %d deployments in the cluster\n", len(deploy.Items)) - // - //l.Info(fmt.Sprintf("Done reconcile 12-- obj %s", name)) + go r.collection[req.NamespacedName].Run() return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (r *ResourceManagerReconciler) SetupWithManager(mgr ctrl.Manager) error { - collection = make(map[types.NamespacedName]FHandler) + r.collection = make(map[types.NamespacedName]handlers.Obj) return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.ResourceManager{}). Complete(r) diff --git a/controllers/utils/utils.go b/controllers/utils/utils.go index e5887cc..badea5e 100644 --- a/controllers/utils/utils.go +++ b/controllers/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "time" ) @@ -16,3 +17,27 @@ func IsObjExpired(creation v1.Time, expiration string) (bool, int) { secondsUntilExp := objExpiredAt.Sub(now).Seconds() return secondsUntilExp <= 0, int(secondsUntilExp) } + +func IsIntervalOccurred(timeframe string) (error, bool) { + + // parse timeframe to time object + timeframeTime, err := time.Parse("15:04", timeframe) + if err != nil { + fmt.Println("Could not parse timeframe:", err) + return err, false + } + + // current hour in the same time format "15:04" + nowTime, err := time.Parse("15:04", time.Now().Format("15:04")) + if err != nil { + fmt.Println("Could not parse time:", err) + return err, false + } + + secondsUntilTimeframe := timeframeTime.Sub(nowTime).Seconds() + if secondsUntilTimeframe <= 0 && secondsUntilTimeframe > -60 { + fmt.Println(fmt.Sprintf("timeframe triggerd: '%s'", timeframeTime)) + return nil, true + } + return nil, false +}