mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Enhance Initializer: export the phase of Initializer and help install build-in Initializer (#1932)
* add phase for initilaizer * help install build-in initializer * add test
This commit is contained in:
@@ -22,6 +22,18 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// InitializerPhase is a label for the condition of a initializer at the current time
|
||||
type InitializerPhase string
|
||||
|
||||
const (
|
||||
// InitializerCheckingDependsOn means the initializer is checking the status of dependent Initializer
|
||||
InitializerCheckingDependsOn InitializerPhase = "checkingDependsOn"
|
||||
// InitializerInitializing means the initializer is initializing
|
||||
InitializerInitializing InitializerPhase = "initializing"
|
||||
// InitializerSuccess means the initializer successfully initialized the environment
|
||||
InitializerSuccess InitializerPhase = "success"
|
||||
)
|
||||
|
||||
// DependsOn refer to an object which Initializer depends on
|
||||
type DependsOn struct {
|
||||
Ref corev1.ObjectReference `json:"ref"`
|
||||
@@ -42,6 +54,8 @@ type InitializerStatus struct {
|
||||
// ConditionedStatus reflects the observed status of a resource
|
||||
runtimev1alpha1.ConditionedStatus `json:",inline"`
|
||||
|
||||
Phase InitializerPhase `json:"status,omitempty"`
|
||||
|
||||
// The generation observed by the Initializer controller.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration"`
|
||||
@@ -52,6 +66,8 @@ type InitializerStatus struct {
|
||||
// Initializer is the Schema for the Initializer API
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={oam},shortName=init
|
||||
// +kubebuilder:printcolumn:name="PHASE",type=string,JSONPath=`.status.status`
|
||||
// +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=".metadata.creationTimestamp"
|
||||
type Initializer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -19,7 +19,14 @@ spec:
|
||||
singular: initializer
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1beta1
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.status
|
||||
name: PHASE
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Initializer is the Schema for the Initializer API
|
||||
@@ -692,6 +699,9 @@ spec:
|
||||
description: The generation observed by the Initializer controller.
|
||||
format: int64
|
||||
type: integer
|
||||
status:
|
||||
description: InitializerPhase is a label for the condition of a initializer at the current time
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
||||
@@ -190,5 +190,29 @@ webhooks:
|
||||
- UPDATE
|
||||
resources:
|
||||
- componentdefinitions
|
||||
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ template "kubevela.name" . }}-webhook
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-core-oam-dev-v1beta1-initializers
|
||||
{{- if .Values.admissionWebhooks.patch.enabled }}
|
||||
failurePolicy: Ignore
|
||||
{{- else }}
|
||||
failurePolicy: Fail
|
||||
{{- end }}
|
||||
name: validating.core.oam-dev.v1beta1.initializers
|
||||
sideEffects: None
|
||||
admissionReviewVersions:
|
||||
- v1beta1
|
||||
rules:
|
||||
- apiGroups:
|
||||
- core.oam.dev
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- initializers
|
||||
{{- end -}}
|
||||
|
||||
@@ -7,6 +7,13 @@ metadata:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
name: initializers.core.oam.dev
|
||||
spec:
|
||||
additionalPrinterColumns:
|
||||
- JSONPath: .status.status
|
||||
name: PHASE
|
||||
type: string
|
||||
- JSONPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
group: core.oam.dev
|
||||
names:
|
||||
categories:
|
||||
@@ -692,6 +699,9 @@ spec:
|
||||
description: The generation observed by the Initializer controller.
|
||||
format: int64
|
||||
type: integer
|
||||
status:
|
||||
description: InitializerPhase is a label for the condition of a initializer at the current time
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
|
||||
@@ -20,7 +20,9 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/pkg/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -34,8 +36,11 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
oamctrl "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
// InitializerReconcileWaitTime is the time to wait before reconcile again
|
||||
@@ -66,52 +71,84 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
}
|
||||
|
||||
klog.Info("Check the status of the Initializers which you depend on")
|
||||
unsatisfied, err := r.checkDependsOn(ctx, init.Spec.DependsOn)
|
||||
init.Status.Phase = v1beta1.InitializerCheckingDependsOn
|
||||
dependsOnInitReady, err := r.checkOrInstallDependsOn(ctx, init.Spec.DependsOn)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Initializers which you depend on are not ready")
|
||||
r.record.Event(init, event.Warning("Initializers which you depend on are not ready", err))
|
||||
return ctrl.Result{}, err
|
||||
return r.endWithNegativeCondition(ctx, init, cpv1alpha1.ReconcileError(err))
|
||||
}
|
||||
if !unsatisfied {
|
||||
if !dependsOnInitReady {
|
||||
klog.Info("Wait for dependent Initializer to be ready")
|
||||
return reconcile.Result{RequeueAfter: InitializerReconcileWaitTime}, nil
|
||||
}
|
||||
|
||||
ready, err := r.applyResources(ctx, init)
|
||||
init.Status.Phase = v1beta1.InitializerInitializing
|
||||
appReady, err := r.applyResources(ctx, init)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Could not create resources via application to initialize the env")
|
||||
r.record.Event(init, event.Warning("Could not create resources via application", err))
|
||||
return ctrl.Result{}, err
|
||||
return r.endWithNegativeCondition(ctx, init, cpv1alpha1.ReconcileError(err))
|
||||
}
|
||||
if !ready {
|
||||
if !appReady {
|
||||
klog.Info("Wait for the Application created by Initializer to be ready")
|
||||
return reconcile.Result{RequeueAfter: InitializerReconcileWaitTime}, nil
|
||||
}
|
||||
|
||||
if err = r.updateObservedGeneration(ctx, init); err != nil {
|
||||
klog.ErrorS(err, "Could not update ObservedGeneration")
|
||||
r.record.Event(init, event.Warning("Could not update ObservedGeneration", err))
|
||||
return ctrl.Result{}, err
|
||||
init.Status.Phase = v1beta1.InitializerSuccess
|
||||
if err = r.patchStatus(ctx, init); err != nil {
|
||||
klog.ErrorS(err, "Could not update status")
|
||||
r.record.Event(init, event.Warning("Could not update status", err))
|
||||
return ctrl.Result{}, oamutil.EndReconcileWithNegativeCondition(ctx, r, init, cpv1alpha1.ReconcileError(err))
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) checkDependsOn(ctx context.Context, depends []v1beta1.DependsOn) (bool, error) {
|
||||
// checkOrInstallDependsOn check the status of dependOn Initializer or help install the build-in Initializer.
|
||||
// If the dependOn Initializer is not found and the namespace is default or vela-system, we will try to find
|
||||
// and install the build-in Initializer from ConfigMap.
|
||||
func (r *Reconciler) checkOrInstallDependsOn(ctx context.Context, depends []v1beta1.DependsOn) (bool, error) {
|
||||
for _, depend := range depends {
|
||||
dependInit := new(v1beta1.Initializer)
|
||||
if err := r.Client.Get(ctx, client.ObjectKey{Namespace: depend.Ref.Namespace, Name: depend.Ref.Name}, dependInit); err != nil {
|
||||
dependInit, err := utils.GetInitializer(ctx, r.Client, depend.Ref.Namespace, depend.Ref.Name)
|
||||
if err != nil {
|
||||
// if Initializer is not found and the namespace is default or vela-system,
|
||||
// try to install build-in initializer from ConfigMap
|
||||
if apierrors.IsNotFound(err) && (depend.Ref.Namespace == "" || depend.Ref.Namespace == velatypes.DefaultKubeVelaNS) {
|
||||
init, err := utils.GetBuildInInitializer(ctx, r.Client, depend.Ref.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, r.Client.Create(ctx, init)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if dependInit.Status.ObservedGeneration < dependInit.Generation {
|
||||
|
||||
if dependInit.Status.Phase != v1beta1.InitializerSuccess {
|
||||
klog.InfoS("Initializer you depend on is not ready",
|
||||
"initializer", klog.KObj(dependInit), "phase", dependInit.Status.Phase)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) updateObservedGeneration(ctx context.Context, init *v1beta1.Initializer) error {
|
||||
func (r *Reconciler) endWithNegativeCondition(ctx context.Context, init *v1beta1.Initializer, condition cpv1alpha1.Condition) (ctrl.Result, error) {
|
||||
init.SetConditions(condition)
|
||||
if err := r.patchStatus(ctx, init); err != nil {
|
||||
return ctrl.Result{}, errors.WithMessage(err, "cannot update initializer status")
|
||||
}
|
||||
return ctrl.Result{}, errors.Errorf("object level reconcile error, type: %q, msg: %q", string(condition.Type), condition.Message)
|
||||
}
|
||||
|
||||
func (r *Reconciler) patchStatus(ctx context.Context, init *v1beta1.Initializer) error {
|
||||
updateObservedGeneration(init)
|
||||
return r.Client.Status().Patch(ctx, init, client.Merge)
|
||||
}
|
||||
|
||||
func updateObservedGeneration(init *v1beta1.Initializer) {
|
||||
if init.Status.ObservedGeneration != init.Generation {
|
||||
init.Status.ObservedGeneration = init.Generation
|
||||
}
|
||||
return r.UpdateStatus(ctx, init)
|
||||
}
|
||||
|
||||
func (r *Reconciler) applyResources(ctx context.Context, init *v1beta1.Initializer) (bool, error) {
|
||||
@@ -137,11 +174,12 @@ func (r *Reconciler) applyResources(ctx context.Context, init *v1beta1.Initializ
|
||||
return false, err
|
||||
}
|
||||
|
||||
klog.InfoS("Check the status of Application", "app", klog.KObj(app))
|
||||
err := r.Client.Get(ctx, client.ObjectKey{Namespace: app.Namespace, Name: app.Name}, app)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
klog.InfoS("Check the status of Application", "app", klog.KObj(app), "phase", app.Status.Phase)
|
||||
if app.Status.Phase != common.ApplicationRunning {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ import (
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -45,6 +47,7 @@ import (
|
||||
commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
@@ -385,6 +388,46 @@ func GetUnstructuredObjectStatusCondition(obj *unstructured.Unstructured, condTy
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// GetInitializer get initializer from two level namespace
|
||||
func GetInitializer(ctx context.Context, cli client.Client, namespace, name string) (*v1beta1.Initializer, error) {
|
||||
init := new(v1beta1.Initializer)
|
||||
req := client.ObjectKey{Namespace: namespace, Name: name}
|
||||
err := cli.Get(ctx, req, init)
|
||||
if kerrors.IsNotFound(err) && req.Namespace == "" {
|
||||
req.Namespace = velatypes.DefaultKubeVelaNS
|
||||
err = cli.Get(ctx, req, init)
|
||||
return init, err
|
||||
}
|
||||
return init, err
|
||||
}
|
||||
|
||||
// GetBuildInInitializer get build-in initializer from configMap in vela-system namespace
|
||||
func GetBuildInInitializer(ctx context.Context, cli client.Client, name string) (*v1beta1.Initializer, error) {
|
||||
listOpts := []client.ListOption{
|
||||
client.InNamespace(velatypes.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAddonsName: name,
|
||||
},
|
||||
}
|
||||
configMapList := new(corev1.ConfigMapList)
|
||||
err := cli.List(ctx, configMapList, listOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(configMapList.Items) != 1 {
|
||||
return nil, errors.Errorf("fail to get build-in initializer %s, there are %d matched initializers", name, len(configMapList.Items))
|
||||
}
|
||||
|
||||
init := new(v1beta1.Initializer)
|
||||
initYaml := configMapList.Items[0].Data["initializer"]
|
||||
err = yaml.Unmarshal([]byte(initYaml), init)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "fail to unmarshal build-in initializer %s from configmap", name)
|
||||
}
|
||||
|
||||
return init, nil
|
||||
}
|
||||
|
||||
// ReadyCondition generate ready condition for conditionType
|
||||
func ReadyCondition(tpy string) runtimev1alpha1.Condition {
|
||||
return runtimev1alpha1.Condition{
|
||||
|
||||
@@ -56,6 +56,9 @@ const (
|
||||
LabelControllerRevisionComponent = "controller.oam.dev/component"
|
||||
// LabelComponentRevisionHash records the hash value of a component
|
||||
LabelComponentRevisionHash = "app.oam.dev/component-revision-hash"
|
||||
|
||||
// LabelAddonsName records the name of initializer stored in configMap
|
||||
LabelAddonsName = "addons.oam.dev/type"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -479,7 +479,7 @@ func EndReconcileWithNegativeCondition(ctx context.Context, r client.StatusClien
|
||||
}
|
||||
// if no condition is changed, patching status can not trigger requeue, so we must return an error to
|
||||
// requeue the resource
|
||||
return fmt.Errorf(ErrReconcileErrInCondition, condition[0].Type, condition[0].Message)
|
||||
return errors.Errorf(ErrReconcileErrInCondition, condition[0].Type, condition[0].Message)
|
||||
}
|
||||
|
||||
// EndReconcileWithPositiveCondition is used to handle reconcile success for a conditioned resource.
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/applicationrollout"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/component"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/componentdefinition"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/initializer"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,7 @@ func Register(mgr manager.Manager, args controller.Args) {
|
||||
componentdefinition.RegisterMutatingHandler(mgr, args)
|
||||
componentdefinition.RegisterValidatingHandler(mgr, args)
|
||||
traitdefinition.RegisterValidatingHandler(mgr, args)
|
||||
initializer.RegisterValidatingHandler(mgr, args)
|
||||
applicationrollout.RegisterMutatingHandler(mgr)
|
||||
applicationrollout.RegisterValidatingHandler(mgr)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterValidatingHandler will register TraitDefinition validation to webhook
|
||||
// RegisterValidatingHandler will register ComponentDefinition validation to webhook
|
||||
func RegisterValidatingHandler(mgr manager.Manager, args controller.Args) {
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/validating-core-oam-dev-v1beta1-componentdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright 2021. The KubeVela Authors.
|
||||
|
||||
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 initializer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
)
|
||||
|
||||
var initializerGVR = v1beta1.SchemeGroupVersion.WithResource("initializers")
|
||||
|
||||
// ValidatingHandler handles validation of initializer
|
||||
type ValidatingHandler struct {
|
||||
Client client.Client
|
||||
|
||||
// Decoder decodes object
|
||||
Decoder *admission.Decoder
|
||||
}
|
||||
|
||||
var _ inject.Client = &ValidatingHandler{}
|
||||
|
||||
// InjectClient injects the client into the InitializerValidateHandler
|
||||
func (h *ValidatingHandler) InjectClient(c client.Client) error {
|
||||
if h.Client != nil {
|
||||
return nil
|
||||
}
|
||||
h.Client = c
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ admission.DecoderInjector = &ValidatingHandler{}
|
||||
|
||||
// InjectDecoder injects the decoder into the ValidatingHandler
|
||||
func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
|
||||
h.Decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ admission.Handler = &ValidatingHandler{}
|
||||
|
||||
// Handle validate initializer
|
||||
func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
obj := &v1beta1.Initializer{}
|
||||
if req.Resource.String() != initializerGVR.String() {
|
||||
return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", initializerGVR))
|
||||
}
|
||||
|
||||
if req.Operation == admissionv1beta1.Create || req.Operation == admissionv1beta1.Update {
|
||||
err := h.Decoder.Decode(req, obj)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
for _, depend := range obj.Spec.DependsOn {
|
||||
_, err = utils.GetInitializer(ctx, h.Client, depend.Ref.Namespace, depend.Ref.Name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) && (depend.Ref.Namespace == "" || depend.Ref.Namespace == velatypes.DefaultKubeVelaNS) {
|
||||
_, err = utils.GetBuildInInitializer(ctx, h.Client, depend.Ref.Name)
|
||||
if err != nil {
|
||||
return admission.Denied(fmt.Sprintf("fail to get dependOn Initializer %s from err: %s", depend.Ref.Name, err.Error()))
|
||||
}
|
||||
continue
|
||||
}
|
||||
return admission.Denied(fmt.Sprintf("fail to get dependOn Initializer %s err: %s", depend.Ref.Name, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return admission.ValidationResponse(true, "")
|
||||
}
|
||||
|
||||
// RegisterValidatingHandler will register initializer validation to webhook
|
||||
func RegisterValidatingHandler(mgr manager.Manager, args controller.Args) {
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/validating-core-oam-dev-v1beta1-initializers", &webhook.Admission{Handler: &ValidatingHandler{}})
|
||||
}
|
||||
@@ -23,12 +23,14 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
@@ -116,104 +118,7 @@ var _ = Describe("Initializer Normal tests", func() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if init.Status.ObservedGeneration < init.Generation {
|
||||
return fmt.Errorf("environment was not successfully initialized")
|
||||
}
|
||||
return nil
|
||||
}, 30*time.Second, 5*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test apply initializer depends on other initializer", func() {
|
||||
compName := "env2-comp"
|
||||
|
||||
init := &v1beta1.Initializer{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Initializer",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env2",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.InitializerSpec{
|
||||
AppTemplate: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []v1beta1.ApplicationComponent{
|
||||
{
|
||||
Name: compName,
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"cmd": []string{"sleep", "10000"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Initializer env2")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Create(ctx, init)
|
||||
}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
|
||||
|
||||
compName2 := "env3-comp"
|
||||
init2 := &v1beta1.Initializer{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Initializer",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env3",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.InitializerSpec{
|
||||
AppTemplate: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []v1beta1.ApplicationComponent{
|
||||
{
|
||||
Name: compName2,
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"cmd": []string{"sleep", "10000"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DependsOn: []v1beta1.DependsOn{
|
||||
{
|
||||
Ref: corev1.ObjectReference{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "Initializer",
|
||||
Name: "env2",
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Initializer env3 which depends on env2")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Create(ctx, init2)
|
||||
}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
|
||||
|
||||
By("Verify the application is created successfully")
|
||||
app2 := new(v1beta1.Application)
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Name: init2.Name, Namespace: namespace}, app2)
|
||||
}, 60*time.Second, 2*time.Millisecond).Should(Succeed())
|
||||
|
||||
By("Verify the initializer env3 successfully initialized the environment")
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: init2.Name, Namespace: namespace}, init2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if init2.Status.ObservedGeneration < init2.Generation {
|
||||
if init.Status.Phase != v1beta1.InitializerSuccess {
|
||||
return fmt.Errorf("environment was not successfully initialized")
|
||||
}
|
||||
return nil
|
||||
@@ -268,10 +173,257 @@ var _ = Describe("Initializer Normal tests", func() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if init.Status.ObservedGeneration < init.Generation {
|
||||
if init.Status.Phase != v1beta1.InitializerSuccess {
|
||||
return fmt.Errorf("environment was not successfully initialized")
|
||||
}
|
||||
return nil
|
||||
}, 30*time.Second, 5*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
Context("Test apply initializer depends on other initializer", func() {
|
||||
|
||||
It("initializer depends on existing initializer", func() {
|
||||
compName := "env2-comp"
|
||||
|
||||
init := &v1beta1.Initializer{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Initializer",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env2",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.InitializerSpec{
|
||||
AppTemplate: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []v1beta1.ApplicationComponent{
|
||||
{
|
||||
Name: compName,
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"cmd": []string{"sleep", "10000"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Initializer env2")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Create(ctx, init)
|
||||
}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
|
||||
|
||||
compName2 := "env3-comp"
|
||||
init2 := &v1beta1.Initializer{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Initializer",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env3",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.InitializerSpec{
|
||||
AppTemplate: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []v1beta1.ApplicationComponent{
|
||||
{
|
||||
Name: compName2,
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"cmd": []string{"sleep", "10000"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DependsOn: []v1beta1.DependsOn{
|
||||
{
|
||||
Ref: corev1.ObjectReference{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "Initializer",
|
||||
Name: "env2",
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Initializer env3 which depends on env2")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Create(ctx, init2)
|
||||
}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
|
||||
|
||||
By("Verify the application is created successfully")
|
||||
app2 := new(v1beta1.Application)
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Name: init2.Name, Namespace: namespace}, app2)
|
||||
}, 60*time.Second, 2*time.Millisecond).Should(Succeed())
|
||||
|
||||
By("Verify the initializer env3 successfully initialized the environment")
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: init2.Name, Namespace: namespace}, init2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if init2.Status.Phase != v1beta1.InitializerSuccess {
|
||||
return fmt.Errorf("environment was not successfully initialized")
|
||||
}
|
||||
return nil
|
||||
}, 30*time.Second, 5*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("initializer depends on non build-in initializer, should be rejected by webhook", func() {
|
||||
init := &v1beta1.Initializer{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Initializer",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env-depends",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.InitializerSpec{
|
||||
AppTemplate: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []v1beta1.ApplicationComponent{
|
||||
{
|
||||
Name: "",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"cmd": []string{"sleep", "10000"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DependsOn: []v1beta1.DependsOn{
|
||||
{
|
||||
Ref: corev1.ObjectReference{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "Initializer",
|
||||
Name: "non-build-in",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Initializer env-depends")
|
||||
Expect(k8sClient.Create(ctx, init)).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("initializer depends on build-in initializer", func() {
|
||||
initCm := &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "build-in",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
"addons.oam.dev/type": "build-in",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"initializer": initYaml,
|
||||
},
|
||||
}
|
||||
|
||||
By("create build-in addon")
|
||||
Expect(k8sClient.Create(ctx, initCm)).Should(Succeed())
|
||||
|
||||
init := &v1beta1.Initializer{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Initializer",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env-depends-buildin",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.InitializerSpec{
|
||||
AppTemplate: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []v1beta1.ApplicationComponent{
|
||||
{
|
||||
Name: "",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
"cmd": []string{"sleep", "10000"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DependsOn: []v1beta1.DependsOn{
|
||||
{
|
||||
Ref: corev1.ObjectReference{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "Initializer",
|
||||
Name: "build-in",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("Create Initializer env-depends")
|
||||
Expect(k8sClient.Create(ctx, init)).Should(Succeed())
|
||||
|
||||
By("Check build-in Initializer is ready")
|
||||
buildInInit := new(v1beta1.Initializer)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "build-in", Namespace: "vela-system"}, buildInInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if buildInInit.Status.Phase == v1beta1.InitializerSuccess {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("initializer %s is not ready", buildInInit.Name)
|
||||
}, 30*time.Second, 500*time.Millisecond).Should(Succeed())
|
||||
|
||||
By("Check Initializer env-depends-buildin is ready")
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "env-depends-buildin", Namespace: namespace}, buildInInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if buildInInit.Status.Phase == v1beta1.InitializerSuccess {
|
||||
return nil
|
||||
}
|
||||
return errors.New("initializer env-depends-buildin is not ready")
|
||||
}, 30*time.Second, 500*time.Millisecond).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var initYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Initializer
|
||||
metadata:
|
||||
annotations:
|
||||
addons.oam.dev/description: Kruise is a Kubernetes extended suite for application automations
|
||||
name: build-in
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appTemplate:
|
||||
spec:
|
||||
components:
|
||||
- name: kruise-repo
|
||||
type: worker
|
||||
properties:
|
||||
image: busybox,
|
||||
cmd: ["sleep", "10000"]
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user