/* Copyright 2023 ycyxuehan. 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 controller import ( "context" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "github.com/pkg/errors" zeldaiov1alpha1 "github.com/ycyxuehan/zelda/api/v1alpha1" "github.com/ycyxuehan/zelda/pkg/config" "github.com/ycyxuehan/zelda/pkg/utils" watchers "github.com/ycyxuehan/zelda/watcher" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ZBuilderReconciler reconciles a ZBuilder object type ZBuilderReconciler struct { client.Client Scheme *runtime.Scheme Config config.Config } //+kubebuilder:rbac:groups=zelda.io,resources=zbuilders,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=zelda.io,resources=zbuilders/status,verbs=get;update;patch //+kubebuilder:rbac:groups=zelda.io,resources=zbuilders/finalizers,verbs=update //+kubebuilder:rbac:groups=zelda.io,resources=zservices,verbs=get;list;update;patch //+kubebuilder:rbac:groups=zelda.io,resources=zservices/status,verbs=get;update;patch //+kubebuilder:rbac:groups=*,resources=pods,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=*,resources=pods/status,verbs=get;update;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by // the ZBuilder object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile func (r *ZBuilderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) // TODO(user): your logic here zbuilder := zeldaiov1alpha1.ZBuilder{} err := r.Get(ctx, req.NamespacedName, &zbuilder) if err != nil { log.Log.Error(err, "cannot get resource zbuilders.zelda.io/%s/%s", req.Namespace, req.Name) return ctrl.Result{}, err } project := zeldaiov1alpha1.ZProject{} err = r.Get(ctx, utils.NamespacedNameFromObjectReference(zbuilder.Spec.Project), &project) if err != nil { log.Log.Error(err, "cannot get resource zprojects.zelda.io/%s/%s", zbuilder.Spec.Project.Namespace, zbuilder.Spec.Project.Name) return ctrl.Result{}, err } //非删除 if zbuilder.GetDeletionTimestamp() == nil || zbuilder.GetDeletionTimestamp().IsZero() { if zbuilder.Status.Phase == "" { err = r.DoCreate(ctx, &zbuilder, &project) if err != nil { log.Log.Error(err, "do create builder failed") } return ctrl.Result{}, err } err = r.DoUpdate(ctx, &zbuilder, &project) if err != nil { log.Log.Error(err, "update builder failed") } } return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (r *ZBuilderReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&zeldaiov1alpha1.ZBuilder{}). Watches(&corev1.Pod{},&watchers.ZBuilderWatcher{Client: mgr.GetClient(),}). Complete(r) } //处理update事件 更新状态 func (r *ZBuilderReconciler) DoUpdate(ctx context.Context, zbuilder *zeldaiov1alpha1.ZBuilder, project *zeldaiov1alpha1.ZProject) error { switch zbuilder.Status.Phase { case zeldaiov1alpha1.PhaseCompleted: //已完成 return nil case zeldaiov1alpha1.PhaseDeploy: //部署阶段 return nil default: //编译打包阶段 if metav1.Now().Sub(zbuilder.Status.CreatedAt.Time) >= r.Config.Timeout { pod := corev1.Pod{} err := r.Get(context.Background(), client.ObjectKeyFromObject(zbuilder), &pod) if err != nil { return errors.WithMessage(err, "cannot get pod, maybe it create failed") } zbuilder.Status = zeldaiov1alpha1.NewZbuilderStatus(&pod.Status) } else { //超时 zbuilder.Status.Phase = zeldaiov1alpha1.PhaseCompleted zbuilder.Status.Reason = "TimeOut" now := metav1.Now() zbuilder.Status.FinishedAt = &now } r.Status().Update(context.Background(), zbuilder) } return nil } //处理create事件 创建pod,开始编译打包过程。 func (r *ZBuilderReconciler) DoCreate(ctx context.Context, zbuilder *zeldaiov1alpha1.ZBuilder, project *zeldaiov1alpha1.ZProject) error { buildTemplate := zeldaiov1alpha1.ZBuildTemplate{} registry := zeldaiov1alpha1.ZRegistry{} err := r.Get(context.Background(), utils.NamespacedNameFromObjectReference(project.Spec.Registry), ®istry) if err != nil { return errors.WithMessagef(err, "get zregistries.zelda.io/%s/%s failed", project.Spec.Registry.Namespace, project.Spec.Registry.Name) } err = r.ProcessRegistrySecret(ctx, ®istry, zbuilder) if err != nil { return errors.WithMessagef(err, "process the secret of registry failed") } subProject := project.SubProject(zbuilder.Spec.SubProject) err = r.Get(context.Background(), utils.NamespacedNameFromObjectReference(subProject.BuildSpec.Template), &buildTemplate) if err != nil { return errors.WithMessagef(err, "get zbuildtemplates.zelda.io/%s/%s failed", subProject.BuildSpec.Template.Namespace, subProject.BuildSpec.Template.Name) } pod := zbuilder.Pod(buildTemplate.Spec.DeepCopy(), r.Config.GitImage, project.Spec.Git.Repo, subProject, registry.DeepCopy()) //先更新状态,再创建pod,避免创建pod触发状态更新导致冲突 //更新状态 zbuilder.Status.Phase = zeldaiov1alpha1.PhaseCreating err = r.Status().Update(context.Background(), zbuilder) if err != nil { return errors.WithMessage(err, "update builder status failed") } //创建pod err = r.Create(context.Background(), &pod, &client.CreateOptions{}) return errors.WithMessagef(err, "create pods/%s/%s failed", pod.GetNamespace(), pod.GetName()) } // //UpdateStatus 更新状态 // func (r *ZBuilderReconciler)UpdateStatus(zbuilder*zeldaiov1alpha1.ZBuilder, pod *corev1.Pod)error{ // return nil // } func (r *ZBuilderReconciler) ProcessRegistrySecret(ctx context.Context, zregistry *zeldaiov1alpha1.ZRegistry, zbuilder *zeldaiov1alpha1.ZBuilder) error { secret := &corev1.Secret{} err := r.Get(ctx, client.ObjectKey{Namespace: zbuilder.GetNamespace(), Name: zregistry.SecreteName()}, secret) if err == nil { return nil } secret = zregistry.NamespacedSecret(zbuilder.GetNamespace()) err = r.Create(ctx, secret, &client.CreateOptions{}) return err }