From d6368a16f284ac55bd8a2a7aa1e2218f091f39a7 Mon Sep 17 00:00:00 2001 From: "Md. Anisur Rahman" <54911684+anisurrahman75@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:03:16 +0600 Subject: [PATCH] Add kubestash controller for changing kubeDB phase (#1076) Signed-off-by: Anisur Rahman --- .../{stash => restore}/controller.go | 39 ++- .../restore/kubestash-restoresession.go | 77 +++++ .../stash-restorebatch.go} | 2 +- .../stash-restoresession.go} | 2 +- .../initializer/{stash => restore}/util.go | 119 ++++++-- .../apis/addons/v1alpha1/addon_helpers.go | 27 ++ .../apis/addons/v1alpha1/addon_types.go | 149 +++++++++ .../apimachinery/apis/addons/v1alpha1/doc.go | 23 ++ .../apis/addons/v1alpha1/function_helpers.go | 27 ++ .../apis/addons/v1alpha1/function_types.go | 116 +++++++ .../apis/addons/v1alpha1/groupversion_info.go | 36 +++ .../addons/v1alpha1/zz_generated.deepcopy.go | 282 ++++++++++++++++++ vendor/modules.txt | 1 + 13 files changed, 866 insertions(+), 34 deletions(-) rename pkg/controller/initializer/{stash => restore}/controller.go (77%) create mode 100644 pkg/controller/initializer/restore/kubestash-restoresession.go rename pkg/controller/initializer/{stash/restorebatch.go => restore/stash-restorebatch.go} (99%) rename pkg/controller/initializer/{stash/restoresession.go => restore/stash-restoresession.go} (99%) rename pkg/controller/initializer/{stash => restore}/util.go (72%) create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_helpers.go create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_types.go create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/doc.go create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_helpers.go create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_types.go create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/groupversion_info.go create mode 100644 vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/zz_generated.deepcopy.go diff --git a/pkg/controller/initializer/stash/controller.go b/pkg/controller/initializer/restore/controller.go similarity index 77% rename from pkg/controller/initializer/stash/controller.go rename to pkg/controller/initializer/restore/controller.go index 68db8712d6..2f4830e45e 100644 --- a/pkg/controller/initializer/stash/controller.go +++ b/pkg/controller/initializer/restore/controller.go @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package stash +package restore import ( + "fmt" "time" amc "kubedb.dev/apimachinery/pkg/controller" @@ -26,25 +27,31 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" "k8s.io/klog/v2" + kmapi "kmodules.xyz/client-go/api/v1" dmcond "kmodules.xyz/client-go/dynamic/conditions" "kmodules.xyz/client-go/tools/queue" + coreapi "kubestash.dev/apimachinery/apis/core/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/manager" "stash.appscode.dev/apimachinery/apis/stash/v1beta1" scs "stash.appscode.dev/apimachinery/client/clientset/versioned" stashinformer "stash.appscode.dev/apimachinery/client/informers/externalversions" ) type Controller struct { + manager *manager.Manager *amc.Controller *amc.StashInitializer restrictToNamespace string } func NewController( + mgr *manager.Manager, ctrl *amc.Controller, initializer *amc.StashInitializer, restrictToNamespace string, ) *Controller { return &Controller{ + manager: mgr, Controller: ctrl, StashInitializer: initializer, restrictToNamespace: restrictToNamespace, @@ -53,12 +60,22 @@ func NewController( type restoreInfo struct { invoker core.TypedLocalObjectReference - target *v1beta1.RestoreTarget - phase v1beta1.RestorePhase + stash *stashInfo + kubestash *kubestashInfo do dmcond.DynamicOptions invokerUID types.UID } +type stashInfo struct { + target *v1beta1.RestoreTarget + phase v1beta1.RestorePhase +} + +type kubestashInfo struct { + target *kmapi.TypedObjectReference + phase coreapi.RestorePhase +} + func Configure(cfg *rest.Config, s *amc.StashInitializer, resyncPeriod time.Duration) error { var err error if s.StashClient, err = scs.NewForConfig(cfg); err != nil { @@ -68,7 +85,7 @@ func Configure(cfg *rest.Config, s *amc.StashInitializer, resyncPeriod time.Dura return nil } -func (c *Controller) StartAfterStashInstalled(maxNumRequeues, numThreads int, selector metav1.LabelSelector, stopCh <-chan struct{}) { +func (c *Controller) StartAfterStashInstalled(stopCh <-chan struct{}, maxNumRequeues, numThreads int, selector metav1.LabelSelector) { // Wait until Stash operator installed if err := c.waitUntilStashInstalled(stopCh); err != nil { klog.Errorln("error during waiting for RestoreSession crd. Reason: ", err) @@ -125,3 +142,17 @@ func (c *Controller) startController(stopCh <-chan struct{}) { c.RSQueue.Run(stopCh) c.RBQueue.Run(stopCh) } + +func (c *Controller) StartAfterKubeStashInstalled(stopCh <-chan struct{}, selector metav1.LabelSelector) { + // Here Wait until KubeStash operator installed + if err := c.waitUntilKubeStashInstalled(stopCh); err != nil { + klog.Errorln("error during waiting for RestoreSession crd. Reason: ", err) + return + } + if err := (&RestoreSessionReconciler{ + ctrl: c, + }).SetupWithManager(*c.manager, selector); err != nil { + klog.Info(fmt.Errorf("unable to create RestoreSession controller. Reason: %w", err)) + return + } +} diff --git a/pkg/controller/initializer/restore/kubestash-restoresession.go b/pkg/controller/initializer/restore/kubestash-restoresession.go new file mode 100644 index 0000000000..e3026478c7 --- /dev/null +++ b/pkg/controller/initializer/restore/kubestash-restoresession.go @@ -0,0 +1,77 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 restore + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" + coreapi "kubestash.dev/apimachinery/apis/core/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// RestoreSessionReconciler reconciles a RestoreSession object +type RestoreSessionReconciler struct { + ctrl *Controller +} + +func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("Reconciling: " + req.String()) + c := r.ctrl.KBClient + + rs := &coreapi.RestoreSession{} + if err := c.Get(ctx, req.NamespacedName, rs); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + ri, err := r.ctrl.extractRestoreInfo(rs) + if err != nil { + klog.Errorln("failed to extract kubeStash invoker info. Reason: ", err) + return ctrl.Result{}, err + } + if rs.DeletionTimestamp != nil { + return ctrl.Result{}, r.ctrl.handleTerminateEvent(ri) + } + + return ctrl.Result{}, r.ctrl.handleRestoreInvokerEvent(ri) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager, selector metav1.LabelSelector) error { + return ctrl.NewControllerManagedBy(mgr). + For(&coreapi.RestoreSession{}, builder.WithPredicates( + predicate.NewPredicateFuncs(func(object client.Object) bool { + return hasRequiredLabels(object.GetLabels(), selector.MatchLabels) + }), + )). + Complete(r) +} + +func hasRequiredLabels(actualLabels, requiredLabels map[string]string) bool { + for key, value := range requiredLabels { + if actualValue, found := actualLabels[key]; !found || actualValue != value { + return false + } + } + return true +} diff --git a/pkg/controller/initializer/stash/restorebatch.go b/pkg/controller/initializer/restore/stash-restorebatch.go similarity index 99% rename from pkg/controller/initializer/stash/restorebatch.go rename to pkg/controller/initializer/restore/stash-restorebatch.go index f704fb61f9..bda0287b6f 100644 --- a/pkg/controller/initializer/stash/restorebatch.go +++ b/pkg/controller/initializer/restore/stash-restorebatch.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package stash +package restore import ( "time" diff --git a/pkg/controller/initializer/stash/restoresession.go b/pkg/controller/initializer/restore/stash-restoresession.go similarity index 99% rename from pkg/controller/initializer/stash/restoresession.go rename to pkg/controller/initializer/restore/stash-restoresession.go index f861a45c34..5a95dae6a5 100644 --- a/pkg/controller/initializer/stash/restoresession.go +++ b/pkg/controller/initializer/restore/stash-restoresession.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package stash +package restore import ( "time" diff --git a/pkg/controller/initializer/stash/util.go b/pkg/controller/initializer/restore/util.go similarity index 72% rename from pkg/controller/initializer/stash/util.go rename to pkg/controller/initializer/restore/util.go index 07d5f2bcf4..5fa50b8348 100644 --- a/pkg/controller/initializer/stash/util.go +++ b/pkg/controller/initializer/restore/util.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package stash +package restore import ( "context" @@ -41,6 +41,9 @@ import ( dmcond "kmodules.xyz/client-go/dynamic/conditions" appcat "kmodules.xyz/custom-resources/apis/appcatalog" ab "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1" + addonapi "kubestash.dev/apimachinery/apis/addons/v1alpha1" + coreapi "kubestash.dev/apimachinery/apis/core/v1alpha1" + storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" sapis "stash.appscode.dev/apimachinery/apis" "stash.appscode.dev/apimachinery/apis/stash" "stash.appscode.dev/apimachinery/apis/stash/v1alpha1" @@ -50,9 +53,6 @@ import ( func (c *Controller) extractRestoreInfo(inv interface{}) (*restoreInfo, error) { ri := &restoreInfo{ - invoker: core.TypedLocalObjectReference{ - APIGroup: pointer.StringP(stash.GroupName), - }, do: dmcond.DynamicOptions{ Client: c.DynamicClient, }, @@ -61,31 +61,55 @@ func (c *Controller) extractRestoreInfo(inv interface{}) (*restoreInfo, error) { switch inv := inv.(type) { case *v1beta1.RestoreSession: // invoker information + ri.invoker.APIGroup = pointer.StringP(stash.GroupName) ri.invoker.Kind = inv.Kind ri.invoker.Name = inv.Name - // target information - ri.target = inv.Spec.Target - // restore status - ri.phase = inv.Status.Phase + // database information ri.do.Namespace = inv.Namespace ri.invokerUID = inv.UID + + // stash information + ri.stash = &stashInfo{ + target: ri.stash.target, + phase: ri.stash.phase, + } case *v1beta1.RestoreBatch: // invoker information + ri.invoker.APIGroup = pointer.StringP(stash.GroupName) ri.invoker.Kind = inv.Kind ri.invoker.Name = inv.Name - // target information + + // database information + ri.do.Namespace = inv.Namespace + ri.invokerUID = inv.UID + + // stash information // RestoreBatch can have multiple targets. In this case, only the database related target's phase does matter. - ri.target, err = c.identifyTarget(inv.Spec.Members, ri.do.Namespace) - if err != nil { - return ri, err + info := &stashInfo{} + if info.target, err = c.identifyTarget(inv.Spec.Members, ri.do.Namespace); err != nil { + return nil, err } + // restore status // RestoreBatch can have multiple targets. In this case, finding the appropriate target is necessary. - ri.phase = getTargetPhase(inv.Status, ri.target) + info.phase = getTargetPhase(inv.Status, info.target) + ri.stash = info + case *coreapi.RestoreSession: + // invoker information + ri.invoker.APIGroup = pointer.StringP(storageapi.GroupVersion.Group) + ri.invoker.Kind = inv.Kind + ri.invoker.Name = inv.Name + // database information ri.do.Namespace = inv.Namespace ri.invokerUID = inv.UID + + // kubestash information + ri.kubestash = &kubestashInfo{ + target: inv.Spec.Target, + phase: inv.Status.Phase, + } default: return ri, fmt.Errorf("unknown restore invoker type") } @@ -102,7 +126,7 @@ func (c *Controller) handleTerminateEvent(ri *restoreInfo) error { return fmt.Errorf("invalid restore information. it must not be nil") } // If the target could not be identified properly, we can't process further. - if ri.target == nil { + if ri.stash == nil && ri.kubestash == nil { return fmt.Errorf("couldn't identify the restore target from invoker: %s/%s/%s", *ri.invoker.APIGroup, ri.invoker.Kind, ri.invoker.Name) } @@ -144,7 +168,7 @@ func (c *Controller) handleRestoreInvokerEvent(ri *restoreInfo) error { return fmt.Errorf("invalid restore information. it must not be nil") } // If the target could not be identified properly, we can't process further. - if ri.target == nil { + if ri.stash == nil && ri.kubestash == nil { return fmt.Errorf("couldn't identify the restore target from invoker: %s/%s/%s", *ri.invoker.APIGroup, ri.invoker.Kind, ri.invoker.Name) } @@ -155,7 +179,7 @@ func (c *Controller) handleRestoreInvokerEvent(ri *restoreInfo) error { dbCond := kmapi.Condition{ Type: api.DatabaseDataRestored, } - if ri.phase == v1beta1.RestoreSucceeded { + if (ri.stash != nil && ri.stash.phase == v1beta1.RestoreSucceeded) || (ri.kubestash != nil && ri.kubestash.phase == coreapi.RestoreSucceeded) { dbCond.Status = metav1.ConditionTrue dbCond.Reason = api.DatabaseSuccessfullyRestored dbCond.Message = fmt.Sprintf("Successfully restored data by initializer %s %s/%s with UID %s", @@ -281,27 +305,60 @@ func (c *Controller) waitUntilStashInstalled(stopCh <-chan struct{}) error { }, stopCh) } +// waitUntilKubeStashInstalled waits for KubeStash operator to be installed. It check whether all the CRDs that are necessary for backup KubeDB database, +// is present in the cluster or not. It wait until all the CRDs are found. +func (c *Controller) waitUntilKubeStashInstalled(stopCh <-chan struct{}) error { + klog.Infoln("Looking for the KubeStash operator.......") + return wait.PollImmediateUntil(time.Second*10, func() (bool, error) { + return discovery.ExistsGroupKinds(c.Client.Discovery(), + schema.GroupKind{Group: storageapi.GroupVersion.Group, Kind: storageapi.ResourceKindBackupStorage}, + schema.GroupKind{Group: storageapi.GroupVersion.Group, Kind: storageapi.ResourceKindRepository}, + schema.GroupKind{Group: storageapi.GroupVersion.Group, Kind: storageapi.ResourceKindSnapshot}, + schema.GroupKind{Group: storageapi.GroupVersion.Group, Kind: storageapi.ResourceKindRetentionPolicy}, + schema.GroupKind{Group: coreapi.GroupVersion.Group, Kind: coreapi.ResourceKindBackupBatch}, + schema.GroupKind{Group: coreapi.GroupVersion.Group, Kind: coreapi.ResourceKindBackupBlueprint}, + schema.GroupKind{Group: coreapi.GroupVersion.Group, Kind: coreapi.ResourceKindBackupConfiguration}, + schema.GroupKind{Group: coreapi.GroupVersion.Group, Kind: coreapi.ResourceKindBackupSession}, + schema.GroupKind{Group: coreapi.GroupVersion.Group, Kind: coreapi.ResourceKindRestoreSession}, + schema.GroupKind{Group: addonapi.GroupVersion.Group, Kind: addonapi.ResourceKindAddon}, + schema.GroupKind{Group: addonapi.GroupVersion.Group, Kind: addonapi.ResourceKindFunction}, + ), nil + }, stopCh) +} + func (c *Controller) extractDatabaseInfo(ri *restoreInfo) error { if ri == nil { return fmt.Errorf("invalid restoreInfo. It must not be nil") } - if ri.target == nil { + + // It is guaranteed that if either ri.stash or ri.kubestash is initialized, then 'target' field is also initialized. + if ri.stash == nil && ri.kubestash == nil { return fmt.Errorf("invalid target. It must not be nil") } + var owner *metav1.OwnerReference - if matched, err := targetOfGroupKind(ri.target.Ref, appcat.GroupName, ab.ResourceKindApp); err == nil && matched { - appBinding, err := c.AppCatalogClient.AppcatalogV1alpha1().AppBindings(ri.do.Namespace).Get(context.TODO(), ri.target.Ref.Name, metav1.GetOptions{}) - if err != nil { - return err + if ri.stash != nil { + if matched, err := targetOfGroupKind(ri.stash.target.Ref, appcat.GroupName, ab.ResourceKindApp); err == nil && matched { + appBinding, err := c.AppCatalogClient.AppcatalogV1alpha1().AppBindings(ri.do.Namespace).Get(context.TODO(), ri.stash.target.Ref.Name, metav1.GetOptions{}) + if err != nil { + return err + } + owner = metav1.GetControllerOf(appBinding) + } else if matched, err := targetOfGroupKind(ri.stash.target.Ref, apps.GroupName, sapis.KindStatefulSet); err == nil && matched { + sts, err := c.AppCatalogClient.AppcatalogV1alpha1().AppBindings(ri.do.Namespace).Get(context.TODO(), ri.stash.target.Ref.Name, metav1.GetOptions{}) + if err != nil { + return err + } + owner = metav1.GetControllerOf(sts) } - owner = metav1.GetControllerOf(appBinding) - } else if matched, err := targetOfGroupKind(ri.target.Ref, apps.GroupName, sapis.KindStatefulSet); err == nil && matched { - sts, err := c.AppCatalogClient.AppcatalogV1alpha1().AppBindings(ri.do.Namespace).Get(context.TODO(), ri.target.Ref.Name, metav1.GetOptions{}) + } else { + appBinding, err := c.AppCatalogClient.AppcatalogV1alpha1().AppBindings(ri.kubestash.target.Namespace).Get(context.TODO(), ri.kubestash.target.Name, metav1.GetOptions{}) if err != nil { return err } - owner = metav1.GetControllerOf(sts) + owner = metav1.GetControllerOf(appBinding) } + if owner == nil { return fmt.Errorf("failed to extract database information from the target info. Reason: target does not have controlling owner") } @@ -346,7 +403,13 @@ func (c *Controller) writeRestoreCompletionEvent(do dmcond.DynamicOptions, cond } func isDataRestoreCompleted(ri *restoreInfo) bool { - return ri.phase == v1beta1.RestoreSucceeded || - ri.phase == v1beta1.RestoreFailed || - ri.phase == v1beta1.RestorePhaseUnknown + if ri.stash != nil { + return ri.stash.phase == v1beta1.RestoreSucceeded || + ri.stash.phase == v1beta1.RestoreFailed || + ri.stash.phase == v1beta1.RestorePhaseUnknown + } else { + return ri.kubestash.phase == coreapi.RestoreSucceeded || + ri.kubestash.phase == coreapi.RestoreFailed || + ri.kubestash.phase == coreapi.RestorePhaseUnknown + } } diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_helpers.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_helpers.go new file mode 100644 index 0000000000..19fbc25192 --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_helpers.go @@ -0,0 +1,27 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 + +import ( + "kubestash.dev/apimachinery/crds" + + "kmodules.xyz/client-go/apiextensions" +) + +func (_ Addon) CustomResourceDefinition() *apiextensions.CustomResourceDefinition { + return crds.MustCustomResourceDefinition(GroupVersion.WithResource(ResourcePluralAddon)) +} diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_types.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_types.go new file mode 100644 index 0000000000..123a0f927b --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/addon_types.go @@ -0,0 +1,149 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 + +import ( + "kubestash.dev/apimachinery/apis" + + core "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + ResourceKindAddon = "Addon" + ResourceSingularAddon = "addon" + ResourcePluralAddon = "addons" +) + +// +k8s:openapi-gen=true +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=addons,singular=addon,scope=Cluster,categories={kubestash,appscode,all} +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// Addon specifies the backup and restore capabilities for a particular resource. +// For example, MySQL addon specifies the backup and restore capabilities of MySQL database where +// Postgres addon specifies backup and restore capabilities for PostgreSQL database. +// An Addon CR defines the backup and restore tasks that can be performed by this addon. +type Addon struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AddonSpec `json:"spec,omitempty"` +} + +// AddonSpec defines the specification for backup and restore tasks. +type AddonSpec struct { + // BackupTasks specifies a list of backup tasks that can be performed by the addon. + BackupTasks []Task `json:"backupTasks,omitempty"` + + // RestoreTasks specifies a list of restore tasks that can be performed by the addon. + RestoreTasks []Task `json:"restoreTasks,omitempty"` +} + +// Task defines the specification of a backup/restore task. +type Task struct { + // Name specifies the name of the task. The name of a Task should indicate what + // this task does. For example, a name LogicalBackup indicate that this task performs + // a logical backup of a database. + Name string `json:"name,omitempty"` + + // Function specifies the name of a Function CR that defines a container definition + // which will execute the backup/restore logic for a particular application. + Function string `json:"function,omitempty"` + + // Driver specifies the underlying tool that will be used to upload the data to the backend storage. + // Valid values are: + // - "Restic": The underlying tool is [restic](https://restic.net/). + // - "WalG": The underlying tool is [wal-g](https://github.com/wal-g/wal-g). + // +kubebuilder:validation:Enum=Restic;WalG;VolumeSnapshotter; + Driver apis.Driver `json:"driver,omitempty"` + + // Executor specifies the type of entity that will execute the task. For example, it can be a Job, + // a sidecar container, an ephemeral container, or a Job that creates additional Jobs/Pods + // for executing the backup/restore logic. + // Valid values are: + // - "Job": Stash will create a Job to execute the backup/restore task. + // - "Sidecar": Stash will inject a sidecar container into the application to execute the backup/restore task. + // - "EphemeralContainer": Stash will attach an ephemeral container to the respective Pods to execute the backup/restore task. + // - "MultiLevelJob": Stash will create a Job that will create additional Jobs/Pods to execute the backup/restore task. + Executor TaskExecutor `json:"executor,omitempty"` + + // Singleton specifies whether this task will be executed on a single job or across multiple jobs. + Singleton bool `json:"singleton,omitempty"` + + // Parameters defines a list of parameters that is used by the task to execute its logic. + // +optional + Parameters []apis.ParameterDefinition `json:"parameters,omitempty"` + + // VolumeTemplate specifies a list of volume templates that is used by the respective backup/restore + // Job to execute its logic. + // User can overwrite these volume templates using `addonVolumes` field of BackupConfiguration/BackupBatch. + // +optional + VolumeTemplate []VolumeTemplate `json:"volumeTemplate,omitempty"` + + // VolumeMounts specifies the mount path of the volumes specified in the VolumeTemplate section. + // These volumes will be mounted directly on the Job/Container created/injected by Stash operator. + // If the volume type is VolumeClaimTemplate, then Stash operator is responsible for creating the volume. + // +optional + VolumeMounts []core.VolumeMount `json:"volumeMounts,omitempty"` + + // PassThroughMounts specifies a list of volume mount for the VolumeTemplates that should be mounted + // on second level Jobs/Pods created by the first level executor Job. + // If the volume needs to be mounted on both first level and second level Jobs/Pods, then specify the + // mount in both VolumeMounts and PassThroughMounts section. + // If the volume type is VolumeClaimTemplate, then the first level job is responsible for creating the volume. + // +optional + PassThroughMounts []core.VolumeMount `json:"passThroughMounts,omitempty"` +} + +// TaskExecutor defines the type of the executor that will execute the backup/restore task. +// +kubebuilder:validation:Enum=Job;Sidecar;EphemeralContainer;MultiLevelJob +type TaskExecutor string + +const ( + ExecutorJob TaskExecutor = "Job" + ExecutorSidecar TaskExecutor = "Sidecar" + ExecutorEphemeralContainer TaskExecutor = "EphemeralContainer" + ExecutorMultiLevelJob TaskExecutor = "MultiLevelJob" +) + +// VolumeTemplate specifies the name, usage, and the source of volume that will be used by the +// addon to execute it's backup/restore task. +type VolumeTemplate struct { + // Name specifies the name of the volume + Name string `json:"name,omitempty"` + + // Usage specifies the usage of the volume. + // +optional + Usage string `json:"usage,omitempty"` + + // Source specifies the source of this volume. + Source *apis.VolumeSource `json:"source,omitempty"` +} + +//+kubebuilder:object:root=true + +// AddonList contains a list of Addon +type AddonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Addon `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Addon{}, &AddonList{}) +} diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/doc.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/doc.go new file mode 100644 index 0000000000..271374d77a --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 is the v1alpha1 version of the API. + +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta + +// +groupName=addons.kubestash.com +package v1alpha1 diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_helpers.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_helpers.go new file mode 100644 index 0000000000..413b2c67b7 --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_helpers.go @@ -0,0 +1,27 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 + +import ( + "kubestash.dev/apimachinery/crds" + + "kmodules.xyz/client-go/apiextensions" +) + +func (_ Function) CustomResourceDefinition() *apiextensions.CustomResourceDefinition { + return crds.MustCustomResourceDefinition(GroupVersion.WithResource(ResourcePluralFunction)) +} diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_types.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_types.go new file mode 100644 index 0000000000..9945a96a1e --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/function_types.go @@ -0,0 +1,116 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 + +import ( + core "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ofst "kmodules.xyz/offshoot-api/api/v1" +) + +const ( + ResourceKindFunction = "Function" + ResourcePluralFunction = "functions" + ResourceSingularFunction = "function" +) + +// +k8s:openapi-gen=true +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=functions,singular=function,scope=Cluster,shortName=fn,categories={kubestash,appscode,all} +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// Function is the Schema for the functions API +type Function struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FunctionSpec `json:"spec,omitempty"` +} + +type FunctionSpec struct { + // Image specifies the docker image name. + // More info: https://kubernetes.io/docs/concepts/containers/images + // This field is optional to allow higher level config management to default or override + // container images in workload controllers like Deployments and StatefulSets. + // +optional + Image string `json:"image,omitempty"` + // Entrypoint array. Not executed within a shell. + // The docker image's ENTRYPOINT is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax + // can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, + // regardless of whether the variable exists or not. + // Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + Command []string `json:"command,omitempty"` + // Args specifies the arguments to the entrypoint. + // The docker image's CMD is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax + // can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, + // regardless of whether the variable exists or not. + // Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + Args []string `json:"args,omitempty"` + // WorkDir specifies the container's working directory. + // If not specified, the container runtime's default will be used, which + // might be configured in the container image. + // Cannot be updated. + // +optional + WorkingDir string `json:"workingDir,omitempty"` + // Ports specifies the list of ports to expose from the container. Exposing a port here gives + // the system additional information about the network connections a + // container uses, but is primarily informational. Not specifying a port here + // DOES NOT prevent that port from being exposed. Any port which is + // listening on the default "0.0.0.0" address inside a container will be + // accessible from the network. + // Cannot be updated. + // +optional + // +patchMergeKey=containerPort + // +patchStrategy=merge + Ports []core.ContainerPort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"containerPort"` + // VolumeMounts specifies the Pod volumes to mount into the container's filesystem. + // Cannot be updated. + // +optional + // +patchMergeKey=mountPath + // +patchStrategy=merge + VolumeMounts []core.VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath"` + // VolumeDevices is the list of block devices to be used by the container. + // This is an alpha feature and may change in the future. + // +patchMergeKey=devicePath + // +patchStrategy=merge + // +optional + VolumeDevices []core.VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath"` + // RuntimeSettings allow to specify Resources, LivenessProbe, ReadinessProbe, Lifecycle, SecurityContext etc. + // +optional + RuntimeSettings *ofst.ContainerRuntimeSettings `json:"runtimeSettings,omitempty"` +} + +//+kubebuilder:object:root=true + +// FunctionList contains a list of Function +type FunctionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Function `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Function{}, &FunctionList{}) +} diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/groupversion_info.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/groupversion_info.go new file mode 100644 index 0000000000..a61ea8c984 --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 contains API Schema definitions for the addons v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=addons.kubestash.com +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "addons.kubestash.com", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/zz_generated.deepcopy.go b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..970e34a2d2 --- /dev/null +++ b/vendor/kubestash.dev/apimachinery/apis/addons/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,282 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright AppsCode Inc. and Contributors + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + apiv1 "kmodules.xyz/offshoot-api/api/v1" + "kubestash.dev/apimachinery/apis" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Addon) DeepCopyInto(out *Addon) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addon. +func (in *Addon) DeepCopy() *Addon { + if in == nil { + return nil + } + out := new(Addon) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Addon) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddonList) DeepCopyInto(out *AddonList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Addon, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonList. +func (in *AddonList) DeepCopy() *AddonList { + if in == nil { + return nil + } + out := new(AddonList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AddonList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddonSpec) DeepCopyInto(out *AddonSpec) { + *out = *in + if in.BackupTasks != nil { + in, out := &in.BackupTasks, &out.BackupTasks + *out = make([]Task, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RestoreTasks != nil { + in, out := &in.RestoreTasks, &out.RestoreTasks + *out = make([]Task, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonSpec. +func (in *AddonSpec) DeepCopy() *AddonSpec { + if in == nil { + return nil + } + out := new(AddonSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Function) DeepCopyInto(out *Function) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Function. +func (in *Function) DeepCopy() *Function { + if in == nil { + return nil + } + out := new(Function) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Function) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FunctionList) DeepCopyInto(out *FunctionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Function, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionList. +func (in *FunctionList) DeepCopy() *FunctionList { + if in == nil { + return nil + } + out := new(FunctionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FunctionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FunctionSpec) DeepCopyInto(out *FunctionSpec) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]v1.ContainerPort, len(*in)) + copy(*out, *in) + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]v1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumeDevices != nil { + in, out := &in.VolumeDevices, &out.VolumeDevices + *out = make([]v1.VolumeDevice, len(*in)) + copy(*out, *in) + } + if in.RuntimeSettings != nil { + in, out := &in.RuntimeSettings, &out.RuntimeSettings + *out = new(apiv1.ContainerRuntimeSettings) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionSpec. +func (in *FunctionSpec) DeepCopy() *FunctionSpec { + if in == nil { + return nil + } + out := new(FunctionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Task) DeepCopyInto(out *Task) { + *out = *in + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make([]apis.ParameterDefinition, len(*in)) + copy(*out, *in) + } + if in.VolumeTemplate != nil { + in, out := &in.VolumeTemplate, &out.VolumeTemplate + *out = make([]VolumeTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]v1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PassThroughMounts != nil { + in, out := &in.PassThroughMounts, &out.PassThroughMounts + *out = make([]v1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Task. +func (in *Task) DeepCopy() *Task { + if in == nil { + return nil + } + out := new(Task) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeTemplate) DeepCopyInto(out *VolumeTemplate) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(apis.VolumeSource) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeTemplate. +func (in *VolumeTemplate) DeepCopy() *VolumeTemplate { + if in == nil { + return nil + } + out := new(VolumeTemplate) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f4b921a570..d9b0a1a4bd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1545,6 +1545,7 @@ kubeops.dev/sidekick/apis/apps/v1alpha1 # kubestash.dev/apimachinery v0.2.0 ## explicit; go 1.20 kubestash.dev/apimachinery/apis +kubestash.dev/apimachinery/apis/addons/v1alpha1 kubestash.dev/apimachinery/apis/core/v1alpha1 kubestash.dev/apimachinery/apis/storage/v1alpha1 kubestash.dev/apimachinery/crds