diff --git a/pkg/controller/common/rollout/rollout_webhook.go b/pkg/controller/common/rollout/rollout_webhook.go index 9540e987e..9b3a32f9a 100644 --- a/pkg/controller/common/rollout/rollout_webhook.go +++ b/pkg/controller/common/rollout/rollout_webhook.go @@ -64,7 +64,10 @@ func makeHTTPRequest(ctx context.Context, webhookEndPoint, method string, payloa // failed even with retry if err != nil { - return nil, r.StatusCode, err + if r != nil { + return nil, r.StatusCode, err + } + return nil, -1, err } return body, r.StatusCode, nil } diff --git a/pkg/controller/common/rollout/rollout_webhook_test.go b/pkg/controller/common/rollout/rollout_webhook_test.go index c50341144..48ad8dea4 100644 --- a/pkg/controller/common/rollout/rollout_webhook_test.go +++ b/pkg/controller/common/rollout/rollout_webhook_test.go @@ -18,7 +18,7 @@ const mockUrl = "127.0.0.1:4848" func Test_MakeHTTPRequest(t *testing.T) { ctx := context.TODO() - type parameter struct { + type mockHTTPParameter struct { method string statusCode int body string @@ -29,15 +29,16 @@ func Test_MakeHTTPRequest(t *testing.T) { body string } tests := map[string]struct { - method string - payload interface{} - parameter parameter - want want + url string + method string + payload interface{} + httpParameter mockHTTPParameter + want want }{ "Test normal case": { method: http.MethodPost, payload: "doesn't matter", - parameter: parameter{ + httpParameter: mockHTTPParameter{ method: http.MethodPost, statusCode: http.StatusAccepted, body: "all good", @@ -48,10 +49,25 @@ func Test_MakeHTTPRequest(t *testing.T) { body: "all good", }, }, + "Test http failed case with retry": { + url: "127.0.0.1:13622", + method: http.MethodPost, + payload: "doesn't matter", + httpParameter: mockHTTPParameter{ + method: http.MethodGet, + statusCode: http.StatusAccepted, + body: "doesn't matter", + }, + want: want{ + err: fmt.Errorf("internal server error, status code = %d", http.StatusNotImplemented), + statusCode: -1, + body: "", + }, + }, "Test failed case with retry": { method: http.MethodPost, payload: "doesn't matter", - parameter: parameter{ + httpParameter: mockHTTPParameter{ method: http.MethodPost, statusCode: http.StatusNotImplemented, body: "please retry", @@ -65,7 +81,7 @@ func Test_MakeHTTPRequest(t *testing.T) { "Test client error failed case": { method: http.MethodPost, payload: "doesn't matter", - parameter: parameter{ + httpParameter: mockHTTPParameter{ method: http.MethodPost, statusCode: http.StatusBadRequest, body: "bad request", @@ -80,22 +96,33 @@ func Test_MakeHTTPRequest(t *testing.T) { for testName, tt := range tests { t.Run(testName, func(t *testing.T) { // generate a test server so we can capture and inspect the request - testServer := NewMock(tt.parameter.method, tt.parameter.statusCode, tt.parameter.body) + testServer := NewMock(tt.httpParameter.method, tt.httpParameter.statusCode, tt.httpParameter.body) defer testServer.Close() - gotReply, gotCode, gotErr := makeHTTPRequest(ctx, "http://"+mockUrl, tt.method, tt.payload) - if (tt.want.err == nil && gotErr != nil) || (tt.want.err != nil && gotErr == nil) { - t.Errorf("\n%s\nr.Reconcile(...): want error `%s`, got error:`%s`\n", testName, tt.want.err, gotErr) - } - if tt.want.err != nil && gotErr != nil && gotErr.Error() != tt.want.err.Error() { - t.Errorf("\n%s\nr.Reconcile(...): want error `%s`, got error:`%s`\n", testName, tt.want.err, gotErr) - } - if string(gotReply) != tt.want.body { - t.Errorf("\n%s\nr.Reconcile(...): want reply `%s`, got reply:`%s`\n", testName, tt.want.body, string(gotReply)) + if len(tt.url) == 0 { + tt.url = mockUrl } + gotReply, gotCode, gotErr := makeHTTPRequest(ctx, "http://"+tt.url, tt.method, tt.payload) if gotCode != tt.want.statusCode { t.Errorf("\n%s\nr.Reconcile(...): want code `%d`, got code:`%d`\n", testName, tt.want.statusCode, gotCode) } + if gotCode == -1 { + // we don't know exactly what error we should get when the network call failed + if gotErr == nil { + t.Errorf("\n%s\nr.Reconcile(...): want some error, got error:`%s`\n", testName, gotErr) + } + } else { + if (tt.want.err == nil && gotErr != nil) || (tt.want.err != nil && gotErr == nil) { + t.Errorf("\n%s\nr.Reconcile(...): want error `%s`, got error:`%s`\n", testName, tt.want.err, gotErr) + } + if tt.want.err != nil && gotErr != nil && gotErr.Error() != tt.want.err.Error() { + t.Errorf("\n%s\nr.Reconcile(...): want error `%s`, got error:`%s`\n", testName, tt.want.err, gotErr) + } + + } + if string(gotReply) != tt.want.body { + t.Errorf("\n%s\nr.Reconcile(...): want reply `%s`, got reply:`%s`\n", testName, tt.want.body, string(gotReply)) + } }) } } diff --git a/test/e2e-test/rollout_plan_test.go b/test/e2e-test/rollout_plan_test.go new file mode 100644 index 000000000..480c226cf --- /dev/null +++ b/test/e2e-test/rollout_plan_test.go @@ -0,0 +1,207 @@ +package controllers_test + +import ( + "context" + "math/rand" + "strconv" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" + kruise "github.com/openkruise/kruise-api/apps/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + oamstd "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" + "github.com/oam-dev/kubevela/pkg/controller/utils" + "github.com/oam-dev/kubevela/pkg/oam/util" +) + +var _ = Describe("Test Rolling out Application", func() { + ctx := context.Background() + namespace := "rolling" + var ns corev1.Namespace + + BeforeEach(func() { + logf.Log.Info("Start to run a test, clean up previous resources") + namespace = string(strconv.AppendInt([]byte(namespace), rand.Int63(), 16)) + ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + // delete the namespace with all its resources + Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))). + Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{})) + logf.Log.Info("make sure all the resources are removed") + objectKey := client.ObjectKey{ + Name: namespace, + } + res := &corev1.Namespace{} + Eventually( + func() error { + return k8sClient.Get(ctx, objectKey, res) + }, + time.Second*120, time.Millisecond*500).Should(&util.NotFoundMatcher{}) + Eventually( + func() error { + return k8sClient.Create(ctx, &ns) + }, + time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + By("Install CloneSet based workloadDefinition") + var cd v1alpha2.WorkloadDefinition + Expect(readYaml("testdata/rollout/clonesetDefinition.yaml", &cd)).Should(BeNil()) + // create the workloadDefinition if not exist + Eventually( + func() error { + return k8sClient.Create(ctx, &cd) + }, + time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + }) + + AfterEach(func() { + logf.Log.Info("Clean up resources") + // delete the namespace with all its resources + Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(BeNil()) + }) + + It("Basic cloneset rollout", func() { + By("Apply an application") + var app v1alpha2.Application + Expect(readYaml("testdata/rollout/app-source.yaml", &app)).Should(BeNil()) + app.Namespace = namespace + Expect(k8sClient.Create(ctx, &app)).Should(Succeed()) + By("Get Application latest status after AppConfig created") + Eventually( + func() *v1alpha2.Revision { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: app.Name}, &app) + return app.Status.LatestRevision + }, + time.Second*30, time.Millisecond*500).ShouldNot(BeNil()) + By("Wait for AppConfig1 synced") + var appConfig1 v1alpha2.ApplicationConfiguration + Eventually( + func() corev1.ConditionStatus { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: app.Status.LatestRevision.Name}, &appConfig1) + return appConfig1.Status.GetCondition(v1alpha1.TypeSynced).Status + }, + time.Second*30, time.Millisecond*500).Should(BeEquivalentTo(corev1.ConditionTrue)) + + By("Mark the application as rolling") + Expect(readYaml("testdata/rollout/app-source-prep.yaml", &app)).Should(BeNil()) + app.Namespace = namespace + Expect(k8sClient.Update(ctx, &app)).Should(Succeed()) + By("Wait for AppConfig1 to be templated") + Eventually( + func() v1alpha2.RollingStatus { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: app.Status.LatestRevision.Name}, &appConfig1) + return appConfig1.Status.RollingStatus + }, + time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(v1alpha2.RollingTemplated)) + + By("Update the application during rolling") + Expect(readYaml("testdata/rollout/app-target.yaml", &app)).Should(BeNil()) + app.Namespace = namespace + Expect(k8sClient.Update(ctx, &app)).Should(Succeed()) + By("Get Application latest status after AppConfig created") + Eventually( + func() int64 { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: app.Name}, &app) + return app.Status.LatestRevision.Revision + }, + time.Second*10, time.Millisecond*500).ShouldNot(BeEquivalentTo(1)) + By("Wait for AppConfig2 synced") + var appConfig2 v1alpha2.ApplicationConfiguration + Eventually( + func() corev1.ConditionStatus { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: app.Status.LatestRevision.Name}, &appConfig2) + return appConfig2.Status.GetCondition(v1alpha1.TypeSynced).Status + }, + time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(corev1.ConditionTrue)) + + By("Wait for AppConfig2 to be templated") + Eventually( + func() v1alpha2.RollingStatus { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: app.Status.LatestRevision.Name}, &appConfig2) + return appConfig2.Status.RollingStatus + }, + time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(v1alpha2.RollingTemplated)) + + By("Get the cloneset workload") + var kc kruise.CloneSet + workloadName := utils.ExtractComponentName(appConfig2.Spec.Components[0].RevisionName) + Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: workloadName}, + &kc)).ShouldNot(HaveOccurred()) + Expect(kc.Spec.UpdateStrategy.Paused).Should(BeTrue()) + + By("Apply the application rollout that stops after two batches") + var appDeploy v1alpha2.ApplicationDeployment + Expect(readYaml("testdata/rollout/app-deploy-pause.yaml", &appDeploy)).Should(BeNil()) + appDeploy.Namespace = namespace + Expect(k8sClient.Create(ctx, &appDeploy)).Should(Succeed()) + + By("Wait for the rollout phase change to rolling in batches") + Eventually( + func() oamstd.RollingState { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy) + return appDeploy.Status.RollingState + }, + time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(oamstd.RollingInBatchesState)) + + By("Wait for rollout to finish two batches") + Eventually( + func() int32 { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy) + return appDeploy.Status.CurrentBatch + }, + time.Second*60, time.Millisecond*500).Should(BeEquivalentTo(1)) + + By("Verify that the rollout stops at two batches") + // wait for the batch to be ready + Eventually( + func() oamstd.BatchRollingState { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy) + return appDeploy.Status.BatchRollingState + }, + time.Second*60, time.Millisecond*500).Should(Equal(oamstd.BatchReadyState)) + // wait for 30 seconds, it should still be at 1 + time.Sleep(30 * time.Second) + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy) + Expect(appDeploy.Status.CurrentBatch).Should(BeEquivalentTo(1)) + Expect(appDeploy.Status.BatchRollingState).Should(BeEquivalentTo(oamstd.BatchReadyState)) + + Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: workloadName}, + &kc)).ShouldNot(HaveOccurred()) + Expect(kc.Status.UpdatedReplicas).Should(BeEquivalentTo(3)) + Expect(kc.Status.UpdatedReadyReplicas).Should(BeEquivalentTo(3)) + + By("Finish the application rollout") + Expect(readYaml("testdata/rollout/app-deploy-finish.yaml", &appDeploy)).Should(BeNil()) + appDeploy.Namespace = namespace + Expect(k8sClient.Update(ctx, &appDeploy)).Should(Succeed()) + + By("Wait for the rollout phase change to succeeded") + Eventually( + func() oamstd.RollingState { + k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appDeploy.Name}, &appDeploy) + return appDeploy.Status.RollingState + }, + time.Second*60, time.Millisecond*500).Should(Equal(oamstd.RolloutSucceedState)) + + By("Wait for rollout to finish two batches") + Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: workloadName}, + &kc)).ShouldNot(HaveOccurred()) + Expect(kc.Status.UpdatedReplicas).Should(BeEquivalentTo(5)) + Expect(kc.Status.UpdatedReadyReplicas).Should(BeEquivalentTo(5)) + // Clean up + k8sClient.Delete(ctx, &appDeploy) + k8sClient.Delete(ctx, &appConfig2) + k8sClient.Delete(ctx, &appConfig1) + k8sClient.Delete(ctx, &app) + }) +}) diff --git a/test/e2e-test/suite_test.go b/test/e2e-test/suite_test.go index daff0cefd..81fe94494 100644 --- a/test/e2e-test/suite_test.go +++ b/test/e2e-test/suite_test.go @@ -19,10 +19,12 @@ import ( "context" "encoding/json" "testing" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + kruise "github.com/openkruise/kruise-api/apps/v1alpha1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" @@ -75,6 +77,8 @@ var _ = BeforeSuite(func(done Done) { Expect(err).Should(BeNil()) err = crdv1.AddToScheme(scheme) Expect(err).Should(BeNil()) + err = kruise.AddToScheme(scheme) + Expect(err).Should(BeNil()) depExample := &unstructured.Unstructured{} depExample.SetGroupVersionKind(schema.GroupVersionKind{ Group: "example.com", @@ -94,15 +98,6 @@ var _ = BeforeSuite(func(done Done) { } By("Finished setting up test environment") - By("Applying CRD of WorkloadDefinition and TraitDefinition") - var workloadDefinitionCRD crdv1.CustomResourceDefinition - Expect(readYaml("../../charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml", &workloadDefinitionCRD)).Should(BeNil()) - Expect(k8sClient.Create(context.Background(), &workloadDefinitionCRD)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) - - var traitDefinitionCRD crdv1.CustomResourceDefinition - Expect(readYaml("../../charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml", &traitDefinitionCRD)).Should(BeNil()) - Expect(k8sClient.Create(context.Background(), &traitDefinitionCRD)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) - // Create manual scaler trait definition manualscalertrait = v1alpha2.TraitDefinition{ ObjectMeta: metav1.ObjectMeta{ @@ -263,7 +258,11 @@ var _ = BeforeSuite(func(done Done) { By("Create workload definition for revision mechanism test") var nwd v1alpha2.WorkloadDefinition Expect(readYaml("testdata/revision/workload-def.yaml", &nwd)).Should(BeNil()) - Expect(k8sClient.Create(context.Background(), &nwd)).Should(Succeed()) + Eventually( + func() error { + return k8sClient.Create(context.Background(), &nwd) + }, + time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) close(done) }, 300) @@ -288,12 +287,13 @@ var _ = AfterSuite(func() { Expect(k8sClient.Delete(context.Background(), &crd)).Should(BeNil()) By("Deleted the custom resource definition") + By("Deleting all the definitions by deleting the definition CRDs") crd = crdv1.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ Name: "workloaddefinitions.core.oam.dev", }, } - Expect(k8sClient.Delete(context.Background(), &crd)).Should(BeNil()) + Expect(k8sClient.Delete(context.Background(), &crd)).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{})) By("Deleted the workloaddefinitions CRD") crd = crdv1.CustomResourceDefinition{ @@ -301,6 +301,6 @@ var _ = AfterSuite(func() { Name: "traitdefinitions.core.oam.dev", }, } - Expect(k8sClient.Delete(context.Background(), &crd)).Should(BeNil()) + Expect(k8sClient.Delete(context.Background(), &crd)).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{})) By("Deleted the workloaddefinitions CRD") }) diff --git a/test/e2e-test/testdata/rollout/app-deploy-finish.yaml b/test/e2e-test/testdata/rollout/app-deploy-finish.yaml new file mode 100644 index 000000000..adcf0bd33 --- /dev/null +++ b/test/e2e-test/testdata/rollout/app-deploy-finish.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: ApplicationDeployment +metadata: + name: rolling-e2e-test +spec: + # application (revision) reference + targetApplicationName: test-e2e-rolling-v2 + sourceApplicationName: test-e2e-rolling-v1 + # HPA reference (optional) + componentList: + - metrics-provider + rolloutPlan: + rolloutStrategy: "IncreaseFirst" + rolloutBatches: + - replicas: 10% + - replicas: 2 + - replicas: 2 + batchPartition: 2 diff --git a/test/e2e-test/testdata/rollout/app-deploy-pause.yaml b/test/e2e-test/testdata/rollout/app-deploy-pause.yaml new file mode 100644 index 000000000..c3623a19c --- /dev/null +++ b/test/e2e-test/testdata/rollout/app-deploy-pause.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: ApplicationDeployment +metadata: + name: rolling-e2e-test +spec: + # application (revision) reference + targetApplicationName: test-e2e-rolling-v2 + sourceApplicationName: test-e2e-rolling-v1 + # HPA reference (optional) + componentList: + - metrics-provider + rolloutPlan: + rolloutStrategy: "IncreaseFirst" + rolloutBatches: + - replicas: 10% + - replicas: 2 + - replicas: 2 + batchPartition: 1 diff --git a/test/e2e-test/testdata/rollout/app-source-prep.yaml b/test/e2e-test/testdata/rollout/app-source-prep.yaml new file mode 100644 index 000000000..617271cfe --- /dev/null +++ b/test/e2e-test/testdata/rollout/app-source-prep.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-e2e-rolling + annotations: + "app.oam.dev/rolling-components": "metrics-provider" + "app.oam.dev/rollout-template": "true" +spec: + components: + - name: metrics-provider + type: clonesetservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:4.0.6 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/test/e2e-test/testdata/rollout/app-source.yaml b/test/e2e-test/testdata/rollout/app-source.yaml new file mode 100644 index 000000000..16c35f77a --- /dev/null +++ b/test/e2e-test/testdata/rollout/app-source.yaml @@ -0,0 +1,15 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-e2e-rolling +spec: + components: + - name: metrics-provider + type: clonesetservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:4.0.6 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/test/e2e-test/testdata/rollout/app-target.yaml b/test/e2e-test/testdata/rollout/app-target.yaml new file mode 100644 index 000000000..bb1310307 --- /dev/null +++ b/test/e2e-test/testdata/rollout/app-target.yaml @@ -0,0 +1,18 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: test-e2e-rolling + annotations: + "app.oam.dev/rolling-components": "metrics-provider" + "app.oam.dev/rollout-template": "true" +spec: + components: + - name: metrics-provider + type: clonesetservice + settings: + cmd: + - ./podinfo + - stress-cpu=1 + image: stefanprodan/podinfo:5.0.2 + port: 8080 + updateStrategyType: InPlaceIfPossible \ No newline at end of file diff --git a/test/e2e-test/testdata/rollout/clonesetDefinition.yaml b/test/e2e-test/testdata/rollout/clonesetDefinition.yaml new file mode 100644 index 000000000..13c2b27af --- /dev/null +++ b/test/e2e-test/testdata/rollout/clonesetDefinition.yaml @@ -0,0 +1,105 @@ +# Code generated by KubeVela templates. DO NOT EDIT. +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: clonesetservice + annotations: + definition.oam.dev/description: "Describes long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers. + If workload type is skipped for any service defined in Appfile, it will be defaulted to `webservice` type." +spec: + definitionRef: + name: clonesets.apps.kruise.io + schematic: + cue: + template: | + output: { + apiVersion: "apps.kruise.io/v1alpha1" + kind: "CloneSet" + metadata: labels: { + "app.oam.dev/component": context.name + } + spec: { + replicas: parameter.replicas + selector: matchLabels: { + "app.oam.dev/component": context.name + } + + template: { + metadata: labels: { + "app.oam.dev/component": context.name + } + + spec: { + containers: [{ + name: context.name + image: parameter.image + + if parameter["cmd"] != _|_ { + command: parameter.cmd + } + + if parameter["env"] != _|_ { + env: parameter.env + } + + if context["config"] != _|_ { + env: context.config + } + + ports: [{ + containerPort: parameter.port + }] + + if parameter["cpu"] != _|_ { + resources: { + limits: + cpu: parameter.cpu + requests: + cpu: parameter.cpu + } + } + }] + } + } + if parameter["updateStrategyType"] != _|_ { + updateStrategy: { + type: parameter.updateStrategyType + } + } + } + } + parameter: { + // +usage=Which image would you like to use for your service + // +short=i + image: string + + // +usage=Commands to run in the container + cmd?: [...string] + + // +usage=Which port do you want customer traffic sent to + // +short=p + port: *80 | int + // +usage=Define arguments by using environment variables + env?: [...{ + // +usage=Environment variable name + name: string + // +usage=The value of the environment variable + value?: string + // +usage=Specifies a source the value of this var should come from + valueFrom?: { + // +usage=Selects a key of a secret in the pod's namespace + secretKeyRef: { + // +usage=The name of the secret in the pod's namespace to select from + name: string + // +usage=The key of the secret to select from. Must be a valid secret key + key: string + } + } + }] + // +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core) + cpu?: string + // +usage=Cloneset updateStrategy, candidates are `ReCreate`/`InPlaceIfPossible`/`InPlaceOnly` + updateStrategyType?: string + // +usage=Number of pods in the cloneset + replicas: *5 | int + } \ No newline at end of file