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:
yangsoon
2021-07-20 20:40:01 +08:00
committed by GitHub
parent ff6fcd9f10
commit 5e064219e7
12 changed files with 522 additions and 120 deletions
@@ -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
}
+43
View File
@@ -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{
+3
View File
@@ -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 (
+1 -1
View File
@@ -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.
+2
View File
@@ -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{}})
}
+251 -99
View File
@@ -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"]
`