mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
Merge pull request #1034 from oam-dev/master
merge master to release-0.3 align with v0.3.3
This commit is contained in:
@@ -12,6 +12,7 @@ env:
|
||||
ENDPOINT: oss-cn-hangzhou.aliyuncs.com
|
||||
ACCESS_KEY: ${{ secrets.OSS_ACCESS_KEY }}
|
||||
ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
ARTIFACT_HUB_REPOSITORY_ID: ${{ secrets.ARTIFACT_HUB_REPOSITORY_ID }}
|
||||
|
||||
jobs:
|
||||
publish-images:
|
||||
@@ -63,6 +64,11 @@ jobs:
|
||||
docker.io/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
publish-charts:
|
||||
env:
|
||||
HELM_CHARTS_DIR: charts
|
||||
HELM_CHART: charts/vela-core
|
||||
LEGACY_HELM_CHART: legacy/charts/vela-core-legacy
|
||||
LOCAL_OSS_DIRECTORY: .oss/
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
@@ -83,21 +89,34 @@ jobs:
|
||||
uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: v3.4.0
|
||||
- name: Prepare legacy chart
|
||||
run: |
|
||||
rsync -r $LEGACY_HELM_CHART $HELM_CHARTS_DIR
|
||||
rsync -r $HELM_CHART/* $LEGACY_HELM_CHART --exclude=Chart.yaml --exclude=crds
|
||||
- name: Tag helm chart image
|
||||
run: |
|
||||
version=${{ steps.get_version.outputs.VERSION }}
|
||||
sed -i "s/latest/$version/g" charts/vela-core/values.yaml
|
||||
sed -i "s/latest/$version/g" $HELM_CHART/values.yaml
|
||||
sed -i "s/latest/$version/g" $LEGACY_HELM_CHART/values.yaml
|
||||
number=${version#"v"}
|
||||
sed -i "s/0.1.0/$number/g" charts/vela-core/Chart.yaml
|
||||
sed -i "s/0.1.0/$number/g" $HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$number/g" $LEGACY_HELM_CHART/Chart.yaml
|
||||
- name: Install ossutil
|
||||
run: wget http://gosspublic.alicdn.com/ossutil/1.7.0/ossutil64 && chmod +x ossutil64 && mv ossutil64 ossutil
|
||||
- name: Configure Alibaba Cloud OSSUTIL
|
||||
run: ./ossutil --config-file .ossutilconfig config -i ${ACCESS_KEY} -k ${ACCESS_KEY_SECRET} -e ${ENDPOINT} -c .ossutilconfig
|
||||
- name: sync cloud to local
|
||||
run: ./ossutil --config-file .ossutilconfig sync oss://kubevelacharts/core .oss/
|
||||
run: ./ossutil --config-file .ossutilconfig sync oss://$BUCKET/core $LOCAL_OSS_DIRECTORY
|
||||
- name: add artifacthub stuff to the repo
|
||||
run: |
|
||||
rsync docs/en/install.md $HELM_CHART/README.md
|
||||
rsync docs/en/install.md $LEGACY_HELM_CHART/README.md
|
||||
sed -i "s/ARTIFACT_HUB_REPOSITORY_ID/$ARTIFACT_HUB_REPOSITORY_ID/g" hack/artifacthub/artifacthub-repo.yml
|
||||
rsync hack/artifacthub/artifacthub-repo.yml $LOCAL_OSS_DIRECTORY
|
||||
- name: Package helm charts
|
||||
run: |
|
||||
helm package charts/vela-core --destination .oss/
|
||||
helm repo index --url https://kubevelacharts.oss-cn-hangzhou.aliyuncs.com/core .oss/
|
||||
helm package $HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $LEGACY_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm repo index --url https://$BUCKET.$ENDPOINT/core $LOCAL_OSS_DIRECTORY
|
||||
- name: sync local to cloud
|
||||
run: ./ossutil --config-file .ossutilconfig sync .oss/ oss://kubevelacharts/core -f
|
||||
run: ./ossutil --config-file .ossutilconfig sync $LOCAL_OSS_DIRECTORY oss://$BUCKET/core -f
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ make core-run
|
||||
This command will run controller locally, it will use your local KubeConfig which means you need to have a k8s cluster
|
||||
locally. If you don't have a one, we suggest that you could setup up a cluster with [kind](https://kind.sigs.k8s.io/).
|
||||
|
||||
When you're developing `vela-core`, make sure the controller installed by `vela install` is not running.
|
||||
When you're developing `vela-core`, make sure the controller installed by helm chart is not running.
|
||||
Otherwise, it will conflict with your local running controller.
|
||||
|
||||
You can check and uninstall it by using helm.
|
||||
|
||||
@@ -248,7 +248,7 @@ endif
|
||||
|
||||
start-dashboard:
|
||||
go run pkg/server/main/startAPIServer.go &
|
||||
cd dashboard && yarn && yarn start && cd ..
|
||||
cd dashboard && npm install && npm start && cd ..
|
||||
|
||||
swagger-gen:
|
||||
$(GOBIN)/swag init -g server/route.go -d pkg/ -o pkg/server/docs/
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[](https://github.com/oam-dev/kubevela/releases)
|
||||
[](https://www.tickgit.com/browse?repo=github.com/oam-dev/kubevela)
|
||||
[](https://twitter.com/oam_dev)
|
||||
[](https://artifacthub.io/packages/search?repo=kubevela)
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ type ApplicationDeploymentSpec struct {
|
||||
|
||||
// SourceApplicationName contains the name of the application that we need to upgrade from.
|
||||
// it can be empty only when it's the first time to deploy the application
|
||||
SourceApplicationName string `json:"sourceApplicationName"`
|
||||
SourceApplicationName string `json:"sourceApplicationName,omitempty"`
|
||||
|
||||
// The list of component to upgrade in the application.
|
||||
// We only support single component application so far
|
||||
@@ -47,6 +47,19 @@ type ApplicationDeploymentSpec struct {
|
||||
RevertOnDelete *bool `json:"revertOnDelete,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationDeploymentStatus defines the observed state of ApplicationDeployment
|
||||
type ApplicationDeploymentStatus struct {
|
||||
v1alpha1.RolloutStatus `json:",inline"`
|
||||
|
||||
// LastTargetApplicationName contains the name of the application that we upgraded to
|
||||
// We will restart the rollout if this is not the same as the spec
|
||||
LastTargetApplicationName string `json:"lastTargetApplicationName"`
|
||||
|
||||
// LastSourceApplicationName contains the name of the application that we need to upgrade from.
|
||||
// We will restart the rollout if this is not the same as the spec
|
||||
LastSourceApplicationName string `json:"lastSourceApplicationName,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationDeployment is the Schema for the ApplicationDeployment API
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:categories={oam}
|
||||
@@ -55,8 +68,8 @@ type ApplicationDeployment struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ApplicationDeploymentSpec `json:"spec,omitempty"`
|
||||
Status v1alpha1.RolloutStatus `json:"status,omitempty"`
|
||||
Spec ApplicationDeploymentSpec `json:"spec,omitempty"`
|
||||
Status ApplicationDeploymentStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationDeploymentList contains a list of ApplicationDeployment
|
||||
|
||||
@@ -36,6 +36,8 @@ const (
|
||||
ApplicationRendering ApplicationPhase = "rendering"
|
||||
// ApplicationRunning means the app finished rendering and applied result to the cluster
|
||||
ApplicationRunning ApplicationPhase = "running"
|
||||
// ApplicationHealthChecking means the app finished rendering and applied result to the cluster, but still unhealthy
|
||||
ApplicationHealthChecking ApplicationPhase = "healthChecking"
|
||||
)
|
||||
|
||||
// AppStatus defines the observed state of Application
|
||||
@@ -49,6 +51,24 @@ type AppStatus struct {
|
||||
|
||||
// Components record the related Components created by Application Controller
|
||||
Components []runtimev1alpha1.TypedReference `json:"components,omitempty"`
|
||||
|
||||
// Services record the status of the application services
|
||||
Services []ApplicationComponentStatus `json:"services,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationComponentStatus record the health status of App component
|
||||
type ApplicationComponentStatus struct {
|
||||
Name string `json:"name"`
|
||||
Healthy bool `json:"healthy"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Traits []ApplicationTraitStatus `json:"traits,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationTraitStatus records the trait health status
|
||||
type ApplicationTraitStatus struct {
|
||||
Type string `json:"type"`
|
||||
Healthy bool `json:"healthy"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationTrait defines the trait of application
|
||||
|
||||
@@ -64,12 +64,36 @@ type WorkloadDefinitionSpec struct {
|
||||
// +optional
|
||||
PodSpecPath string `json:"podSpecPath,omitempty"`
|
||||
|
||||
// Status defines the custom health policy and status message for workload
|
||||
// +optional
|
||||
Status *Status `json:"status,omitempty"`
|
||||
|
||||
// Template defines the abstraction template data of the workload, it will replace the old template in extension field.
|
||||
// the data format depends on templateType, by default it's CUE
|
||||
// +optional
|
||||
Template string `json:"template,omitempty"`
|
||||
|
||||
// TemplateType defines the data format of the template, by default it's CUE format
|
||||
// Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
// +optional
|
||||
TemplateType string `json:"templateType,omitempty"`
|
||||
|
||||
// Extension is used for extension needs by OAM platform builders
|
||||
// +optional
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
Extension *runtime.RawExtension `json:"extension,omitempty"`
|
||||
}
|
||||
|
||||
// Status defines the loop back status of the abstraction by using CUE template
|
||||
type Status struct {
|
||||
// CustomStatus defines the custom status message that could display to user
|
||||
// +optional
|
||||
CustomStatus string `json:"customStatus,omitempty"`
|
||||
// HealthPolicy defines the health check policy for the abstraction
|
||||
// +optional
|
||||
HealthPolicy string `json:"healthPolicy,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// A WorkloadDefinition registers a kind of Kubernetes custom resource as a
|
||||
@@ -126,6 +150,20 @@ type TraitDefinitionSpec struct {
|
||||
// +optional
|
||||
ConflictsWith []string `json:"conflictsWith,omitempty"`
|
||||
|
||||
// Template defines the abstraction template data of the workload, it will replace the old template in extension field.
|
||||
// the data format depends on templateType, by default it's CUE
|
||||
// +optional
|
||||
Template string `json:"template,omitempty"`
|
||||
|
||||
// TemplateType defines the data format of the template, by default it's CUE format
|
||||
// Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
// +optional
|
||||
TemplateType string `json:"templateType,omitempty"`
|
||||
|
||||
// Status defines the custom health policy and status message for trait
|
||||
// +optional
|
||||
Status *Status `json:"status,omitempty"`
|
||||
|
||||
// Extension is used for extension needs by OAM platform builders
|
||||
// +optional
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
@@ -401,6 +439,13 @@ type WorkloadStatus struct {
|
||||
// ComponentRevisionName of current component
|
||||
ComponentRevisionName string `json:"componentRevisionName,omitempty"`
|
||||
|
||||
// ObservedGeneration indicates the generation observed by the appconfig controller.
|
||||
// The same field is also recorded in the annotations of workloads.
|
||||
// A workload is possible to be deleted from cluster after created.
|
||||
// This field is useful to track the observed generation of workloads after they are
|
||||
// deleted.
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// Reference to a workload created by an ApplicationConfiguration.
|
||||
Reference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
|
||||
|
||||
|
||||
@@ -34,6 +34,13 @@ func (in *AppStatus) DeepCopyInto(out *AppStatus) {
|
||||
*out = make([]v1alpha1.TypedReference, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Services != nil {
|
||||
in, out := &in.Services, &out.Services
|
||||
*out = make([]ApplicationComponentStatus, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppStatus.
|
||||
@@ -103,6 +110,26 @@ func (in *ApplicationComponent) DeepCopy() *ApplicationComponent {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationComponentStatus) DeepCopyInto(out *ApplicationComponentStatus) {
|
||||
*out = *in
|
||||
if in.Traits != nil {
|
||||
in, out := &in.Traits, &out.Traits
|
||||
*out = make([]ApplicationTraitStatus, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationComponentStatus.
|
||||
func (in *ApplicationComponentStatus) DeepCopy() *ApplicationComponentStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ApplicationComponentStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationConfiguration) DeepCopyInto(out *ApplicationConfiguration) {
|
||||
*out = *in
|
||||
@@ -344,6 +371,22 @@ func (in *ApplicationDeploymentSpec) DeepCopy() *ApplicationDeploymentSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationDeploymentStatus) DeepCopyInto(out *ApplicationDeploymentStatus) {
|
||||
*out = *in
|
||||
in.RolloutStatus.DeepCopyInto(&out.RolloutStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentStatus.
|
||||
func (in *ApplicationDeploymentStatus) DeepCopy() *ApplicationDeploymentStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ApplicationDeploymentStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationList) DeepCopyInto(out *ApplicationList) {
|
||||
*out = *in
|
||||
@@ -414,6 +457,21 @@ func (in *ApplicationTrait) DeepCopy() *ApplicationTrait {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationTraitStatus) DeepCopyInto(out *ApplicationTraitStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationTraitStatus.
|
||||
func (in *ApplicationTraitStatus) DeepCopy() *ApplicationTraitStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ApplicationTraitStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CPUResources) DeepCopyInto(out *CPUResources) {
|
||||
*out = *in
|
||||
@@ -1607,6 +1665,21 @@ func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Status) DeepCopyInto(out *Status) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status.
|
||||
func (in *Status) DeepCopy() *Status {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Status)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPSocketProbe) DeepCopyInto(out *TCPSocketProbe) {
|
||||
*out = *in
|
||||
@@ -1694,6 +1767,11 @@ func (in *TraitDefinitionSpec) DeepCopyInto(out *TraitDefinitionSpec) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(Status)
|
||||
**out = **in
|
||||
}
|
||||
if in.Extension != nil {
|
||||
in, out := &in.Extension, &out.Extension
|
||||
*out = new(runtime.RawExtension)
|
||||
@@ -1842,6 +1920,11 @@ func (in *WorkloadDefinitionSpec) DeepCopyInto(out *WorkloadDefinitionSpec) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(Status)
|
||||
**out = **in
|
||||
}
|
||||
if in.Extension != nil {
|
||||
in, out := &in.Extension, &out.Extension
|
||||
*out = new(runtime.RawExtension)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:trivialVersions=true output:artifacts:config=../legacy/charts/vela-core-legacy/crds
|
||||
//go:generate go run ../legacy/convert/main.go ../legacy/charts/vela-core-legacy/crds
|
||||
|
||||
//go:generate go run ../hack/crd/update.go ../charts/vela-core/crds/
|
||||
//go:generate go run ../hack/crd/update.go ../charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml ../legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml
|
||||
|
||||
package apis
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ type HookType string
|
||||
|
||||
const (
|
||||
// InitializeRolloutHook execute webhook during the rollout initializing phase
|
||||
InitializeRolloutHook HookType = "initilize-rollout"
|
||||
InitializeRolloutHook HookType = "initialize-rollout"
|
||||
// PreBatchRolloutHook execute webhook before each batch rollout
|
||||
PreBatchRolloutHook HookType = "pre-batch-rollout"
|
||||
// PostBatchRolloutHook execute webhook after each batch rollout
|
||||
@@ -34,38 +34,39 @@ const (
|
||||
type RollingState string
|
||||
|
||||
const (
|
||||
// Verifying verify that the rollout setting is valid and the controller can locate both the
|
||||
// VerifyingState verify that the rollout setting is valid and the controller can locate both the
|
||||
// target and the source
|
||||
Verifying RollingState = "verifying"
|
||||
// Initializing rollout is initializing all the new resources
|
||||
Initializing RollingState = "initializing"
|
||||
// Rolling rolling out
|
||||
Rolling RollingState = "rolling"
|
||||
// Finalising finalize the rolling, possibly clean up the old resources, adjust traffic
|
||||
Finalising RollingState = "finalising"
|
||||
// Succeed rollout successfully completed to match the desired target state
|
||||
Succeed RollingState = "succeed"
|
||||
// Failed rollout is failed, the target replica is not reached
|
||||
VerifyingState RollingState = "verifying"
|
||||
// InitializingState rollout is initializing all the new resources
|
||||
InitializingState RollingState = "initializing"
|
||||
// RollingInBatchesState rolling out
|
||||
RollingInBatchesState RollingState = "rollingInBatches"
|
||||
// FinalisingState finalize the rolling, possibly clean up the old resources, adjust traffic
|
||||
FinalisingState RollingState = "finalising"
|
||||
// RolloutSucceedState rollout successfully completed to match the desired target state
|
||||
RolloutSucceedState RollingState = "rolloutSucceed"
|
||||
// RolloutFailedState rollout is failed, the target replica is not reached
|
||||
// we can not move forward anymore
|
||||
// we will let the client to decide when or whether to revert
|
||||
Failed RollingState = "failed"
|
||||
RolloutFailedState RollingState = "rolloutFailed"
|
||||
)
|
||||
|
||||
// BatchRollingState is the sub state when the rollout is on the fly
|
||||
type BatchRollingState string
|
||||
|
||||
const (
|
||||
// BatchRolling still rolling the batch, the batch rolling is not completed yet
|
||||
BatchRolling BatchRollingState = "batchRolling"
|
||||
// BatchStopped rollout is stopped, the batch rolling is not completed
|
||||
BatchStopped BatchRollingState = "batchStopped"
|
||||
// BatchReady the pods in the batch are ready. Wait for auto or manual verification.
|
||||
BatchReady BatchRollingState = "batchReady"
|
||||
// BatchVerifying verifying if the application is ready to roll. This happens when it's either manual or
|
||||
// automatic with analysis
|
||||
BatchVerifying RollingState = "batchVerifying"
|
||||
// BatchAvailable one batch is ready, we could move to the batch
|
||||
BatchAvailable BatchRollingState = "batchAvailable"
|
||||
// BatchInitializingState still rolling the batch, the batch rolling is not completed yet
|
||||
BatchInitializingState BatchRollingState = "batchInitializing"
|
||||
// BatchInRollingState still rolling the batch, the batch rolling is not completed yet
|
||||
BatchInRollingState BatchRollingState = "batchInRolling"
|
||||
// BatchVerifyingState verifying if the application is ready to roll.
|
||||
BatchVerifyingState BatchRollingState = "batchVerifying"
|
||||
// BatchRolloutFailedState indicates that the batch didn't get the manual or automatic approval
|
||||
BatchRolloutFailedState BatchRollingState = "batchVerifyFailed"
|
||||
// BatchFinalizingState indicates that all the pods in the are available, we can move on to the next batch
|
||||
BatchFinalizingState BatchRollingState = "batchFinalizing"
|
||||
// BatchReadyState indicates that all the pods in the are upgraded and its state is ready
|
||||
BatchReadyState BatchRollingState = "batchReady"
|
||||
)
|
||||
|
||||
// RolloutPlan fines the details of the rollout plan
|
||||
@@ -86,7 +87,10 @@ type RolloutPlan struct {
|
||||
NumBatches *int32 `json:"numBatches,omitempty"`
|
||||
|
||||
// The exact distribution among batches.
|
||||
// mutually exclusive to NumBatches
|
||||
// mutually exclusive to NumBatches.
|
||||
// The total number cannot exceed the targetSize or the size of the source resource
|
||||
// We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum
|
||||
// We highly recommend to leave the last batch's replica field empty
|
||||
// +optional
|
||||
RolloutBatches []RolloutBatch `json:"rolloutBatches,omitempty"`
|
||||
|
||||
@@ -101,7 +105,7 @@ type RolloutPlan struct {
|
||||
// +optional
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
|
||||
// RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
// RolloutWebhooks provide a way for the rollout to interact with an external process
|
||||
// +optional
|
||||
RolloutWebhooks []RolloutWebhook `json:"rolloutWebhooks,omitempty"`
|
||||
|
||||
@@ -115,6 +119,7 @@ type RolloutPlan struct {
|
||||
type RolloutBatch struct {
|
||||
// Replicas is the number of pods to upgrade in this batch
|
||||
// it can be an absolute number (ex: 5) or a percentage of total pods
|
||||
// we will ignore the percentage of the last batch to just fill the gap
|
||||
// +optional
|
||||
// it is mutually exclusive with the PodList field
|
||||
Replicas intstr.IntOrString `json:"replicas,omitempty"`
|
||||
@@ -204,16 +209,19 @@ type MetricsExpectedRange struct {
|
||||
Max *intstr.IntOrString `json:"max,omitempty"`
|
||||
}
|
||||
|
||||
// RolloutStatus defines the observed state of Rollout
|
||||
// RolloutStatus defines the observed state of a rollout plan
|
||||
type RolloutStatus struct {
|
||||
// Conditions represents the latest available observations of a CloneSet's current state.
|
||||
runtimev1alpha1.ConditionedStatus `json:",inline"`
|
||||
|
||||
// The target resource generation
|
||||
TargetGeneration string `json:"targetGeneration"`
|
||||
// NewPodTemplateIdentifier is a string that uniquely represent the new pod template
|
||||
// each workload type could use different ways to identify that so we cannot compare between resources
|
||||
NewPodTemplateIdentifier string `json:"targetGeneration,omitempty"`
|
||||
|
||||
// The source resource generation
|
||||
SourceGeneration string `json:"sourceGeneration"`
|
||||
// lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template
|
||||
// each workload type could use different ways to identify that so we cannot compare between resources
|
||||
// We update this field only after a successful rollout
|
||||
LastAppliedPodTemplateIdentifier string `json:"lastAppliedPodTemplateIdentifier,omitempty"`
|
||||
|
||||
// RollingState is the Rollout State
|
||||
RollingState RollingState `json:"rollingState"`
|
||||
@@ -223,6 +231,7 @@ type RolloutStatus struct {
|
||||
BatchRollingState BatchRollingState `json:"batchRollingState"`
|
||||
|
||||
// The current batch the rollout is working on/blocked
|
||||
// it starts from 0
|
||||
CurrentBatch int32 `json:"currentBatch"`
|
||||
|
||||
// UpgradedReplicas is the number of Pods upgraded by the rollout controller
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// RolloutEvent is used to describe the events during rollout
|
||||
type RolloutEvent string
|
||||
|
||||
const (
|
||||
// RollingFailedEvent indicates that we encountered an unexpected error during upgrading and can't be retried
|
||||
RollingFailedEvent RolloutEvent = "RollingFailedEvent"
|
||||
|
||||
// RollingRetriableFailureEvent indicates that we encountered an unexpected but retriable error
|
||||
RollingRetriableFailureEvent RolloutEvent = "RollingRetriableFailureEvent"
|
||||
|
||||
// RollingSpecVerifiedEvent indicates that we have successfully verified that the rollout spec
|
||||
RollingSpecVerifiedEvent RolloutEvent = "RollingSpecVerifiedEvent"
|
||||
|
||||
// RollingInitializedEvent indicates that we have finished initializing all the workload resources
|
||||
RollingInitializedEvent RolloutEvent = "RollingInitializedEvent"
|
||||
|
||||
// AllBatchFinishedEvent indicates that all batches are upgraded
|
||||
AllBatchFinishedEvent RolloutEvent = "AllBatchFinishedEvent"
|
||||
|
||||
// RollingFinalizedEvent indicates that we have finalized the rollout which includes but not
|
||||
// limited to the resource garbage collection
|
||||
RollingFinalizedEvent RolloutEvent = "AllBatchFinishedEvent"
|
||||
|
||||
// InitializedOneBatchEvent indicates that we have successfully rolled out one batch
|
||||
InitializedOneBatchEvent RolloutEvent = "InitializedOneBatchEvent"
|
||||
|
||||
// FinishedOneBatchEvent indicates that we have successfully rolled out one batch
|
||||
FinishedOneBatchEvent RolloutEvent = "FinishedOneBatchEvent"
|
||||
|
||||
// BatchRolloutContinueEvent indicates that we need to continue to upgrade the pods in the batch
|
||||
BatchRolloutContinueEvent RolloutEvent = "BatchRolloutContinueEvent"
|
||||
|
||||
// BatchRolloutVerifyingEvent indicates that we are waiting for the approval of resume one batch
|
||||
BatchRolloutVerifyingEvent RolloutEvent = "BatchRolloutVerifyingEvent"
|
||||
|
||||
// OneBatchAvailableEvent indicates that the batch resource is considered available
|
||||
// this events comes after we have examine the pod readiness check and traffic shifting if needed
|
||||
OneBatchAvailableEvent RolloutEvent = "OneBatchAvailable"
|
||||
|
||||
// BatchRolloutApprovedEvent indicates that we got the approval manually
|
||||
BatchRolloutApprovedEvent RolloutEvent = "BatchRolloutApprovedEvent"
|
||||
|
||||
// BatchRolloutFailedEvent indicates that we are waiting for the approval of the
|
||||
BatchRolloutFailedEvent RolloutEvent = "BatchRolloutFailedEvent"
|
||||
|
||||
// WorkloadModifiedEvent indicates that the res
|
||||
WorkloadModifiedEvent RolloutEvent = "WorkloadModifiedEvent"
|
||||
)
|
||||
|
||||
// These are valid conditions of the rollout.
|
||||
const (
|
||||
// RolloutSpecVerified indicates that the rollout spec matches the resource we have in the cluster
|
||||
RolloutSpecVerified runtimev1alpha1.ConditionType = "RolloutSpecVerified"
|
||||
// RolloutInitialized means all the needed initialization work is done
|
||||
RolloutInitialized runtimev1alpha1.ConditionType = "Initialized"
|
||||
// RolloutInProgress means we are upgrading resources.
|
||||
RolloutInProgress runtimev1alpha1.ConditionType = "Ready"
|
||||
// RolloutSucceed means that the rollout is done.
|
||||
RolloutSucceed runtimev1alpha1.ConditionType = "Succeed"
|
||||
// BatchInitialized
|
||||
BatchInitialized runtimev1alpha1.ConditionType = "BatchInitialized"
|
||||
// BatchInRolled
|
||||
BatchInRolled runtimev1alpha1.ConditionType = "BatchInRolled"
|
||||
// BatchVerified
|
||||
BatchVerified runtimev1alpha1.ConditionType = "BatchVerified"
|
||||
// BatchRolloutFailed
|
||||
BatchRolloutFailed runtimev1alpha1.ConditionType = "BatchRolloutFailed"
|
||||
// BatchFinalized
|
||||
BatchFinalized runtimev1alpha1.ConditionType = "BatchFinalized"
|
||||
// BatchReady
|
||||
BatchReady runtimev1alpha1.ConditionType = "BatchReady"
|
||||
)
|
||||
|
||||
// NewPositiveCondition creates a positive condition type
|
||||
func NewPositiveCondition(condType runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
|
||||
return runtimev1alpha1.Condition{
|
||||
Type: condType,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.NewTime(time.Now()),
|
||||
}
|
||||
}
|
||||
|
||||
// NewNegativeCondition creates a false condition type
|
||||
func NewNegativeCondition(condType runtimev1alpha1.ConditionType, message string) runtimev1alpha1.Condition {
|
||||
return runtimev1alpha1.Condition{
|
||||
Type: condType,
|
||||
Status: v1.ConditionFalse,
|
||||
LastTransitionTime: metav1.NewTime(time.Now()),
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
const invalidRollingStateTransition = "the rollout state transition from `%s` state with `%s` is invalid"
|
||||
|
||||
const invalidBatchRollingStateTransition = "the batch rolling state transition from `%s` state with `%s` is invalid"
|
||||
|
||||
func (r *RolloutStatus) getRolloutConditionType() runtimev1alpha1.ConditionType {
|
||||
// figure out which condition type should we put in the condition depends on its state
|
||||
switch r.RollingState {
|
||||
case VerifyingState:
|
||||
return RolloutSpecVerified
|
||||
|
||||
case InitializingState:
|
||||
return RolloutInitialized
|
||||
|
||||
case RollingInBatchesState:
|
||||
switch r.BatchRollingState {
|
||||
case BatchInitializingState:
|
||||
return BatchInitialized
|
||||
|
||||
case BatchVerifyingState:
|
||||
return BatchVerified
|
||||
|
||||
case BatchFinalizingState:
|
||||
return BatchFinalized
|
||||
|
||||
case BatchReadyState:
|
||||
return BatchReady
|
||||
|
||||
default:
|
||||
return RolloutInProgress
|
||||
}
|
||||
|
||||
case FinalisingState:
|
||||
return RolloutSucceed
|
||||
|
||||
default:
|
||||
return RolloutSucceed
|
||||
}
|
||||
}
|
||||
|
||||
// RolloutRetry is a special state transition since we need an error message
|
||||
func (r *RolloutStatus) RolloutRetry(reason string) {
|
||||
// we can still retry, no change on the state
|
||||
r.SetConditions(NewNegativeCondition(r.getRolloutConditionType(), reason))
|
||||
}
|
||||
|
||||
// RolloutFailed is a special state transition since we need an error message
|
||||
func (r *RolloutStatus) RolloutFailed(reason string) {
|
||||
// set the condition first which depends on the state
|
||||
r.SetConditions(NewNegativeCondition(r.getRolloutConditionType(), reason))
|
||||
r.RollingState = RolloutFailedState
|
||||
}
|
||||
|
||||
// StateTransition is the center place to do rollout state transition
|
||||
// it returns an error if the transition is invalid
|
||||
// it changes the coming rollout state if it's valid
|
||||
func (r *RolloutStatus) StateTransition(event RolloutEvent) {
|
||||
rollingState := r.RollingState
|
||||
batchRollingState := r.BatchRollingState
|
||||
defer klog.InfoS("try to execute a rollout state transition",
|
||||
"pre rolling state", rollingState,
|
||||
"pre batch rolling state", batchRollingState,
|
||||
"post rolling state", r.RollingState,
|
||||
"post batch rolling state", r.BatchRollingState)
|
||||
|
||||
// we have special transition for these two types of event
|
||||
if event == RollingFailedEvent || event == RollingRetriableFailureEvent {
|
||||
panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event))
|
||||
}
|
||||
|
||||
switch rollingState {
|
||||
case VerifyingState:
|
||||
if event == RollingSpecVerifiedEvent {
|
||||
r.RollingState = InitializingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event))
|
||||
|
||||
case InitializingState:
|
||||
if event == RollingInitializedEvent {
|
||||
r.RollingState = RollingInBatchesState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event))
|
||||
|
||||
case RollingInBatchesState:
|
||||
r.batchStateTransition(event)
|
||||
return
|
||||
|
||||
case FinalisingState:
|
||||
if event == RollingFinalizedEvent {
|
||||
r.RollingState = RolloutSucceedState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event))
|
||||
|
||||
case RolloutSucceedState:
|
||||
if event == WorkloadModifiedEvent {
|
||||
r.RollingState = VerifyingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
if event == RollingFinalizedEvent {
|
||||
// no op
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event))
|
||||
|
||||
case RolloutFailedState:
|
||||
if event == WorkloadModifiedEvent {
|
||||
r.RollingState = VerifyingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
if event == RollingFailedEvent {
|
||||
// no op
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event))
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("invalid rolling state %s", rollingState))
|
||||
}
|
||||
}
|
||||
|
||||
// batchStateTransition handles the state transition when the rollout is in action
|
||||
func (r *RolloutStatus) batchStateTransition(event RolloutEvent) {
|
||||
batchRollingState := r.BatchRollingState
|
||||
if event == BatchRolloutFailedEvent {
|
||||
r.BatchRollingState = BatchRolloutFailedState
|
||||
r.RollingState = RolloutFailedState
|
||||
r.SetConditions(NewNegativeCondition(r.getRolloutConditionType(), "failed"))
|
||||
return
|
||||
}
|
||||
switch batchRollingState {
|
||||
case BatchInitializingState:
|
||||
if event == InitializedOneBatchEvent {
|
||||
r.BatchRollingState = BatchInRollingState
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event))
|
||||
|
||||
case BatchInRollingState:
|
||||
if event == BatchRolloutContinueEvent {
|
||||
// no op
|
||||
return
|
||||
}
|
||||
if event == BatchRolloutVerifyingEvent {
|
||||
r.BatchRollingState = BatchVerifyingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event))
|
||||
|
||||
case BatchVerifyingState:
|
||||
if event == OneBatchAvailableEvent {
|
||||
r.BatchRollingState = BatchFinalizingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
if event == BatchRolloutVerifyingEvent {
|
||||
// no op
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event))
|
||||
|
||||
case BatchFinalizingState:
|
||||
if event == FinishedOneBatchEvent {
|
||||
r.BatchRollingState = BatchReadyState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
if event == AllBatchFinishedEvent {
|
||||
// transition out of the batch loop
|
||||
r.RollingState = FinalisingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event))
|
||||
|
||||
case BatchReadyState:
|
||||
if event == BatchRolloutApprovedEvent {
|
||||
r.BatchRollingState = BatchInitializingState
|
||||
r.SetConditions(NewPositiveCondition(r.getRolloutConditionType()))
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event))
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("invalid batch rolling state %s", batchRollingState))
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,9 @@ type RouteSpec struct {
|
||||
|
||||
// Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
Provider string `json:"provider,omitempty"`
|
||||
|
||||
// IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
IngressClass string `json:"ingressClass,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines to route rule
|
||||
|
||||
@@ -18,12 +18,10 @@ package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Source record the source of Capability
|
||||
@@ -107,20 +105,6 @@ type Parameter struct {
|
||||
Alias string `json:"alias,omitempty"`
|
||||
}
|
||||
|
||||
// ConvertTemplateJSON2Object convert spec.extension to object
|
||||
func ConvertTemplateJSON2Object(in *runtime.RawExtension) (Capability, error) {
|
||||
var t Capability
|
||||
var extension Capability
|
||||
if in == nil || in.Raw == nil {
|
||||
return t, fmt.Errorf("no template found")
|
||||
}
|
||||
err := json.Unmarshal(in.Raw, &extension)
|
||||
if err == nil {
|
||||
t = extension
|
||||
}
|
||||
return t, err
|
||||
}
|
||||
|
||||
// SetFlagBy set cli flag from Parameter
|
||||
func SetFlagBy(flags *pflag.FlagSet, v Parameter) {
|
||||
name := v.Name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: vela-core
|
||||
description: A Helm chart for Kube Vela core
|
||||
description: A Helm chart for KubeVela core
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
|
||||
@@ -386,6 +386,10 @@ spec:
|
||||
componentRevisionName:
|
||||
description: ComponentRevisionName of current component
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: ObservedGeneration indicates the generation observed by the appconfig controller. The same field is also recorded in the annotations of workloads. A workload is possible to be deleted from cluster after created. This field is useful to track the observed generation of workloads after they are deleted.
|
||||
format: int64
|
||||
type: integer
|
||||
scopes:
|
||||
description: Scopes associated with this workload.
|
||||
items:
|
||||
|
||||
@@ -108,7 +108,7 @@ spec:
|
||||
description: Paused the rollout, default is false
|
||||
type: boolean
|
||||
rolloutBatches:
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches. The total number cannot exceed the targetSize or the size of the source resource We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum We highly recommend to leave the last batch's replica field empty
|
||||
items:
|
||||
description: RolloutBatch is used to describe how the each batch rollout should be
|
||||
properties:
|
||||
@@ -210,7 +210,7 @@ spec:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods it is mutually exclusive with the PodList field'
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods we will ignore the percentage of the last batch to just fill the gap it is mutually exclusive with the PodList field'
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
type: array
|
||||
@@ -218,7 +218,7 @@ spec:
|
||||
description: RolloutStrategy defines strategies for the rollout plan
|
||||
type: string
|
||||
rolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
description: RolloutWebhooks provide a way for the rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
@@ -258,11 +258,10 @@ spec:
|
||||
type: string
|
||||
required:
|
||||
- rolloutPlan
|
||||
- sourceApplicationName
|
||||
- targetApplicationName
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
description: ApplicationDeploymentStatus defines the observed state of ApplicationDeployment
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
@@ -296,17 +295,23 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
currentBatch:
|
||||
description: The current batch the rollout is working on/blocked
|
||||
description: The current batch the rollout is working on/blocked it starts from 0
|
||||
format: int32
|
||||
type: integer
|
||||
lastAppliedPodTemplateIdentifier:
|
||||
description: lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template each workload type could use different ways to identify that so we cannot compare between resources We update this field only after a successful rollout
|
||||
type: string
|
||||
lastSourceApplicationName:
|
||||
description: LastSourceApplicationName contains the name of the application that we need to upgrade from. We will restart the rollout if this is not the same as the spec
|
||||
type: string
|
||||
lastTargetApplicationName:
|
||||
description: LastTargetApplicationName contains the name of the application that we upgraded to We will restart the rollout if this is not the same as the spec
|
||||
type: string
|
||||
rollingState:
|
||||
description: RollingState is the Rollout State
|
||||
type: string
|
||||
sourceGeneration:
|
||||
description: The source resource generation
|
||||
type: string
|
||||
targetGeneration:
|
||||
description: The target resource generation
|
||||
description: NewPodTemplateIdentifier is a string that uniquely represent the new pod template each workload type could use different ways to identify that so we cannot compare between resources
|
||||
type: string
|
||||
upgradedReadyReplicas:
|
||||
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
|
||||
@@ -318,9 +323,8 @@ spec:
|
||||
type: integer
|
||||
required:
|
||||
- currentBatch
|
||||
- lastTargetApplicationName
|
||||
- rollingState
|
||||
- sourceGeneration
|
||||
- targetGeneration
|
||||
- upgradedReadyReplicas
|
||||
- upgradedReplicas
|
||||
type: object
|
||||
|
||||
@@ -127,6 +127,37 @@ spec:
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
services:
|
||||
description: Services record the status of the application services
|
||||
items:
|
||||
description: ApplicationComponentStatus record the health status of App component
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
traits:
|
||||
items:
|
||||
description: ApplicationTraitStatus records the trait health status
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- healthy
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- healthy
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
status:
|
||||
description: ApplicationPhase is a label for the condition of a application at the current time
|
||||
type: string
|
||||
|
||||
@@ -68,6 +68,22 @@ spec:
|
||||
revisionEnabled:
|
||||
description: Revision indicates whether a trait is aware of component revision
|
||||
type: boolean
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for trait
|
||||
properties:
|
||||
customStatus:
|
||||
description: CustomStatus defines the custom status message that could display to user
|
||||
type: string
|
||||
healthPolicy:
|
||||
description: HealthPolicy defines the health check policy for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object
|
||||
type: string
|
||||
|
||||
@@ -82,6 +82,22 @@ spec:
|
||||
revisionLabel:
|
||||
description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods).
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for workload
|
||||
properties:
|
||||
customStatus:
|
||||
description: CustomStatus defines the custom status message that could display to user
|
||||
type: string
|
||||
healthPolicy:
|
||||
description: HealthPolicy defines the health check policy for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
required:
|
||||
- definitionRef
|
||||
type: object
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: rollouts.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
kind: Rollout
|
||||
listKind: RolloutList
|
||||
plural: rollouts
|
||||
singular: rollout
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Rollout is the Schema for the rollouts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RolloutSpec defines the desired state of Rollout
|
||||
properties:
|
||||
rolloutPlan:
|
||||
description: RolloutPlan is the details on how to rollout the resources
|
||||
properties:
|
||||
canaryMetric:
|
||||
description: CanaryMetric provides a way for the rollout process to automatically check certain metrics before complete the process
|
||||
items:
|
||||
description: CanaryMetric holds the reference to metrics used for canary analysis
|
||||
properties:
|
||||
interval:
|
||||
description: Interval represents the windows size
|
||||
type: string
|
||||
metricsRange:
|
||||
description: Range value accepted for this metric
|
||||
properties:
|
||||
max:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Maximum value
|
||||
x-kubernetes-int-or-string: true
|
||||
min:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Minimum value
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
name:
|
||||
description: Name of the metric
|
||||
type: string
|
||||
templateRef:
|
||||
description: TemplateRef references a metric template object
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
lastBatchToRollout:
|
||||
description: All pods in the batches up to the batchPartition (included) will have the target resource specification while the rest still have the source resource This is designed for the operators to manually rollout Default is the the number of batches which will rollout all the batches
|
||||
format: int32
|
||||
type: integer
|
||||
numBatches:
|
||||
description: The number of batches, default = 1 mutually exclusive to RolloutBatches
|
||||
format: int32
|
||||
type: integer
|
||||
rolloutBatches:
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches
|
||||
items:
|
||||
description: RolloutBatch is used to describe how the each batch rollout should be
|
||||
properties:
|
||||
batchRolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the batch rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
metadata:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Metadata (key-value pairs) for this webhook
|
||||
type: object
|
||||
name:
|
||||
description: Name of this webhook
|
||||
type: string
|
||||
timeout:
|
||||
description: Request timeout for this webhook
|
||||
type: string
|
||||
type:
|
||||
description: Type of this webhook
|
||||
type: string
|
||||
url:
|
||||
description: URL address of this webhook
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- url
|
||||
type: object
|
||||
type: array
|
||||
canaryMetric:
|
||||
description: CanaryMetric provides a way for the batch rollout process to automatically check certain metrics before moving to the next batch
|
||||
items:
|
||||
description: CanaryMetric holds the reference to metrics used for canary analysis
|
||||
properties:
|
||||
interval:
|
||||
description: Interval represents the windows size
|
||||
type: string
|
||||
metricsRange:
|
||||
description: Range value accepted for this metric
|
||||
properties:
|
||||
max:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Maximum value
|
||||
x-kubernetes-int-or-string: true
|
||||
min:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Minimum value
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
name:
|
||||
description: Name of the metric
|
||||
type: string
|
||||
templateRef:
|
||||
description: TemplateRef references a metric template object
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
instanceInterval:
|
||||
description: The wait time, in seconds, between instances upgrades, default = 0
|
||||
format: int32
|
||||
type: integer
|
||||
maxUnavailable:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: MaxUnavailable is the max allowed number of pods that is unavailable during the upgrade. We will mark the batch as ready as long as there are less or equal number of pods unavailable than this number. default = 0
|
||||
x-kubernetes-int-or-string: true
|
||||
podList:
|
||||
description: The list of Pods to get upgraded it is mutually exclusive with the Replica field
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
replica:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replica is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods it is mutually exclusive with the PodList field'
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
type: array
|
||||
rolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
metadata:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Metadata (key-value pairs) for this webhook
|
||||
type: object
|
||||
name:
|
||||
description: Name of this webhook
|
||||
type: string
|
||||
timeout:
|
||||
description: Request timeout for this webhook
|
||||
type: string
|
||||
type:
|
||||
description: Type of this webhook
|
||||
type: string
|
||||
url:
|
||||
description: URL address of this webhook
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- url
|
||||
type: object
|
||||
type: array
|
||||
stopped:
|
||||
description: Stopped the rollout, default is false
|
||||
type: boolean
|
||||
targetSize:
|
||||
description: The size of the target resource. The default is the same as the size of the source resource.
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
sourceRef:
|
||||
description: SourceRef references the source resource that contains the older version of the software. We assume that it's the first time to deploy when we cannot find the source.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
targetRef:
|
||||
description: TargetRef references a target resource that contains the newer version of the software. We assumed that new resource already exists. This is the only resource we work on if the resource is a stateful resource (cloneset/statefulset)
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- rolloutPlan
|
||||
- targetRef
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
type: string
|
||||
conditions:
|
||||
description: Conditions represents the latest available observations of a CloneSet's current state.
|
||||
items:
|
||||
description: RolloutCondition is the condition of the rollout
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
type: string
|
||||
lastTransitionTime:
|
||||
description: Last time the condition transitioned to this state
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details about the transition.
|
||||
type: string
|
||||
reason:
|
||||
description: The reason for the condition's last transition.
|
||||
type: string
|
||||
rollingState:
|
||||
description: RollingState is the Rollout Status
|
||||
type: string
|
||||
required:
|
||||
- rollingState
|
||||
type: object
|
||||
type: array
|
||||
currentBatch:
|
||||
description: The current batch the rollout is working on/blocked
|
||||
format: int32
|
||||
type: integer
|
||||
rollingState:
|
||||
description: RollingState is the Rollout State
|
||||
type: string
|
||||
sourceGeneration:
|
||||
description: The source resource generation
|
||||
type: string
|
||||
targetGeneration:
|
||||
description: The target resource generation
|
||||
type: string
|
||||
upgradedReplicas:
|
||||
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- currentBatch
|
||||
- rollingState
|
||||
- sourceGeneration
|
||||
- targetGeneration
|
||||
- upgradedReplicas
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -98,7 +98,7 @@ spec:
|
||||
description: Paused the rollout, default is false
|
||||
type: boolean
|
||||
rolloutBatches:
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches. The total number cannot exceed the targetSize or the size of the source resource We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum We highly recommend to leave the last batch's replica field empty
|
||||
items:
|
||||
description: RolloutBatch is used to describe how the each batch rollout should be
|
||||
properties:
|
||||
@@ -200,7 +200,7 @@ spec:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods it is mutually exclusive with the PodList field'
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods we will ignore the percentage of the last batch to just fill the gap it is mutually exclusive with the PodList field'
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
type: array
|
||||
@@ -208,7 +208,7 @@ spec:
|
||||
description: RolloutStrategy defines strategies for the rollout plan
|
||||
type: string
|
||||
rolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
description: RolloutWebhooks provide a way for the rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
@@ -288,7 +288,7 @@ spec:
|
||||
- targetRef
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
description: RolloutStatus defines the observed state of a rollout plan
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
@@ -322,17 +322,17 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
currentBatch:
|
||||
description: The current batch the rollout is working on/blocked
|
||||
description: The current batch the rollout is working on/blocked it starts from 0
|
||||
format: int32
|
||||
type: integer
|
||||
lastAppliedPodTemplateIdentifier:
|
||||
description: lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template each workload type could use different ways to identify that so we cannot compare between resources We update this field only after a successful rollout
|
||||
type: string
|
||||
rollingState:
|
||||
description: RollingState is the Rollout State
|
||||
type: string
|
||||
sourceGeneration:
|
||||
description: The source resource generation
|
||||
type: string
|
||||
targetGeneration:
|
||||
description: The target resource generation
|
||||
description: NewPodTemplateIdentifier is a string that uniquely represent the new pod template each workload type could use different ways to identify that so we cannot compare between resources
|
||||
type: string
|
||||
upgradedReadyReplicas:
|
||||
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
|
||||
@@ -345,8 +345,6 @@ spec:
|
||||
required:
|
||||
- currentBatch
|
||||
- rollingState
|
||||
- sourceGeneration
|
||||
- targetGeneration
|
||||
- upgradedReadyReplicas
|
||||
- upgradedReplicas
|
||||
type: object
|
||||
|
||||
@@ -37,6 +37,9 @@ spec:
|
||||
host:
|
||||
description: Host is the host of the route
|
||||
type: string
|
||||
ingressClass:
|
||||
description: IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
type: string
|
||||
provider:
|
||||
description: Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
type: string
|
||||
|
||||
@@ -7,54 +7,63 @@ metadata:
|
||||
Please use route trait in cap center for advanced usage."
|
||||
name: ingress
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
if len(context.outputs.ingress.status.loadBalancer.ingress) > 0 {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + context.outputs.ingress.status.loadBalancer.ingress[0].ip
|
||||
}
|
||||
if len(context.outputs.ingress.status.loadBalancer.ingress) == 0 {
|
||||
message: "No loadBalancer found, visiting by using 'vela port-forward " + context.appName + " --route'\n"
|
||||
}
|
||||
healthPolicy: |
|
||||
isHealth: len(context.outputs.service.spec.clusterIP) > 0
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worker
|
||||
extension:
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
selector:
|
||||
"app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
selector:
|
||||
"app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,18 +12,17 @@ spec:
|
||||
definitionRef:
|
||||
name: manualscalertraits.core.oam.dev
|
||||
workloadRefPath: spec.workloadRef
|
||||
extension:
|
||||
template: |-
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
//+usage=Replicas of the workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
//+usage=Replicas of the workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
|
||||
|
||||
@@ -8,40 +8,39 @@ metadata:
|
||||
spec:
|
||||
definitionRef:
|
||||
name: jobs.batch
|
||||
extension:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
template: spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
spec: {
|
||||
parallelism: parameter.count
|
||||
completions: parameter.count
|
||||
template: spec: {
|
||||
restartPolicy: parameter.restart
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=specify number of tasks to run in parallel
|
||||
// +short=c
|
||||
count: *1 | int
|
||||
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
|
||||
restart: *"Never" | string
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
|
||||
|
||||
@@ -9,84 +9,83 @@ metadata:
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
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
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
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
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -8,40 +8,39 @@ metadata:
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
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
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
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
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ spec:
|
||||
- "--webhook-cert-dir={{ .Values.certificate.mountPath }}"
|
||||
{{ end }}
|
||||
- "--health-addr=:{{ .Values.healthCheck.port }}"
|
||||
- "--apply-once-only={{ .Values.applyOnceOnly }}"
|
||||
{{ if ne .Values.disableCaps "" }}
|
||||
- "--disable-caps={{ .Values.disableCaps }}"
|
||||
{{ end }}
|
||||
|
||||
@@ -142,6 +142,28 @@ webhooks:
|
||||
admissionReviewVersions:
|
||||
- v1beta1
|
||||
timeoutSeconds: 5
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ template "kubevela.name" . }}-webhook
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-core-oam-dev-v1alpha2-traitdefinitions
|
||||
failurePolicy: Fail
|
||||
name: validating.core.oam.dev.v1alpha2.traitdefinitions
|
||||
rules:
|
||||
- apiGroups:
|
||||
- core.oam.dev
|
||||
apiVersions:
|
||||
- v1alpha2
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- traitdefinitions
|
||||
scope: Cluster
|
||||
admissionReviewVersions:
|
||||
- v1beta1
|
||||
timeoutSeconds: 5
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
replicaCount: 1
|
||||
installCertManager: false
|
||||
# Valid applyOnceOnly values: true/false/on/off/force
|
||||
applyOnceOnly: "off"
|
||||
useWebhook: true
|
||||
# By default, don't disable any builtin capabilities
|
||||
disableCaps: ""
|
||||
|
||||
+23
-5
@@ -9,6 +9,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
injectorcontroller "github.com/oam-dev/trait-injector/controllers"
|
||||
"github.com/oam-dev/trait-injector/pkg/injector"
|
||||
"github.com/oam-dev/trait-injector/pkg/plugin"
|
||||
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
kedav1alpha1 "github.com/wonderflow/keda-api/api/v1alpha1"
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -62,6 +64,7 @@ func init() {
|
||||
_ = injectorv1alpha1.AddToScheme(scheme)
|
||||
_ = certmanager.AddToScheme(scheme)
|
||||
_ = kedav1alpha1.AddToScheme(scheme)
|
||||
_ = kruise.AddToScheme(scheme)
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
@@ -77,6 +80,7 @@ func main() {
|
||||
var disableCaps string
|
||||
var storageDriver string
|
||||
var syncPeriod time.Duration
|
||||
var applyOnceOnly string
|
||||
|
||||
flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook")
|
||||
flag.BoolVar(&useTraitInjector, "use-trait-injector", false, "Enable TraitInjector")
|
||||
@@ -93,8 +97,8 @@ func main() {
|
||||
flag.IntVar(&controllerArgs.RevisionLimit, "revision-limit", 50,
|
||||
"RevisionLimit is the maximum number of revisions that will be maintained. The default value is 50.")
|
||||
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.")
|
||||
flag.BoolVar(&controllerArgs.ApplyOnceOnly, "apply-once-only", false,
|
||||
"For the purpose of some production environment that workload or trait should not be affected if no spec change")
|
||||
flag.StringVar(&applyOnceOnly, "apply-once-only", "false",
|
||||
"For the purpose of some production environment that workload or trait should not be affected if no spec change, available options: on, off, force.")
|
||||
flag.StringVar(&controllerArgs.CustomRevisionHookURL, "custom-revision-hook-url", "",
|
||||
"custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision")
|
||||
flag.StringVar(&disableCaps, "disable-caps", "", "To be disabled builtin capability list.")
|
||||
@@ -162,6 +166,23 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(applyOnceOnly) {
|
||||
case "", "false", string(oamcontroller.ApplyOnceOnlyOff):
|
||||
controllerArgs.ApplyMode = oamcontroller.ApplyOnceOnlyOff
|
||||
setupLog.Info("ApplyOnceOnly is disabled")
|
||||
case "true", string(oamcontroller.ApplyOnceOnlyOn):
|
||||
controllerArgs.ApplyMode = oamcontroller.ApplyOnceOnlyOn
|
||||
setupLog.Info("ApplyOnceOnly is enabled, that means workload or trait only apply once if no spec change even they are changed by others")
|
||||
case string(oamcontroller.ApplyOnceOnlyForce):
|
||||
controllerArgs.ApplyMode = oamcontroller.ApplyOnceOnlyForce
|
||||
setupLog.Info("ApplyOnceOnlyForce is enabled, that means workload or trait only apply once if no spec change even they are changed or deleted by others")
|
||||
default:
|
||||
setupLog.Error(fmt.Errorf("invalid apply-once-only value: %s", applyOnceOnly),
|
||||
"unable to setup the vela core controller",
|
||||
"valid apply-once-only value:", "on/off/force, by default it's off")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = oamv1alpha2.Setup(mgr, controllerArgs, logging.NewLogrLogger(setupLog)); err != nil {
|
||||
setupLog.Error(err, "unable to setup the oam core controller")
|
||||
os.Exit(1)
|
||||
@@ -201,9 +222,6 @@ func main() {
|
||||
|
||||
setupLog.Info("starting the vela controller manager")
|
||||
|
||||
if controllerArgs.ApplyOnceOnly {
|
||||
setupLog.Info("applyOnceOnly is enabled that means workload or trait only apply once if no spec change even they are changed by others")
|
||||
}
|
||||
if err := mgr.Start(makeSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
spec:
|
||||
components:
|
||||
- name: myweb
|
||||
type: worker
|
||||
settings:
|
||||
image: "busybox"
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
lives: "3"
|
||||
enemies: "alien"
|
||||
traits:
|
||||
- name: ingress
|
||||
properties:
|
||||
domain: "www.example.com"
|
||||
http:
|
||||
"/": 80
|
||||
@@ -0,0 +1,119 @@
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: nworker
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
status:
|
||||
healthPolicy: |
|
||||
isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)
|
||||
customStatus: |-
|
||||
message: "type: " + context.output.spec.template.spec.containers[0].image + ",\t enemies:" + context.outputs.gameconfig.data.enemies
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
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
|
||||
envFrom: [{
|
||||
configMapRef: name: context.name + "game-config"
|
||||
}]
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputs: gameconfig: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name + "game-config"
|
||||
}
|
||||
data: {
|
||||
enemies: parameter.enemies
|
||||
lives: parameter.lives
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
lives: string
|
||||
enemies: string
|
||||
}
|
||||
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
name: ingress
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
message: "type: "+ context.outputs.service.spec.type +",\t clusterIP:"+ context.outputs.service.spec.clusterIP+",\t ports:"+ "\(context.outputs.service.spec.ports[0].port)"+",\t domain"+context.outputs.ingress.spec.rules[0].host
|
||||
healthPolicy: |
|
||||
isHealth: len(context.outputs.service.spec.clusterIP) > 0
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector:
|
||||
app: context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
+39
-47
@@ -7,37 +7,55 @@ In the root folder of this project, run `make start-dashboard` to start backend
|
||||
```shell
|
||||
➜ xxx/src/github.com/oam-dev/kubevela $ make start-dashboard
|
||||
go run pkg/server/main/startAPIServer.go &
|
||||
cd dashboard && yarn && yarn start && cd ..
|
||||
yarn install v1.22.4
|
||||
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
|
||||
[1/5] 🔍 Validating package.json...
|
||||
[2/5] 🔍 Resolving packages...
|
||||
success Already up-to-date.
|
||||
$ umi g tmp
|
||||
✨ Done in 5.89s.
|
||||
yarn run v1.22.4
|
||||
$ umi dev
|
||||
Starting the development server...
|
||||
I1230 10:37:54.157092 14236 request.go:621] Throttling request took 1.04915427s, request: GET:https://47.242.145.141:6443/apis/split.smi-spec.io/v1alpha2?timeout=32s
|
||||
cd dashboard && npm install && npm start && cd ..
|
||||
I0205 11:25:55.742786 5535 request.go:621] Throttling request took 1.002149891s, request: GET:https://47.242.145.141:6443/apis/coordination.k8s.io/v1beta1?timeout=32s
|
||||
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||||
- using env: export GIN_MODE=release
|
||||
- using code: gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
[GIN-debug] POST /api/envs/ --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).CreateEnv-fm (6 handlers)
|
||||
[GIN-debug] PUT /api/envs/:envName --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).UpdateEnv-fm (6 handlers)
|
||||
...
|
||||
[GIN-debug] GET /api/version --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).GetVersion-fm (6 handlers)
|
||||
[GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (7 handlers)
|
||||
[GIN-debug] GET /api/envs/:envName --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).GetEnv-fm (6 handlers)
|
||||
[GIN-debug] GET /api/envs/ --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).ListEnv-fm (6 handlers)
|
||||
|
||||
> fsevents@1.2.13 install /Users/zhouzhengxi/Programming/golang/src/github.com/oam-dev/kubevela/dashboard/node_modules/watchpack-chokidar2/node_modules/fsevents
|
||||
> node install.js
|
||||
|
||||
SOLINK_MODULE(target) Release/.node
|
||||
CXX(target) Release/obj.target/fse/fsevents.o
|
||||
SOLINK_MODULE(target) Release/fse.node
|
||||
|
||||
> ejs@2.7.4 postinstall /Users/zhouzhengxi/Programming/golang/src/github.com/oam-dev/kubevela/dashboard/node_modules/umi-webpack-bundle-analyzer/node_modules/ejs
|
||||
> node ./postinstall.js
|
||||
|
||||
Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)
|
||||
|
||||
|
||||
> kubevela@0.0.1 postinstall /Users/zhouzhengxi/Programming/golang/src/github.com/oam-dev/kubevela/dashboard
|
||||
> umi g tmp
|
||||
|
||||
added 1234 packages from 743 contributors, removed 49 packages, updated 85 packages and audited 3208 packages in 41.551s
|
||||
|
||||
235 packages are looking for funding
|
||||
run `npm fund` for details
|
||||
|
||||
found 19 vulnerabilities (18 low, 1 high)
|
||||
run `npm audit fix` to fix them, or `npm audit` for details
|
||||
|
||||
> kubevela@0.0.1 start /Users/zhouzhengxi/Programming/golang/src/github.com/oam-dev/kubevela/dashboard
|
||||
> umi dev
|
||||
|
||||
Starting the development server...
|
||||
|
||||
✔ Webpack
|
||||
Compiled successfully in 26.86s
|
||||
Compiled successfully in 34.81s
|
||||
|
||||
DONE Compiled successfully in 26865ms 10:38:22 AM
|
||||
DONE Compiled successfully in 34815ms 11:27:12 AM
|
||||
|
||||
|
||||
App running at:
|
||||
- Local: http://localhost:8000 (copied to clipboard)
|
||||
- Network: http://192.168.31.114:8000
|
||||
- Local: http://localhost:8002 (copied to clipboard)
|
||||
- Network: http://30.240.99.101:8002
|
||||
```
|
||||
|
||||
## Development
|
||||
@@ -45,37 +63,11 @@ I1230 10:37:54.157092 14236 request.go:621] Throttling request took 1.04915427
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
npm install
|
||||
```
|
||||
|
||||
### Start up
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
### Lint and Test
|
||||
|
||||
- Check code style
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
You can also use script to auto fix some lint error:
|
||||
|
||||
```bash
|
||||
yarn prettier
|
||||
```
|
||||
|
||||
- Test code
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
npm start
|
||||
```
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
path: `/applications`,
|
||||
component: './Application',
|
||||
},
|
||||
/* Application Create should be moved to /Application */
|
||||
{
|
||||
name: 'create_application',
|
||||
path: '/applications/create',
|
||||
component: './CreateApplication'
|
||||
},
|
||||
{
|
||||
name: 'capability',
|
||||
icon: 'AppstoreAddOutlined',
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"@umijs/route-utils": "^1.0.33",
|
||||
"antd": "^4.9.4",
|
||||
"classnames": "^2.2.6",
|
||||
"form-render": "^0.9.0",
|
||||
"dayjs": "^1.9.7",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.25.3",
|
||||
@@ -63,6 +64,7 @@
|
||||
"react-dev-inspector": "^1.1.1",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-helmet-async": "^1.0.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"umi": "^3.2.14",
|
||||
"umi-request": "^1.0.8",
|
||||
"use-merge-value": "^1.0.1"
|
||||
@@ -100,7 +102,8 @@
|
||||
"pro-download": "1.0.1",
|
||||
"puppeteer-core": "^5.0.0",
|
||||
"stylelint": "^13.0.0",
|
||||
"typescript": "^4.1.2"
|
||||
"typescript": "^4.1.2",
|
||||
"webpack-plugin-fr-theme": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import * as api from '@/services/traits';
|
||||
import * as api from '@/services/capability';
|
||||
|
||||
interface State {
|
||||
loading?: boolean;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import * as api from '@/services/workloads';
|
||||
import * as api from '@/services/capability';
|
||||
|
||||
interface State {
|
||||
loading?: boolean;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Link, useModel, useRequest } from 'umi';
|
||||
import { deleteApplication, getApplications } from '@/services/application';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
// @ts-ignore
|
||||
import { Link as ReactLink } from 'react-router-dom';
|
||||
|
||||
export default () => {
|
||||
const { currentEnvironment } = useModel('useEnvironmentModel');
|
||||
@@ -42,7 +44,7 @@ export default () => {
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<Space>
|
||||
<Button type="primary" icon={<PlusOutlined />}>
|
||||
Create
|
||||
<ReactLink to="/applications/create"> Create</ReactLink>
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Input, Dropdown, Menu, Button, Divider, Row, Col } from 'antd';
|
||||
import { useModel } from '@@/plugin-model/useModel';
|
||||
import { DownOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import FormRender from 'form-render/lib/antd';
|
||||
import { getCapabilityOpenAPISchema } from '@/services/capability';
|
||||
// prevent Ant design style from being overridden
|
||||
import 'antd/dist/antd.css';
|
||||
|
||||
export default (): React.ReactNode => {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { workloadsLoading, workloadList } = useModel('useWorkloadsModel');
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { traitsLoading, traitsList } = useModel('useTraitsModel');
|
||||
|
||||
const workloadMenuList = workloadList?.map((i) => (
|
||||
<Menu.Item
|
||||
key={i.name}
|
||||
icon={<UserOutlined />}
|
||||
onClick={() => handleMenuClick('workload_type', i.name)}
|
||||
>
|
||||
{i.name}
|
||||
</Menu.Item>
|
||||
));
|
||||
|
||||
const traitMenuList = traitsList?.map((i) => (
|
||||
<Menu.Item
|
||||
key={i.name}
|
||||
icon={<UserOutlined />}
|
||||
onClick={() => handleMenuClick('trait', i.name)}
|
||||
>
|
||||
{i.name}
|
||||
</Menu.Item>
|
||||
));
|
||||
|
||||
const workloadsMenu = <Menu>{workloadMenuList}</Menu>;
|
||||
|
||||
const traitsMenu = <Menu>{traitMenuList}</Menu>;
|
||||
|
||||
// Capability parameters form render
|
||||
const [formData, setData] = useState({});
|
||||
// schema is OpenAPI Schema JSON data
|
||||
const [workloadSchema, setWorkloadSchema] = useState({});
|
||||
const [traitSchema, setTraitSchema] = useState({});
|
||||
const [valid, setValid] = useState([]);
|
||||
|
||||
function handleMenuClick(capabilityType: string, capabilityName: string) {
|
||||
console.log('click', capabilityName);
|
||||
getCapabilityOpenAPISchema(capabilityName).then((result) => {
|
||||
const data = JSON.parse(result.data);
|
||||
if (capabilityType === 'workload_type') {
|
||||
setWorkloadSchema(data);
|
||||
} else if (capabilityType === 'trait') {
|
||||
setTraitSchema(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
// valid == 0: validation passed
|
||||
if (valid.length > 0) {
|
||||
alert(`invalid:${valid.toString()}`);
|
||||
} else {
|
||||
alert(JSON.stringify(formData, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 600 }}>
|
||||
<Row>
|
||||
<Col span="4">Application</Col>
|
||||
<Col span="20" />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Name:</Col>
|
||||
<Col span="8">
|
||||
<Input placeholder="Basic usage" />
|
||||
</Col>
|
||||
<Col span="12" />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="24">
|
||||
<Divider />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Services</Col>
|
||||
<Col span="20" />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Name:</Col>
|
||||
<Col span="8">
|
||||
<Input placeholder="Basic usage" />
|
||||
</Col>
|
||||
<Col span="12" />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Type:</Col>
|
||||
<Col span="20">
|
||||
<Dropdown overlay={workloadsMenu}>
|
||||
<a className="ant-dropdown-link">
|
||||
Select <DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Settings:</Col>
|
||||
<Col span="20">
|
||||
<FormRender
|
||||
schema={workloadSchema}
|
||||
formData={formData}
|
||||
onChange={setData}
|
||||
onValidate={setValid}
|
||||
displayType="column"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="24">
|
||||
<Divider />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Traits</Col>
|
||||
<Col span="20" />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Type:</Col>
|
||||
<Col span="20">
|
||||
<Dropdown overlay={traitsMenu}>
|
||||
<a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
|
||||
Select <DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="4">Properties:</Col>
|
||||
<Col span="20">
|
||||
<FormRender
|
||||
schema={traitSchema}
|
||||
formData={formData}
|
||||
onChange={setData}
|
||||
onValidate={setValid}
|
||||
displayType="column"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span="8" />
|
||||
<Col span="8">
|
||||
<Button onClick={onSubmit} type="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span="8" />
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { request } from 'umi';
|
||||
|
||||
/*
|
||||
* workload type list: get /api/workloads/
|
||||
*/
|
||||
export async function getWorkloads(): Promise<API.VelaResponse<API.Workloads[]>> {
|
||||
return request('/api/workloads');
|
||||
}
|
||||
|
||||
/*
|
||||
* trait list: get /api/traits/
|
||||
*/
|
||||
export async function getTraits(): Promise<API.VelaResponse<API.Traits[]>> {
|
||||
return request('/api/traits');
|
||||
}
|
||||
|
||||
export async function getCapabilityOpenAPISchema(
|
||||
capabilityName: string,
|
||||
): Promise<API.VelaResponse<string>> {
|
||||
return request(`/api/definitions/${capabilityName}`, { method: 'get' });
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { request } from 'umi';
|
||||
|
||||
const BASE_PATH = '/api/traits';
|
||||
|
||||
/*
|
||||
* trait 列表: get /api/traits/
|
||||
*/
|
||||
export async function getTraits(): Promise<API.VelaResponse<API.Traits[]>> {
|
||||
return request(BASE_PATH);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { request } from 'umi';
|
||||
|
||||
const BASE_PATH = '/api/workloads';
|
||||
|
||||
/*
|
||||
* workload 列表: get /api/workloads/
|
||||
*/
|
||||
export async function getWorkloads(): Promise<API.VelaResponse<API.Workloads[]>> {
|
||||
return request(BASE_PATH);
|
||||
}
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
In KubeVela, APIServer provides the RESTful API for external systems (e.g. UI) to manage Vela abstractions like Applications, Definitions; Catalog stores templates to install common-off-the-shell (COTS) capabilities on Kubernetes.
|
||||
|
||||
This doc provides a top-down architecture design for Vela APIServer and Catalog. It clarifies the API interfaces for platform builders to build integration solutions, and describes the architecture design in details for incoming roadmap. Some of the interfaces might have not been implemented yet, but we will follow this design in the future project roadmap.
|
||||
This doc provides a top-down architecture design for Vela APIServer and Catalog. It clarifies the API interfaces for platform builders to build integration solutions and describes the architecture design in details for the incoming roadmap. Some of the interfaces might have not been implemented yet, but we will follow this design in the future project roadmap.
|
||||
|
||||
## Motivation
|
||||
|
||||
This design is based on and tries to resolve the following use cases:
|
||||
|
||||
1. UI component wants to discover APIs to integrate with Vela APIServer.
|
||||
1. Users want to manage multiple clusters, catalogs, configuration environments in a single place.
|
||||
1. Users want to manage multiple clusters, catalogues, configuration environments in a single place.
|
||||
1. The management data can be stored in a cloud database like MySQL instead of k8s control plane.
|
||||
1. Because there aren't control logic for those data. This is unlike other Vela resources stored as CR in K8s control plane.
|
||||
1. It is more expensive to host a k8s control plane than MySQL database on cloud.
|
||||
@@ -250,7 +250,7 @@ The structure of one package version contains:
|
||||
|
||||
- `definitions`: definition files that describe the capabilities from this package to enable on a cluster. Note that these definitions will compared against a cluster on APIServer side to see if a cluster can install or upgrade this package.
|
||||
|
||||
- `conditions/`: definingg conditional checks before deploying this package. For example, check if a CRD with specific version exist, if not then the deployment should fail.
|
||||
- `conditions/`: defining conditional checks before deploying this package. For example, check if a CRD with specific version exist, if not then the deployment should fail.
|
||||
|
||||
```yaml
|
||||
# check-crd.yaml
|
||||
@@ -301,7 +301,7 @@ The structure of one package version contains:
|
||||
|
||||
Please refer to `/catalogs/<catalog>` API endpoint above.
|
||||
|
||||
Under the hood, APIServer will scan the catalog repo based on the predefined structure to parse each packages and versions.
|
||||
Under the hood, APIServer will scan the catalog repo based on the predefined structure to parse each packag and versions.
|
||||
|
||||
#### Sync a catalog in APIServer
|
||||
|
||||
@@ -315,7 +315,7 @@ Vela APIServer aggregates package information from multiple catalog servers. To
|
||||
|
||||

|
||||
|
||||
In our future roadmap, we will build a catalog controller for each k8s cluster. Then we will add API endpoint to install the package in APIServer which basically creates a CR to trigger the controller to reconcile package installation into the cluster. We choose this instead of APIServer installing the package because in this way we can bypass the APIServer in the package data transfer path and avoid APIServer becoming single point of failure.
|
||||
In our future roadmap, we will build a catalog controller for each k8s cluster. Then we will add API endpoint to install the package in APIServer which basically creates a CR to trigger the controller to reconcile package installation into the cluster. We choose this instead of APIServer installing the package because in this way we can bypass the APIServer in the package data transfer path and avoid APIServer becoming a single point of failure.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -323,7 +323,7 @@ In our future roadmap, we will build a catalog controller for each k8s cluster.
|
||||
|
||||
### Package parameters
|
||||
|
||||
We can parse the schema of parameters from Helm Chart or Terraform. For example, Helm supports [value schema file](https://www.arthurkoziel.com/validate-helm-chart-values-with-json-schemas/) for input validation and there is an [automation tool](https://github.com/karuppiah7890/helm-schema-gen] to generate the schema.
|
||||
We can parse the schema of parameters from Helm Chart or Terraform. For example, Helm supports [value schema file](https://www.arthurkoziel.com/validate-helm-chart-values-with-json-schemas/) for input validation and there is an [automation tool](https://github.com/karuppiah7890/helm-schema-gen) to generate the schema.
|
||||
|
||||
### Package dependency
|
||||
|
||||
@@ -331,8 +331,8 @@ Instead of having multiple definitions in one package, we could define that one
|
||||
|
||||
To provide a bundle of definitions, we could define package dependency. So a parent package could depend on multiple atomic packages to provide a full-fledged capability.
|
||||
|
||||
Package dependency solution will simplify the structure and provide more atomic packages. But this is not a simple problem and beyond the current scope. We will add this on future roadmap.
|
||||
Package dependency solution will simplify the structure and provide more atomic packages. But this is not a simple problem and beyond the current scope. We will add this on the future roadmap.
|
||||
|
||||
### Multi-tenancy
|
||||
|
||||
For initial version we plan to implement APIServer without multi-tenancy. But as an applicatio platform we expect multi-tenancy is a necessary part of Vela. We will keep API compatibility and might add some sort of auth token (e.g. JWT) as a query parameter in the future.
|
||||
For initial version we plan to implement APIServer without multi-tenancy. But as an application platform we expect multi-tenancy is a necessary part of Vela. We will keep API compatibility and might add some sort of auth token (e.g. JWT) as a query parameter in the future.
|
||||
|
||||
@@ -127,3 +127,28 @@ Since discrepancy is found, vela-core controller will apply(update) the Deployme
|
||||
Thus, the changes we made to the Deployment before will also be eliminated.
|
||||
|
||||
The same mechanism also works for Trait as well as Workload.
|
||||
|
||||
### Apply Once Only Force
|
||||
|
||||
Based on the same mechanism as `apply-once-only`, `apply-once-only-force` allows to skip re-creating a workload or trait that has already been DELETED from the cluster if its spec is not changed.
|
||||
It's regarded as a stronger case of `apply-once-only`.
|
||||
|
||||
## Usage
|
||||
|
||||
Three available options are provided to a vela-core runtime setup flag named `apply-one-only`, referring to three modes:
|
||||
|
||||
- off - `apply-once-only` is disabeld, this is the default option
|
||||
- on - `apply-once-only` is enabled
|
||||
- force - `apply-once-only-force` is enabled
|
||||
|
||||
You can set it through `helm` chart value `applyOnceOnly` which is "off" by default if omitted, for example
|
||||
|
||||
```shell
|
||||
helm install -n vela-system kubevela ./charts/vela-core --set applyOnceOnly=on
|
||||
```
|
||||
or
|
||||
```
|
||||
helm install -n vela-system kubevela ./charts/vela-core --set applyOnceOnly=force
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ type RouteSpec struct {
|
||||
|
||||
// Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
Provider string `json:"provider,omitempty"`
|
||||
|
||||
// IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
IngressClass string `json:"ingressClass,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines to route rule
|
||||
@@ -92,7 +95,8 @@ Besides `workloadRef`, one Route will have only one `host` and many rules. `host
|
||||
It's required and will be used to generate mTLS secrets.
|
||||
|
||||
Route Trait designed to be compatible with different ingress controller implementations, the `provider` field will allow
|
||||
you to give a specified ingress controller type. Currently, only nginx-ingress is supported.
|
||||
you to give a specified ingress controller type. The `ingressClass` field will allow you to set the ingressClass.
|
||||
Currently, only nginx-ingress is supported.
|
||||
|
||||
The `tls` field allow you to specify a TLS for this route with an IssuerName, the IssuerName pointing to an [Issuer Object](https://cert-manager.io/docs/concepts/issuer/)
|
||||
created by cert-manager. Cert-manager and ingress controller will handle certificate creation and binding.
|
||||
@@ -148,4 +152,3 @@ route trait will check `WorkloadDefinition` for podSpec field, with the `podSpec
|
||||
- 2.2 Use ChildResource: If No `PodSpecable` mechanism found in workload, we will continue discovery child resources of workload. If there
|
||||
is a valid `PodTemplate` structure in child resource, we will regard it as discovery target, use the same strategy like
|
||||
`workload.oam.dev/podspecable: true` but no `podSpecPath`.
|
||||
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
- [Concepts and Glossaries](/en/concepts.md)
|
||||
|
||||
- Platform Team Guide
|
||||
- [Overview](/en/platform-engineers/overview.md)
|
||||
<!-- - [Define Environments](TBD) -->
|
||||
- [What is KubeVela?](/en/platform-engineers/overview.md)
|
||||
- Register Capability Modules
|
||||
- [Workload Type](/en/platform-engineers/workload-type.md)
|
||||
- [Trait](/en/platform-engineers/trait.md)
|
||||
|
||||
@@ -29,7 +29,8 @@ Name | Description | Type | Required | Default
|
||||
domain | Domain name | string | true | empty
|
||||
issuer | | string | true | empty
|
||||
rules | | [[]rules](#rules) | false |
|
||||
provider | | string | false |
|
||||
provider | | string | false |
|
||||
ingressClass | | string | false |
|
||||
|
||||
|
||||
### rules
|
||||
|
||||
@@ -8,7 +8,7 @@ The trend of cloud-native technology is moving towards pursuing consistent appli
|
||||
|
||||
On the other hand, abstracting Kubernetes to serve developers' requirements is a highly opinionated process, and the resultant abstractions would only make sense had the decision makers been the platform builders. Unfortunately, the platform builders today face the following dilemma:
|
||||
|
||||
*There is no tool or framework for them to easily build user friendly yet highly extensible platforms*.
|
||||
*There is no tool or framework for them to easily build user friendly yet highly extensible abstractions*.
|
||||
|
||||
Thus, many platforms today are essentially restricted abstractions with in-house add-on mechanisms despite the extensibility of Kubernetes. This makes extending such platforms for developers' requirements or to wider scenarios almost impossible, not to mention taking the full advantage of the rich Kubernetes ecosystems.
|
||||
|
||||
|
||||
@@ -1,3 +1,78 @@
|
||||
# KubeVela for Platform Builders
|
||||
# What is KubeVela?
|
||||
|
||||
TBD: this documentation is still work in progress.
|
||||
This documentation explains "what KubeVela can do for you" in perspective of platform team.
|
||||
|
||||
## Overview
|
||||
|
||||
KubeVela provides several independent building blocks to help you create application platforms easily.
|
||||
|
||||

|
||||
|
||||
### 1. Application Encapsulation
|
||||
|
||||
The encapsulation engine enables you to define an `Application` abstraction that encapsulates all the needed resources composed your app.
|
||||
|
||||
One typical use case is we want to encapsulate a Kubernetes `Deployment` and a `Service` into a module probably named *Web Service*, and let end users to instantiate this module by simply filling in the parameters (e.g. `image`, `replicas` and `ports`). For example, the [`web-service.ts` ](https://github.com/awslabs/cdk8s/blob/master/examples/typescript/web-service/web-service.ts) lib in cdk8s, the [`kube.cue`](https://github.com/cuelang/cue/blob/b8b489251a3f9ea318830788794c1b4a753031c0/doc/tutorial/kubernetes/quick/services/kube.cue#L70) lib in CUE, and this widely used [Deployment + Service](https://docs.bitnami.com/tutorials/create-your-first-helm-chart/) Helm chart. Of course, some teams with great frontend engineers will choose to build a GUI console to create such abstraction.
|
||||
|
||||
The `Application` abstraction supports all the scenarios above. From end user's view, an `Application` is assembled by components (workload specifications) and traits (operational behaviors), for example:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
spec:
|
||||
components:
|
||||
- name: foo
|
||||
type: worker # component type
|
||||
settings:
|
||||
image: "busybox"
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
traits:
|
||||
- name: scaler
|
||||
properties:
|
||||
replicas: 10
|
||||
- name: sidecar
|
||||
properties:
|
||||
name: "sidecar-test"
|
||||
image: "nginx"
|
||||
- name: bar
|
||||
type: aliyun-oss # component type
|
||||
bucket: "my-bucket"
|
||||
```
|
||||
|
||||
In detail, every `component` and `trait` in above abstraction is defined by platform team via `Definition` objects. For example, [`WorkloadDefinition`](https://github.com/oam-dev/kubevela/tree/master/config/samples/application#workload-definition) and [`TraitDefinition`](https://github.com/oam-dev/kubevela/tree/master/config/samples/application#scaler-trait-definition). As the end user, they only need to assemble these modules into an application. Also, if end user has any new requirements, the platform team could customize the template in definitions by any time.
|
||||
|
||||
Besides this extensibility, there are several other benefits that the encapsulation engine can bring to you.
|
||||
|
||||
#### Unified Abstraction
|
||||
|
||||
KubeVela intends to support any possible module types as possible, for example `CUE`, `Terraform`, `Helm`, etc or just a plain Kubernetes CRD. This enables platform team to create unified abstraction that can model and deploy any kind of resource with ease, including cloud services, as long as they could be encapsulated by a supported module type. In the `application-sample` above, it defines a OSS bucket on Alibaba Cloud as a component which is powered by a Terraform module behind the scenes.
|
||||
|
||||
#### No Configuration Drift
|
||||
|
||||
Many of the existing modules today are defined by client side Infrastructure-as-Code (IaC) tools and even Kubernetes tool like Helm sits at client side as well. So in the nutshell, KubeVela encapsulation engine can just be implemented at client side which would be easier to be adopted.
|
||||
|
||||
But client side abstractions, though light-weighted, always lead to an issue called infrastructure/configuration drift, i.e. the generated component instances are not in line with the expected configuration. This could be caused by incomplete coverage, less-than-perfect processes or emergency changes.
|
||||
|
||||
In KubeVela, the encapsulation engine is intended to be implemented in a [Kubernetes Control Loop](https://kubernetes.io/docs/concepts/architecture/controller/). This is the key for KubeVela to eliminate the issue of configuration drifting but still keeps the simplicity and software delivery velocity enabled by IaC (and Helm) modules.
|
||||
|
||||
#### No "Juggling" Approach to Manage Kubernetes Objects
|
||||
|
||||
A typical use case is, as the platform team, we want to leverage `Istio` as the Service Mesh layer to control the traffic to certain `Deployment` instances. But this could be really painful today because we have to enforce end users to define and manage a set of Kubernetes resources in a "juggling" approach. For example, in a simple canary rollout case, the end users have to carefully manage a primary `Deployment`, a primary `Service`, a `root Service`, a canary `Deployment`, a canary `Service`, and have to probably rename the `Deployment` instance after canary promotion (this is actually unacceptable in production because renaming will lead to the app restart). What's worse, we have to expect the users properly set the labels and selectors on those objects carefully because they are the key to ensure proper accessibility of every app instance and the only revision mechanism our Istio controller could count on.
|
||||
|
||||
The issue above could be even painful if the workload instance is not `Deployment`, but `StatefulSet` or custom workload type. For example, normally it doesn't make sense to replicate a `StatefulSet` instance during rollout, this means the users have to maintain the name, revision, label, selector, app instances in a totally different approach from `Deployment`.
|
||||
|
||||
#### Standard Contract Behind The Abstraction
|
||||
|
||||
The encapsulation engine in KubeVela is designed to relieve such burden of managing versionized Kubernetes resources manually. In nutshell, all the needed Kubernetes resources for an app are now encapsulated in a single abstraction, and KubeVela will maintain the instance name, revisions, labels and selector by the battle tested reconcile loop automation, not by human hand. At the meantime, the existence of definition objects allow the platform team to customize the details of all above metadata behind the abstraction, even control the behavior of how to do revision.
|
||||
|
||||
Thus, all those metadata now become a standard contract that any day 2 operation controller such as Istio or rollout can rely on. This is the key to ensure our platform could provide user friendly experience but keep "transparent" to the operational behaviors.
|
||||
|
||||
### 2. Progressive Rollout
|
||||
|
||||
The deployment engine is responsible for progressive rollout of your app following given rollout strategy (e.g. canary, blue-green, etc).
|
||||
|
||||
> More information about this section is still work in progress.
|
||||
|
||||
@@ -46,8 +46,7 @@ Services:
|
||||
Created at: ...
|
||||
Updated at: ...
|
||||
Traits:
|
||||
- ✅ ingress: domain=testsvc.example.com
|
||||
http=map[/:8000]
|
||||
- ✅ ingress: Visiting URL: testsvc.example.com, IP: <your IP address>
|
||||
```
|
||||
|
||||
**In [kind cluster setup](./install.md#kind)**, you can visit the service via localhost. In other setups, replace localhost with ingress address accordingly.
|
||||
|
||||
@@ -35,7 +35,8 @@ spec:
|
||||
rules: parameter.rules
|
||||
}
|
||||
|
||||
provider: *"nginx" | parameter.provider
|
||||
provider: *"nginx" | parameter.provider
|
||||
ingressClass: *"nginx" | parameter.ingressClass
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
@@ -47,6 +48,7 @@ spec:
|
||||
path: string
|
||||
rewriteTarget: *"" | string
|
||||
}]
|
||||
provider?: string
|
||||
provider?: string
|
||||
ingressClass?: string
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
@@ -17,6 +17,11 @@ var (
|
||||
URL: "https://github.com/oam-dev/kubevela/tree/master/pkg/plugins/testdata",
|
||||
}
|
||||
|
||||
websvcCapability = types.Capability{
|
||||
Name: "webservice.testapps",
|
||||
Type: types.TypeWorkload,
|
||||
}
|
||||
|
||||
scaleCapability = types.Capability{
|
||||
Name: "scaler",
|
||||
Type: types.TypeTrait,
|
||||
@@ -41,9 +46,8 @@ var _ = ginkgo.Describe("Capability", func() {
|
||||
cli := fmt.Sprintf("vela cap center config %s %s", capabilityCenterBasic.Name, capabilityCenterBasic.URL)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
expectedOutput1 := fmt.Sprintf("Successfully configured capability center: %s, start to sync from remote", capabilityCenterBasic.Name)
|
||||
expectedOutput1 := fmt.Sprintf("Successfully configured capability center %s and sync from remote", capabilityCenterBasic.Name)
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput1))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("sync finished"))
|
||||
})
|
||||
|
||||
ginkgo.It("list capability centers", func() {
|
||||
@@ -58,8 +62,18 @@ var _ = ginkgo.Describe("Capability", func() {
|
||||
})
|
||||
|
||||
ginkgo.Context("capability", func() {
|
||||
ginkgo.It("install a capability to cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap add %s/%s", capabilityCenterBasic.Name, scaleCapability.Name)
|
||||
ginkgo.It("install a workload capability to cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap install %s/%s", capabilityCenterBasic.Name, websvcCapability.Name)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
expectedSubStr1 := fmt.Sprintf("Installing %s capability", websvcCapability.Type)
|
||||
expectedSubStr2 := fmt.Sprintf("Successfully installed capability %s from %s", websvcCapability.Name, capabilityCenterBasic.Name)
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(expectedSubStr1))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(expectedSubStr2))
|
||||
})
|
||||
|
||||
ginkgo.It("install a trait capability to cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap install %s/%s", capabilityCenterBasic.Name, scaleCapability.Name)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
expectedSubStr1 := fmt.Sprintf("Installing %s capability", scaleCapability.Type)
|
||||
@@ -68,8 +82,8 @@ var _ = ginkgo.Describe("Capability", func() {
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(expectedSubStr2))
|
||||
})
|
||||
|
||||
ginkgo.It("install a trait without definition reference to cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap add %s/%s", capabilityCenterBasic.Name, ingressCapability.Name)
|
||||
ginkgo.It("install a trait capability without definition reference to cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap install %s/%s", capabilityCenterBasic.Name, ingressCapability.Name)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
expectedSubStr1 := fmt.Sprintf("Installing %s capability", ingressCapability.Type)
|
||||
@@ -84,11 +98,34 @@ var _ = ginkgo.Describe("Capability", func() {
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("NAME"))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("CENTER"))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(websvcCapability.Name))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(ingressCapability.Name))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(scaleCapability.Name))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(routeCapability.Name))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("installed"))
|
||||
})
|
||||
|
||||
ginkgo.It("uninstall a workload capability from cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap uninstall %s", websvcCapability.Name)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
expectedSubStr := fmt.Sprintf("Successfully uninstalled capability %s", websvcCapability.Name)
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(expectedSubStr))
|
||||
})
|
||||
|
||||
ginkgo.It("uninstall a trait capability from cluster", func() {
|
||||
cli := fmt.Sprintf("vela cap uninstall %s", ingressCapability.Name)
|
||||
output, err := e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
expectedSubStr := fmt.Sprintf("Successfully uninstalled capability %s", ingressCapability.Name)
|
||||
gomega.Expect(output).To(gomega.ContainSubstring(expectedSubStr))
|
||||
|
||||
// unstall other installed test capability
|
||||
cli = fmt.Sprintf("vela cap uninstall %s", scaleCapability.Name)
|
||||
_, err = e2e.Exec(cli)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
})
|
||||
|
||||
ginkgo.It("delete a capability center", func() {
|
||||
cli := fmt.Sprintf("vela cap center remove %s", capabilityCenterBasic.Name)
|
||||
output, err := e2e.Exec(cli)
|
||||
|
||||
@@ -37,6 +37,7 @@ require (
|
||||
github.com/olekukonko/tablewriter v0.0.2
|
||||
github.com/onsi/ginkgo v1.13.0
|
||||
github.com/onsi/gomega v1.10.3
|
||||
github.com/openkruise/kruise-api v0.7.0
|
||||
github.com/openservicemesh/osm v0.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
|
||||
@@ -79,7 +80,6 @@ require (
|
||||
|
||||
replace (
|
||||
github.com/Azure/go-autorest => github.com/Azure/go-autorest v12.2.0+incompatible // https://github.com/kubernetes/client-go/issues/628
|
||||
github.com/Sirupsen/logrus v1.7.0 => github.com/sirupsen/logrus v1.7.0
|
||||
// fix build issue https://github.com/docker/distribution/issues/2406
|
||||
github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
|
||||
github.com/docker/docker => github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
|
||||
|
||||
@@ -390,7 +390,6 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
@@ -1473,6 +1472,8 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700 h1:eNUVfm/RFLIi1G7flU5/ZRTHvd4kcVuzfRnL6OFlzCI=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/openkruise/kruise-api v0.7.0 h1:BBQotEfeZ2l1+R0uvlsVK2FN8C4RTlG+JT86ba2hOR4=
|
||||
github.com/openkruise/kruise-api v0.7.0/go.mod h1:nCf5vVOjQJX5OaV7Qi0Z51/Rn9cd7s5kVrg8YLgFp1I=
|
||||
github.com/openservicemesh/osm v0.3.0 h1:U88Nv1xm+7M+xYNkwjYVU6WSMp3MHIObTcO+gH20nOw=
|
||||
github.com/openservicemesh/osm v0.3.0/go.mod h1:gyK0vN5ENnP26Y8huqgeTR52fOotZhnEorie215FnpU=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
@@ -2561,6 +2562,7 @@ k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0H
|
||||
k8s.io/api v0.0.0-20190918195907-bd6ac527cfd2/go.mod h1:AOxZTnaXR/xiarlQL0JUfwQPxjmKDvVYoRp58cA7lUo=
|
||||
k8s.io/api v0.0.0-20191115095533-47f6de673b26/go.mod h1:iA/8arsvelvo4IDqIhX4IbjTEKBGgvsf2OraTuRtLFU=
|
||||
k8s.io/api v0.0.0-20191122220107-b5267f2975e0/go.mod h1:vYpRfxYkMrmPPSesoHEkGNHxNKTk96REAwqm/inQbs0=
|
||||
k8s.io/api v0.15.8/go.mod h1:hpDXsOhY/unVSSzhMol7kihWaNMf2snhF4nejjlzUzk=
|
||||
k8s.io/api v0.16.4/go.mod h1:AtzMnsR45tccQss5q8RnF+W8L81DH6XwXwo/joEx9u0=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
|
||||
@@ -2572,7 +2574,6 @@ k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
||||
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||
k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA=
|
||||
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
|
||||
k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
|
||||
k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
|
||||
k8s.io/api v0.18.7-rc.0/go.mod h1:v6x7KyKMJ7W/BbG7E9olOQshfszuXKKsxfnjaq+ylrk=
|
||||
k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4=
|
||||
@@ -2583,7 +2584,6 @@ k8s.io/apiextensions-apiserver v0.16.4/go.mod h1:HYQwjujEkXmQNhap2C9YDdIVOSskGZ3
|
||||
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
|
||||
k8s.io/apiextensions-apiserver v0.17.6/go.mod h1:Z3CHLP3Tha+Rbav7JR3S+ye427UaJkHBomK2c4XtZ3A=
|
||||
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
|
||||
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
|
||||
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
|
||||
k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio=
|
||||
k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo=
|
||||
@@ -2597,6 +2597,7 @@ k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d/go.mod h1:3jediapYqJ2w1BF
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
|
||||
k8s.io/apimachinery v0.0.0-20191115015347-3c7067801da2/go.mod h1:dXFS2zaQR8fyzuvRdJDHw2Aerij/yVGJSre0bZQSVJA=
|
||||
k8s.io/apimachinery v0.0.0-20191121175448-79c2a76c473a/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.15.8/go.mod h1:Xc10RHc1U+F/e9GCloJ8QAeCGevSVP5xhOhqlE+e1kM=
|
||||
k8s.io/apimachinery v0.16.4/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
|
||||
k8s.io/apimachinery v0.16.5-beta.1/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
@@ -2611,7 +2612,6 @@ k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftc
|
||||
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
|
||||
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.18.7-rc.0/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0=
|
||||
@@ -2625,7 +2625,6 @@ k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
|
||||
k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
|
||||
k8s.io/apiserver v0.17.6/go.mod h1:sAYqm8hUDNA9aj/TzqwsJoExWrxprKv0tqs/z88qym0=
|
||||
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
|
||||
k8s.io/apiserver v0.18.2 h1:fwKxdTWwwYhxvtjo0UUfX+/fsitsNtfErPNegH2x9ic=
|
||||
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
|
||||
k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8=
|
||||
k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg=
|
||||
@@ -2641,6 +2640,7 @@ k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KN
|
||||
k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I=
|
||||
k8s.io/code-generator v0.0.0-20190831074504-732c9ca86353/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/code-generator v0.15.8/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I=
|
||||
k8s.io/code-generator v0.16.4/go.mod h1:mJUgkl06XV4kstAnLHAIzJPVCOzVR+ZcfPIv4fUsFCY=
|
||||
k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
@@ -2700,6 +2700,7 @@ k8s.io/kubectl v0.18.6/go.mod h1:3TLzFOrF9h4mlRPAvdNkDbs5NWspN4e0EnPnEB41CGo=
|
||||
k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/kubernetes v1.14.7 h1:wJx/r2HuPVaaBeCUk/P47GSK0eyrj3mI/kESRFBp6/A=
|
||||
k8s.io/kubernetes v1.14.7/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8=
|
||||
k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js=
|
||||
@@ -2787,10 +2788,9 @@ sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3L
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Artifact Hub repository metadata file
|
||||
#
|
||||
# Some settings like the verified publisher flag or the ignored packages won't
|
||||
# be applied until the next time the repository is processed. Please keep in
|
||||
# mind that the repository won't be processed if it has not changed since the
|
||||
# last time it was processed. Depending on the repository kind, this is checked
|
||||
# in a different way. For Helm http based repositories, we consider it has
|
||||
# changed if the `index.yaml` file changes. For git based repositories, it does
|
||||
# when the hash of the last commit in the branch you set up changes. This does
|
||||
# NOT apply to ownership claim operations, which are processed immediately.
|
||||
#
|
||||
repositoryID: ARTIFACT_HUB_REPOSITORY_ID
|
||||
owners:
|
||||
- name: Lei Zhang (Harry)
|
||||
email: resouer@gmail.com
|
||||
- name: Jianbo Sun
|
||||
email: wonderflow.sun@gmail.com
|
||||
- name: Ryan Zhang
|
||||
email: yangzhangrice@hotmail.com
|
||||
- name: Hongchao Deng
|
||||
email: hongchaodeng1@gmail.com
|
||||
- name: Zheng Xi Zhou
|
||||
email: zzxwill@gmail.com
|
||||
+40
-44
@@ -3,65 +3,61 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
var crds []string
|
||||
args := os.Args
|
||||
if len(args) <= 1 {
|
||||
fmt.Println("no CRDs is specified")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(os.Args) > 1 {
|
||||
dir = os.Args[1]
|
||||
}
|
||||
err = FixNewSchemaValidationCheck(dir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error getting chart source:", err)
|
||||
crds = args[1:]
|
||||
if err := fixNewSchemaValidationCheck(crds); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// FixNewSchemaValidationCheck temporarily corrects spec.validation.openAPIV3Schema issue, and it would be removed
|
||||
// after this issue was fixed https://github.com/oam-dev/kubevela/issues/284.
|
||||
func FixNewSchemaValidationCheck(chartPath string) error {
|
||||
err := filepath.Walk(chartPath, func(path string, info os.FileInfo, err error) error {
|
||||
func fixNewSchemaValidationCheck(crds []string) error {
|
||||
for _, crd := range crds {
|
||||
data, err := ioutil.ReadFile(crd)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to list the content of", path)
|
||||
fmt.Fprintf(os.Stderr, "reading CRD file %s hit an issue: %s\n", crd, err)
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
var newData []string
|
||||
// temporarily corrects spec.validation.openAPIV3Schema issue https://github.com/kubernetes/kubernetes/issues/91395
|
||||
if strings.HasSuffix(crd, "charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml") {
|
||||
var previousLine string
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.Contains(previousLine, "protocol:") &&
|
||||
strings.Contains(line, "description: Protocol for port. Must be UDP, TCP,") {
|
||||
tmp := strings.Split(line, "description")
|
||||
|
||||
if info.Name() != "standard.oam.dev_podspecworkloads.yaml" {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "open path err", path, err)
|
||||
return err
|
||||
}
|
||||
var newdata []string
|
||||
var previousLine string
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.Contains(previousLine, "protocol:") &&
|
||||
strings.Contains(line, "description: Protocol for port. Must be UDP, TCP,") {
|
||||
tmp := strings.Split(line, "description")
|
||||
|
||||
if len(tmp) > 0 {
|
||||
blanks := tmp[0]
|
||||
defaultStr := blanks + "default: TCP"
|
||||
newdata = append(newdata, defaultStr)
|
||||
if len(tmp) > 0 {
|
||||
blanks := tmp[0]
|
||||
defaultStr := blanks + "default: TCP"
|
||||
newData = append(newData, defaultStr)
|
||||
}
|
||||
}
|
||||
newData = append(newData, line)
|
||||
previousLine = line
|
||||
}
|
||||
newdata = append(newdata, line)
|
||||
previousLine = line
|
||||
ioutil.WriteFile(crd, []byte(strings.Join(newData, "\n")), 0644)
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(path, []byte(strings.Join(newdata, "\n")), info.Mode())
|
||||
})
|
||||
return err
|
||||
// fix issue https://github.com/oam-dev/kubevela/issues/993
|
||||
if strings.HasSuffix(crd, "legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml") {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.Contains(line, "default: Issuer") {
|
||||
continue
|
||||
}
|
||||
newData = append(newData, line)
|
||||
}
|
||||
ioutil.WriteFile(crd, []byte(strings.Join(newData, "\n")), 0644)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,17 @@ metadata:
|
||||
Please use route trait in cap center for advanced usage."
|
||||
name: ingress
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
if len(context.outputs.ingress.status.loadBalancer.ingress) > 0 {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + context.outputs.ingress.status.loadBalancer.ingress[0].ip
|
||||
}
|
||||
if len(context.outputs.ingress.status.loadBalancer.ingress) == 0 {
|
||||
message: "No loadBalancer found, visiting by using 'vela port-forward " + context.appName + " --route'\n"
|
||||
}
|
||||
healthPolicy: |
|
||||
isHealth: len(context.outputs.service.spec.clusterIP) > 0
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worker
|
||||
extension:
|
||||
template: |
|
||||
template: |
|
||||
|
||||
@@ -11,5 +11,4 @@ spec:
|
||||
definitionRef:
|
||||
name: manualscalertraits.core.oam.dev
|
||||
workloadRefPath: spec.workloadRef
|
||||
extension:
|
||||
template: |-
|
||||
template: |
|
||||
|
||||
@@ -7,5 +7,4 @@ metadata:
|
||||
spec:
|
||||
definitionRef:
|
||||
name: jobs.batch
|
||||
extension:
|
||||
template: |
|
||||
template: |
|
||||
|
||||
@@ -8,5 +8,4 @@ metadata:
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
template: |
|
||||
|
||||
@@ -7,5 +7,4 @@ metadata:
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
template: |
|
||||
|
||||
@@ -16,7 +16,7 @@ echo "# Code generated by KubeVela templates. DO NOT EDIT." >> tmpC
|
||||
for filename in `ls cue`; do
|
||||
cat "cue/${filename}" > tmp
|
||||
echo "" >> tmp
|
||||
sed -i.bak 's/^/ /' tmp
|
||||
sed -i.bak 's/^/ /' tmp
|
||||
|
||||
nameonly="${filename%.*}"
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Legacy Support
|
||||
|
||||
Now lots of apps are still running on Kubernetes clusters version v1.14 or v1.15, while KubeVela core requires the minimum
|
||||
Kubernetes version to be v1.16+.
|
||||
|
||||
Currently, the main blocker is KubeVela uses CRD v1, while those old Kubernetes versions don't support CRD v1.
|
||||
So we generate v1beta1 CRD here for convenience. But we have no guarantee that KubeVela core will support the
|
||||
legacy Kubernetes versions.
|
||||
|
||||
Follow the instructions in [README](../README.md) to create a namespace like `vela-system` and add the OAM Kubernetes
|
||||
Runtime helm repo.
|
||||
|
||||
```
|
||||
$ kubectl create namespace vela-system
|
||||
$ helm repo add kubevela https://kubevelacharts.oss-cn-hangzhou.aliyuncs.com/core
|
||||
```
|
||||
|
||||
Run the following command to install a KubeVela core legacy chart.
|
||||
|
||||
```
|
||||
$ helm install -n vela-system vela-core-legacy kubevela/vela-core-legacy
|
||||
```
|
||||
|
||||
If you'd like to install an older version of the legacy chart, use `helm search` to choose a proper chart version.
|
||||
```
|
||||
$ helm search repo vela-core-legacy -l
|
||||
NAME CHART VERSION APP VERSION DESCRIPTION
|
||||
kubevela/vela-core-legacy 0.2 0.2 A Helm chart for legacy KubeVela core Controlle...
|
||||
kubevela/vela-core-legacy 0.0.1 0.1 A Helm chart for legacy KubeVela core Controlle...
|
||||
|
||||
$ helm install -n vela-system kubevela-legacy kubevela/vela-core-legacy --version 0.0.1
|
||||
```
|
||||
|
||||
Install the legacy chart as below if you want a nightly version.
|
||||
|
||||
```
|
||||
$ helm install -n vela-system vela-core-legacy kubevela/vela-core-legacy --set image.tag=master
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
name: vela-core-legacy
|
||||
description: A Helm chart for legacy KubeVela Core Controller, targeted on Kubernetes v1.14 and v1.15
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: 0.1.0
|
||||
@@ -386,6 +386,10 @@ spec:
|
||||
componentRevisionName:
|
||||
description: ComponentRevisionName of current component
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: ObservedGeneration indicates the generation observed by the appconfig controller. The same field is also recorded in the annotations of workloads. A workload is possible to be deleted from cluster after created. This field is useful to track the observed generation of workloads after they are deleted.
|
||||
format: int64
|
||||
type: integer
|
||||
scopes:
|
||||
description: Scopes associated with this workload.
|
||||
items:
|
||||
|
||||
@@ -108,7 +108,7 @@ spec:
|
||||
description: Paused the rollout, default is false
|
||||
type: boolean
|
||||
rolloutBatches:
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches. The total number cannot exceed the targetSize or the size of the source resource We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum We highly recommend to leave the last batch's replica field empty
|
||||
items:
|
||||
description: RolloutBatch is used to describe how the each batch rollout should be
|
||||
properties:
|
||||
@@ -210,7 +210,7 @@ spec:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods it is mutually exclusive with the PodList field'
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods we will ignore the percentage of the last batch to just fill the gap it is mutually exclusive with the PodList field'
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
type: array
|
||||
@@ -218,7 +218,7 @@ spec:
|
||||
description: RolloutStrategy defines strategies for the rollout plan
|
||||
type: string
|
||||
rolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
description: RolloutWebhooks provide a way for the rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
@@ -258,11 +258,10 @@ spec:
|
||||
type: string
|
||||
required:
|
||||
- rolloutPlan
|
||||
- sourceApplicationName
|
||||
- targetApplicationName
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
description: ApplicationDeploymentStatus defines the observed state of ApplicationDeployment
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
@@ -296,17 +295,23 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
currentBatch:
|
||||
description: The current batch the rollout is working on/blocked
|
||||
description: The current batch the rollout is working on/blocked it starts from 0
|
||||
format: int32
|
||||
type: integer
|
||||
lastAppliedPodTemplateIdentifier:
|
||||
description: lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template each workload type could use different ways to identify that so we cannot compare between resources We update this field only after a successful rollout
|
||||
type: string
|
||||
lastSourceApplicationName:
|
||||
description: LastSourceApplicationName contains the name of the application that we need to upgrade from. We will restart the rollout if this is not the same as the spec
|
||||
type: string
|
||||
lastTargetApplicationName:
|
||||
description: LastTargetApplicationName contains the name of the application that we upgraded to We will restart the rollout if this is not the same as the spec
|
||||
type: string
|
||||
rollingState:
|
||||
description: RollingState is the Rollout State
|
||||
type: string
|
||||
sourceGeneration:
|
||||
description: The source resource generation
|
||||
type: string
|
||||
targetGeneration:
|
||||
description: The target resource generation
|
||||
description: NewPodTemplateIdentifier is a string that uniquely represent the new pod template each workload type could use different ways to identify that so we cannot compare between resources
|
||||
type: string
|
||||
upgradedReadyReplicas:
|
||||
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
|
||||
@@ -318,9 +323,8 @@ spec:
|
||||
type: integer
|
||||
required:
|
||||
- currentBatch
|
||||
- lastTargetApplicationName
|
||||
- rollingState
|
||||
- sourceGeneration
|
||||
- targetGeneration
|
||||
- upgradedReadyReplicas
|
||||
- upgradedReplicas
|
||||
type: object
|
||||
|
||||
@@ -127,6 +127,37 @@ spec:
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
services:
|
||||
description: Services record the status of the application services
|
||||
items:
|
||||
description: ApplicationComponentStatus record the health status of App component
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
traits:
|
||||
items:
|
||||
description: ApplicationTraitStatus records the trait health status
|
||||
properties:
|
||||
healthy:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- healthy
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- healthy
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
status:
|
||||
description: ApplicationPhase is a label for the condition of a application at the current time
|
||||
type: string
|
||||
|
||||
@@ -67,6 +67,22 @@ spec:
|
||||
revisionEnabled:
|
||||
description: Revision indicates whether a trait is aware of component revision
|
||||
type: boolean
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for trait
|
||||
properties:
|
||||
customStatus:
|
||||
description: CustomStatus defines the custom status message that could display to user
|
||||
type: string
|
||||
healthPolicy:
|
||||
description: HealthPolicy defines the health check policy for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object
|
||||
type: string
|
||||
|
||||
@@ -81,6 +81,22 @@ spec:
|
||||
revisionLabel:
|
||||
description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods).
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the custom health policy and status message for workload
|
||||
properties:
|
||||
customStatus:
|
||||
description: CustomStatus defines the custom status message that could display to user
|
||||
type: string
|
||||
healthPolicy:
|
||||
description: HealthPolicy defines the health check policy for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
template:
|
||||
description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE
|
||||
type: string
|
||||
templateType:
|
||||
description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future.
|
||||
type: string
|
||||
required:
|
||||
- definitionRef
|
||||
type: object
|
||||
|
||||
@@ -1,350 +0,0 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: rollouts.standard.oam.dev
|
||||
spec:
|
||||
group: standard.oam.dev
|
||||
names:
|
||||
kind: Rollout
|
||||
listKind: RolloutList
|
||||
plural: rollouts
|
||||
singular: rollout
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Rollout is the Schema for the rollouts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RolloutSpec defines the desired state of Rollout
|
||||
properties:
|
||||
rolloutPlan:
|
||||
description: RolloutPlan is the details on how to rollout the resources
|
||||
properties:
|
||||
canaryMetric:
|
||||
description: CanaryMetric provides a way for the rollout process to automatically check certain metrics before complete the process
|
||||
items:
|
||||
description: CanaryMetric holds the reference to metrics used for canary analysis
|
||||
properties:
|
||||
interval:
|
||||
description: Interval represents the windows size
|
||||
type: string
|
||||
metricsRange:
|
||||
description: Range value accepted for this metric
|
||||
properties:
|
||||
max:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Maximum value
|
||||
x-kubernetes-int-or-string: true
|
||||
min:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Minimum value
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
name:
|
||||
description: Name of the metric
|
||||
type: string
|
||||
templateRef:
|
||||
description: TemplateRef references a metric template object
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
lastBatchToRollout:
|
||||
description: All pods in the batches up to the batchPartition (included) will have the target resource specification while the rest still have the source resource This is designed for the operators to manually rollout Default is the the number of batches which will rollout all the batches
|
||||
format: int32
|
||||
type: integer
|
||||
numBatches:
|
||||
description: The number of batches, default = 1 mutually exclusive to RolloutBatches
|
||||
format: int32
|
||||
type: integer
|
||||
rolloutBatches:
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches
|
||||
items:
|
||||
description: RolloutBatch is used to describe how the each batch rollout should be
|
||||
properties:
|
||||
batchRolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the batch rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
metadata:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Metadata (key-value pairs) for this webhook
|
||||
type: object
|
||||
name:
|
||||
description: Name of this webhook
|
||||
type: string
|
||||
timeout:
|
||||
description: Request timeout for this webhook
|
||||
type: string
|
||||
type:
|
||||
description: Type of this webhook
|
||||
type: string
|
||||
url:
|
||||
description: URL address of this webhook
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- url
|
||||
type: object
|
||||
type: array
|
||||
canaryMetric:
|
||||
description: CanaryMetric provides a way for the batch rollout process to automatically check certain metrics before moving to the next batch
|
||||
items:
|
||||
description: CanaryMetric holds the reference to metrics used for canary analysis
|
||||
properties:
|
||||
interval:
|
||||
description: Interval represents the windows size
|
||||
type: string
|
||||
metricsRange:
|
||||
description: Range value accepted for this metric
|
||||
properties:
|
||||
max:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Maximum value
|
||||
x-kubernetes-int-or-string: true
|
||||
min:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Minimum value
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
name:
|
||||
description: Name of the metric
|
||||
type: string
|
||||
templateRef:
|
||||
description: TemplateRef references a metric template object
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
instanceInterval:
|
||||
description: The wait time, in seconds, between instances upgrades, default = 0
|
||||
format: int32
|
||||
type: integer
|
||||
maxUnavailable:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: MaxUnavailable is the max allowed number of pods that is unavailable during the upgrade. We will mark the batch as ready as long as there are less or equal number of pods unavailable than this number. default = 0
|
||||
x-kubernetes-int-or-string: true
|
||||
podList:
|
||||
description: The list of Pods to get upgraded it is mutually exclusive with the Replica field
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
replica:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replica is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods it is mutually exclusive with the PodList field'
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
type: array
|
||||
rolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
metadata:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Metadata (key-value pairs) for this webhook
|
||||
type: object
|
||||
name:
|
||||
description: Name of this webhook
|
||||
type: string
|
||||
timeout:
|
||||
description: Request timeout for this webhook
|
||||
type: string
|
||||
type:
|
||||
description: Type of this webhook
|
||||
type: string
|
||||
url:
|
||||
description: URL address of this webhook
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- url
|
||||
type: object
|
||||
type: array
|
||||
stopped:
|
||||
description: Stopped the rollout, default is false
|
||||
type: boolean
|
||||
targetSize:
|
||||
description: The size of the target resource. The default is the same as the size of the source resource.
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
sourceRef:
|
||||
description: SourceRef references the source resource that contains the older version of the software. We assume that it's the first time to deploy when we cannot find the source.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
targetRef:
|
||||
description: TargetRef references a target resource that contains the newer version of the software. We assumed that new resource already exists. This is the only resource we work on if the resource is a stateful resource (cloneset/statefulset)
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referenced object.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referenced object.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
uid:
|
||||
description: UID of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- rolloutPlan
|
||||
- targetRef
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
type: string
|
||||
conditions:
|
||||
description: Conditions represents the latest available observations of a CloneSet's current state.
|
||||
items:
|
||||
description: RolloutCondition is the condition of the rollout
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
type: string
|
||||
lastTransitionTime:
|
||||
description: Last time the condition transitioned to this state
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details about the transition.
|
||||
type: string
|
||||
reason:
|
||||
description: The reason for the condition's last transition.
|
||||
type: string
|
||||
rollingState:
|
||||
description: RollingState is the Rollout Status
|
||||
type: string
|
||||
required:
|
||||
- rollingState
|
||||
type: object
|
||||
type: array
|
||||
currentBatch:
|
||||
description: The current batch the rollout is working on/blocked
|
||||
format: int32
|
||||
type: integer
|
||||
rollingState:
|
||||
description: RollingState is the Rollout State
|
||||
type: string
|
||||
sourceGeneration:
|
||||
description: The source resource generation
|
||||
type: string
|
||||
targetGeneration:
|
||||
description: The target resource generation
|
||||
type: string
|
||||
upgradedReplicas:
|
||||
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- currentBatch
|
||||
- rollingState
|
||||
- sourceGeneration
|
||||
- targetGeneration
|
||||
- upgradedReplicas
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -98,7 +98,7 @@ spec:
|
||||
description: Paused the rollout, default is false
|
||||
type: boolean
|
||||
rolloutBatches:
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches
|
||||
description: The exact distribution among batches. mutually exclusive to NumBatches. The total number cannot exceed the targetSize or the size of the source resource We will IGNORE the last batch's replica field if it's a percentage since round errors can lead to inaccurate sum We highly recommend to leave the last batch's replica field empty
|
||||
items:
|
||||
description: RolloutBatch is used to describe how the each batch rollout should be
|
||||
properties:
|
||||
@@ -200,7 +200,7 @@ spec:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods it is mutually exclusive with the PodList field'
|
||||
description: 'Replicas is the number of pods to upgrade in this batch it can be an absolute number (ex: 5) or a percentage of total pods we will ignore the percentage of the last batch to just fill the gap it is mutually exclusive with the PodList field'
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
type: array
|
||||
@@ -208,7 +208,7 @@ spec:
|
||||
description: RolloutStrategy defines strategies for the rollout plan
|
||||
type: string
|
||||
rolloutWebhooks:
|
||||
description: RolloutWebhooks provides a way for the rollout to interact with an external process
|
||||
description: RolloutWebhooks provide a way for the rollout to interact with an external process
|
||||
items:
|
||||
description: RolloutWebhook holds the reference to external checks used for canary analysis
|
||||
properties:
|
||||
@@ -288,7 +288,7 @@ spec:
|
||||
- targetRef
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
description: RolloutStatus defines the observed state of a rollout plan
|
||||
properties:
|
||||
batchRollingState:
|
||||
description: BatchRollingState only meaningful when the Status is rolling
|
||||
@@ -322,17 +322,17 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
currentBatch:
|
||||
description: The current batch the rollout is working on/blocked
|
||||
description: The current batch the rollout is working on/blocked it starts from 0
|
||||
format: int32
|
||||
type: integer
|
||||
lastAppliedPodTemplateIdentifier:
|
||||
description: lastAppliedPodTemplateIdentifier is a string that uniquely represent the last pod template each workload type could use different ways to identify that so we cannot compare between resources We update this field only after a successful rollout
|
||||
type: string
|
||||
rollingState:
|
||||
description: RollingState is the Rollout State
|
||||
type: string
|
||||
sourceGeneration:
|
||||
description: The source resource generation
|
||||
type: string
|
||||
targetGeneration:
|
||||
description: The target resource generation
|
||||
description: NewPodTemplateIdentifier is a string that uniquely represent the new pod template each workload type could use different ways to identify that so we cannot compare between resources
|
||||
type: string
|
||||
upgradedReadyReplicas:
|
||||
description: UpgradedReplicas is the number of Pods upgraded by the rollout controller that have a Ready Condition.
|
||||
@@ -345,8 +345,6 @@ spec:
|
||||
required:
|
||||
- currentBatch
|
||||
- rollingState
|
||||
- sourceGeneration
|
||||
- targetGeneration
|
||||
- upgradedReadyReplicas
|
||||
- upgradedReplicas
|
||||
type: object
|
||||
|
||||
@@ -37,6 +37,9 @@ spec:
|
||||
host:
|
||||
description: Host is the host of the route
|
||||
type: string
|
||||
ingressClass:
|
||||
description: IngressClass indicate which ingress class the route trait will use, by default it's nginx
|
||||
type: string
|
||||
provider:
|
||||
description: Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
|
||||
type: string
|
||||
@@ -113,7 +116,6 @@ spec:
|
||||
issuerName:
|
||||
type: string
|
||||
type:
|
||||
default: Issuer
|
||||
description: Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
|
||||
type: string
|
||||
type: object
|
||||
|
||||
@@ -27,7 +27,7 @@ var _ = It("Test ApplyTerraform", func() {
|
||||
ioStream := util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
dm, _ := discoverymapper.New(cfg)
|
||||
_, err := ApplyTerraform(app, k8sClient, ioStream, addonNamespace, dm)
|
||||
Expect(err.Error()).Should(Equal("exit status 1"))
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
})
|
||||
|
||||
var _ = Describe("Test generateSecretFromTerraformOutput", func() {
|
||||
|
||||
@@ -34,6 +34,11 @@ const (
|
||||
DefaultUnknowFormatAppfilePath = "./Appfile"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHealthScopeKey is the key in application for default health scope
|
||||
DefaultHealthScopeKey = "healthscopes.core.oam.dev"
|
||||
)
|
||||
|
||||
// AppFile defines the spec of KubeVela Appfile
|
||||
type AppFile struct {
|
||||
Name string `json:"name"`
|
||||
@@ -179,7 +184,7 @@ func addDefaultHealthScopeToApplication(app *v1alpha2.Application) *v1alpha2.Hea
|
||||
health.Spec.WorkloadReferences = make([]v1alpha1.TypedReference, 0)
|
||||
for i := range app.Spec.Components {
|
||||
// FIXME(wonderflow): the hardcode health scope should be fixed.
|
||||
app.Spec.Components[i].Scopes = map[string]string{"healthscopes.core.oam.dev": health.Name}
|
||||
app.Spec.Components[i].Scopes = map[string]string{DefaultHealthScopeKey: health.Name}
|
||||
}
|
||||
return health
|
||||
}
|
||||
|
||||
+52
-28
@@ -22,7 +22,7 @@ const (
|
||||
// AppfileBuiltinConfig defines the built-in config variable
|
||||
AppfileBuiltinConfig = "config"
|
||||
|
||||
// OAMApplicationLabel is application's metadata label
|
||||
// OAMApplicationLabel is application's metadata label tagged on AC and Component
|
||||
OAMApplicationLabel = "application.oam.dev"
|
||||
)
|
||||
|
||||
@@ -32,10 +32,12 @@ type Workload struct {
|
||||
Type string
|
||||
CapabilityCategory types.CapabilityCategory
|
||||
Params map[string]interface{}
|
||||
Template string
|
||||
Health string
|
||||
Traits []*Trait
|
||||
Scopes []Scope
|
||||
|
||||
Template string
|
||||
HealthCheckPolicy string
|
||||
CustomStatusFormat string
|
||||
}
|
||||
|
||||
// GetUserConfigName get user config from AppFile, it will contain config file in it.
|
||||
@@ -56,12 +58,17 @@ func (wl *Workload) GetUserConfigName() string {
|
||||
|
||||
// EvalContext eval workload template and set result to context
|
||||
func (wl *Workload) EvalContext(ctx process.Context) error {
|
||||
return definition.NewWDTemplater(wl.Name, wl.Template, "").Params(wl.Params).Complete(ctx)
|
||||
return definition.NewWorkloadAbstractEngine(wl.Name).Params(wl.Params).Complete(ctx, wl.Template)
|
||||
}
|
||||
|
||||
// EvalStatus eval workload status
|
||||
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return definition.NewWorkloadAbstractEngine(wl.Name).Status(ctx, cli, ns, wl.CustomStatusFormat)
|
||||
}
|
||||
|
||||
// EvalHealth eval workload health check
|
||||
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, name string) error {
|
||||
return definition.NewWDTemplater(wl.Name, "", wl.Health).Output(ctx, client, name).HealthCheck()
|
||||
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
return definition.NewWorkloadAbstractEngine(wl.Name).HealthCheck(ctx, client, namespace, wl.HealthCheckPolicy)
|
||||
}
|
||||
|
||||
// Scope defines the scope of workload
|
||||
@@ -72,21 +79,29 @@ type Scope struct {
|
||||
|
||||
// Trait is ComponentTrait
|
||||
type Trait struct {
|
||||
// The Name is name of TraitDefinition, actually it's a type of the trait instance
|
||||
Name string
|
||||
CapabilityCategory types.CapabilityCategory
|
||||
Params map[string]interface{}
|
||||
|
||||
Template string
|
||||
Health string
|
||||
HealthCheckPolicy string
|
||||
CustomStatusFormat string
|
||||
}
|
||||
|
||||
// EvalContext eval trait template and set result to context
|
||||
func (trait *Trait) EvalContext(ctx process.Context) error {
|
||||
return definition.NewTDTemplater(trait.Name, trait.Template, "").Params(trait.Params).Complete(ctx)
|
||||
return definition.NewTraitAbstractEngine(trait.Name).Params(trait.Params).Complete(ctx, trait.Template)
|
||||
}
|
||||
|
||||
// EvalStatus eval trait status
|
||||
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return definition.NewTraitAbstractEngine(trait.Name).Status(ctx, cli, ns, trait.CustomStatusFormat)
|
||||
}
|
||||
|
||||
// EvalHealth eval trait health check
|
||||
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, name string) error {
|
||||
return definition.NewTDTemplater(trait.Name, "", trait.Health).Output(ctx, client, name).HealthCheck()
|
||||
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
return definition.NewTraitAbstractEngine(trait.Name).HealthCheck(ctx, client, namespace, trait.HealthCheckPolicy)
|
||||
}
|
||||
|
||||
// Appfile describes application
|
||||
@@ -142,7 +157,8 @@ func (p *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload, e
|
||||
}
|
||||
workload.CapabilityCategory = templ.CapabilityCategory
|
||||
workload.Template = templ.TemplateStr
|
||||
workload.Health = templ.Health
|
||||
workload.HealthCheckPolicy = templ.Health
|
||||
workload.CustomStatusFormat = templ.CustomStatus
|
||||
settings, err := util.RawExtension2Map(&comp.Settings)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "fail to parse settings for %s", comp.Name)
|
||||
@@ -187,7 +203,8 @@ func (p *Parser) parseTrait(name string, properties map[string]interface{}) (*Tr
|
||||
CapabilityCategory: templ.CapabilityCategory,
|
||||
Params: properties,
|
||||
Template: templ.TemplateStr,
|
||||
Health: templ.Health,
|
||||
HealthCheckPolicy: templ.Health,
|
||||
CustomStatusFormat: templ.CustomStatus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -213,10 +230,10 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a
|
||||
}
|
||||
for _, tr := range wl.Traits {
|
||||
if err := tr.EvalContext(pCtx); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, errors.Wrapf(err, "evaluate template trait=%s app=%s", tr.Name, wl.Name)
|
||||
}
|
||||
}
|
||||
comp, acComp, err := evalWorkloadWithContext(pCtx, wl)
|
||||
comp, acComp, err := evalWorkloadWithContext(pCtx, wl, app.Name, wl.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -245,20 +262,19 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a
|
||||
}
|
||||
|
||||
// evalWorkloadWithContext evaluate the workload's template to generate component and ACComponent
|
||||
func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compName string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
base, assists := pCtx.Output()
|
||||
componentWorkload, err := base.Unstructured()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, errors.Wrapf(err, "evaluate base template component=%s app=%s", compName, appName)
|
||||
}
|
||||
workloadType := wl.Type
|
||||
labels := componentWorkload.GetLabels()
|
||||
if labels == nil {
|
||||
labels = map[string]string{oam.WorkloadTypeLabel: workloadType}
|
||||
} else {
|
||||
labels[oam.WorkloadTypeLabel] = workloadType
|
||||
|
||||
labels := map[string]string{
|
||||
oam.WorkloadTypeLabel: wl.Type,
|
||||
oam.LabelAppName: appName,
|
||||
oam.LabelAppComponent: compName,
|
||||
}
|
||||
componentWorkload.SetLabels(labels)
|
||||
util.AddLabels(componentWorkload, labels)
|
||||
|
||||
component := &v1alpha2.Component{}
|
||||
component.Spec.Workload.Object = componentWorkload
|
||||
@@ -268,9 +284,17 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Comp
|
||||
for _, assist := range assists {
|
||||
tr, err := assist.Ins.Unstructured()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, compName, appName)
|
||||
}
|
||||
tr.SetLabels(map[string]string{oam.TraitTypeLabel: assist.Type})
|
||||
labels := map[string]string{
|
||||
oam.TraitTypeLabel: assist.Type,
|
||||
oam.LabelAppName: appName,
|
||||
oam.LabelAppComponent: compName,
|
||||
}
|
||||
if assist.Name != "" {
|
||||
labels[oam.TraitResource] = assist.Name
|
||||
}
|
||||
util.AddLabels(tr, labels)
|
||||
acComponent.Traits = append(acComponent.Traits, v1alpha2.ComponentTrait{
|
||||
Trait: runtime.RawExtension{
|
||||
Object: tr,
|
||||
@@ -282,7 +306,7 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Comp
|
||||
|
||||
// PrepareProcessContext prepares a DSL process Context
|
||||
func PrepareProcessContext(k8sClient client.Client, wl *Workload, applicationName string, namespace string) (process.Context, error) {
|
||||
pCtx := process.NewContext(wl.Name)
|
||||
pCtx := process.NewContext(wl.Name, applicationName)
|
||||
userConfig := wl.GetUserConfigName()
|
||||
if userConfig != "" {
|
||||
cg := config.Configmap{Client: k8sClient}
|
||||
@@ -290,12 +314,12 @@ func PrepareProcessContext(k8sClient client.Client, wl *Workload, applicationNam
|
||||
var envName = namespace
|
||||
data, err := cg.GetConfigData(config.GenConfigMapName(applicationName, wl.Name, userConfig), envName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "get config=%s for app=%s in namespace=%s", userConfig, applicationName, namespace)
|
||||
}
|
||||
pCtx.SetConfigs(data)
|
||||
}
|
||||
if err := wl.EvalContext(pCtx); err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "evaluate base template app=%s in namespace=%s", applicationName, namespace)
|
||||
}
|
||||
return pCtx, nil
|
||||
}
|
||||
|
||||
@@ -401,7 +401,9 @@ var _ = Describe("Test appFile parser", func() {
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"app.oam.dev/component": "myweb",
|
||||
"app.oam.dev/name": "test",
|
||||
"trait.oam.dev/type": "scaler",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{"replicaCount": int64(10)},
|
||||
@@ -413,7 +415,8 @@ var _ = Describe("Test appFile parser", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(ac).To(BeEquivalentTo(expectAppConfig))
|
||||
fmt.Println(cmp.Diff(expectAppConfig, ac))
|
||||
Expect(assert.ObjectsAreEqual(expectAppConfig, ac)).To(Equal(true))
|
||||
|
||||
expectComponent := &v1alpha2.Component{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -432,6 +435,8 @@ var _ = Describe("Test appFile parser", func() {
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"workload.oam.dev/type": "worker",
|
||||
"app.oam.dev/component": "myweb",
|
||||
"app.oam.dev/name": "test",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
@@ -464,8 +469,7 @@ var _ = Describe("Test appFile parser", func() {
|
||||
Expect(len(components)).To(BeEquivalentTo(1))
|
||||
Expect(components[0].ObjectMeta).To(BeEquivalentTo(expectComponent.ObjectMeta))
|
||||
Expect(components[0].TypeMeta).To(BeEquivalentTo(expectComponent.TypeMeta))
|
||||
logf.Log.Info(fmt.Sprintf("diff %+v", cmp.Diff(components[0].Spec.Workload.Object,
|
||||
expectComponent.Spec.Workload.Object)))
|
||||
logf.Log.Info(cmp.Diff(components[0].Spec.Workload.Object, expectComponent.Spec.Workload.Object))
|
||||
Expect(assert.ObjectsAreEqual(components[0].Spec.Workload.Object, expectComponent.Spec.Workload.Object)).To(BeTrue())
|
||||
})
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ func (b *Build) buildImage(io cmdutil.IOStreams, image string) error {
|
||||
io.Errorf("BuildImage wait for command execution error:%s", err.Error())
|
||||
return err
|
||||
}
|
||||
return b.pushImage(io, image)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Build) pushImage(io cmdutil.IOStreams, image string) error {
|
||||
|
||||
@@ -226,6 +226,7 @@ func NewCapCenterRemoveCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
|
||||
func listCapCenters(ioStreams cmdutil.IOStreams) error {
|
||||
table := newUITable()
|
||||
table.MaxColWidth = 80
|
||||
table.AddRow("NAME", "ADDRESS")
|
||||
capabilityCenterList, err := serverlib.ListCapabilityCenters()
|
||||
if err != nil {
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ func NewCommand() *cobra.Command {
|
||||
DisableFlagParsing: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
allCommands := cmd.Commands()
|
||||
cmd.Printf("✈️ An Easy-to-use yet Fully Extensible App Platform based on Kubernetes and Open Application Model.\n\nUsage:\n vela [flags]\n vela [command]\n\nAvailable Commands:\n\n")
|
||||
cmd.Printf("A Highly Extensible Platform Engine based on Kubernetes and Open Application Model.\n\nUsage:\n vela [flags]\n vela [command]\n\nAvailable Commands:\n\n")
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeStart)
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeApp)
|
||||
PrintHelpByTag(cmd, allCommands, types.TypeCap)
|
||||
@@ -58,11 +58,11 @@ func NewCommand() *cobra.Command {
|
||||
NewInitCommand(commandArgs, ioStream),
|
||||
NewUpCommand(commandArgs, ioStream),
|
||||
NewExportCommand(commandArgs, ioStream),
|
||||
NewCapabilityShowCommand(commandArgs, ioStream),
|
||||
|
||||
// Apps
|
||||
NewListCommand(commandArgs, ioStream),
|
||||
NewDeleteCommand(commandArgs, ioStream),
|
||||
NewAppShowCommand(commandArgs, ioStream),
|
||||
NewAppStatusCommand(commandArgs, ioStream),
|
||||
NewExecCommand(commandArgs, ioStream),
|
||||
NewPortForwardCommand(commandArgs, ioStream),
|
||||
|
||||
@@ -221,11 +221,13 @@ func OpenBrowser(url string) error {
|
||||
func CheckVelaRuntimeInstalledAndReady(ioStreams cmdutil.IOStreams, c client.Client) (bool, error) {
|
||||
if !helm.IsHelmReleaseRunning(types.DefaultKubeVelaReleaseName, types.DefaultKubeVelaChartName, types.DefaultKubeVelaNS, ioStreams) {
|
||||
ioStreams.Info(fmt.Sprintf("\n%s %s", emojiFail, "KubeVela runtime is not installed yet."))
|
||||
ioStreams.Info(fmt.Sprintf("\n%s %s%s or %s",
|
||||
ioStreams.Info(fmt.Sprintf("\n%s %s%s",
|
||||
emojiLightBulb,
|
||||
"Please use this command to install: ",
|
||||
white.Sprint("vela install -w"),
|
||||
white.Sprint("vela install --help")))
|
||||
white.Sprint("helm repo add kubevela https://kubevelacharts.oss-cn-hangzhou.aliyuncs.com/core && "+
|
||||
"helm repo update \n kubectl create namespace vela-system \n "+
|
||||
"helm install -n vela-system kubevela kubevela/vela-core"),
|
||||
))
|
||||
return false, nil
|
||||
}
|
||||
return PrintTrackVelaRuntimeStatus(context.Background(), c, ioStreams, 5*time.Minute)
|
||||
|
||||
@@ -103,8 +103,7 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
if deployStatus != compStatusDeployed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return printComponentStatus(context.Background(), o.client, o.IOStreams, o.workloadName, o.appName, o.Env)
|
||||
return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Env, cmd)
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeStart,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/gosuri/uitable"
|
||||
hashstructure "github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
|
||||
@@ -40,8 +40,8 @@ const (
|
||||
|
||||
var webSite bool
|
||||
|
||||
// NewAppShowCommand shows the reference doc for a workload type or trait
|
||||
func NewAppShowCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
// NewCapabilityShowCommand shows the reference doc for a workload type or trait
|
||||
func NewCapabilityShowCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show the reference doc for a workload type or trait",
|
||||
|
||||
+74
-149
@@ -4,11 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -19,8 +17,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/api"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oam2 "github.com/oam-dev/kubevela/pkg/serverlib"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
// HealthStatus represents health status strings.
|
||||
@@ -46,10 +43,6 @@ type WorkloadHealthCondition = v1alpha2.WorkloadHealthCondition
|
||||
// ScopeHealthCondition holds health condition of a scope
|
||||
type ScopeHealthCondition = v1alpha2.ScopeHealthCondition
|
||||
|
||||
var (
|
||||
kindHealthScope = reflect.TypeOf(v1alpha2.HealthScope{}).Name()
|
||||
)
|
||||
|
||||
// CompStatus represents the status of a component during "vela init"
|
||||
type CompStatus int
|
||||
|
||||
@@ -119,12 +112,7 @@ func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOSt
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
namespace := env.Name
|
||||
|
||||
targetServices, err := oam2.GetServicesWhenDescribingApplication(cmd, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
namespace := env.Namespace
|
||||
|
||||
cmd.Printf("About:\n\n")
|
||||
table := newUITable()
|
||||
@@ -135,97 +123,74 @@ func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOSt
|
||||
cmd.Printf("%s\n\n", table.String())
|
||||
|
||||
cmd.Printf("Services:\n\n")
|
||||
return loopCheckStatus(ctx, c, ioStreams, appName, env)
|
||||
}
|
||||
|
||||
for _, svcName := range targetServices {
|
||||
if err := printComponentStatus(ctx, c, ioStreams, svcName, appName, env); err != nil {
|
||||
func loadRemoteApplication(c client.Client, ns string, name string) (*v1alpha2.Application, error) {
|
||||
app := new(v1alpha2.Application)
|
||||
err := c.Get(context.Background(), client.ObjectKey{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
}, app)
|
||||
return app, err
|
||||
}
|
||||
|
||||
func loopCheckStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta) error {
|
||||
remoteApp, err := loadRemoteApplication(c, env.Namespace, appName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, comp := range remoteApp.Spec.Components {
|
||||
compName := comp.Name
|
||||
|
||||
healthStatus, healthInfo, err := healthCheckLoop(ctx, c, compName, appName, env)
|
||||
if err != nil {
|
||||
ioStreams.Info(healthInfo)
|
||||
return err
|
||||
}
|
||||
}
|
||||
ioStreams.Infof(white.Sprintf(" - Name: %s\n", compName))
|
||||
ioStreams.Infof(" Type: %s\n", comp.WorkloadType)
|
||||
|
||||
return nil
|
||||
}
|
||||
healthColor := getHealthStatusColor(healthStatus)
|
||||
healthInfo = strings.ReplaceAll(healthInfo, "\n", "\n\t") // format healthInfo output
|
||||
ioStreams.Infof(" %s %s\n", healthColor.Sprint(healthStatus), healthColor.Sprint(healthInfo))
|
||||
|
||||
func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) error {
|
||||
app, appConfig, err := getAppConfig(ctx, c, compName, appName, env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app == nil || appConfig == nil {
|
||||
return errors.New(ErrNotLoadAppConfig)
|
||||
}
|
||||
svc, ok := app.Services[compName]
|
||||
if !ok {
|
||||
return fmt.Errorf(ErrServiceNotFound, compName)
|
||||
}
|
||||
workloadType := svc.GetType()
|
||||
|
||||
healthStatus, healthInfo, err := healthCheckLoop(ctx, c, compName, appName, env)
|
||||
if err != nil {
|
||||
ioStreams.Info(healthInfo)
|
||||
return err
|
||||
}
|
||||
ioStreams.Infof(white.Sprintf(" - Name: %s\n", compName))
|
||||
ioStreams.Infof(" Type: %s\n", workloadType)
|
||||
|
||||
healthColor := getHealthStatusColor(healthStatus)
|
||||
healthInfo = strings.ReplaceAll(healthInfo, "\n", "\n\t") // format healthInfo output
|
||||
ioStreams.Infof(" %s %s\n", healthColor.Sprint(healthStatus), healthColor.Sprint(healthInfo))
|
||||
|
||||
// workload Must found
|
||||
ioStreams.Infof(" Traits:\n")
|
||||
workloadStatus, _ := getWorkloadStatusFromAppConfig(appConfig, compName)
|
||||
for _, tr := range workloadStatus.Traits {
|
||||
traitType, traitInfo, err := traitCheckLoop(ctx, c, tr.Reference, compName, appConfig, app, 60*time.Second)
|
||||
// load it again after health check
|
||||
remoteApp, err = loadRemoteApplication(c, env.Namespace, appName)
|
||||
if err != nil {
|
||||
ioStreams.Infof(" - %s%s: %s, err: %v", emojiFail, white.Sprint(traitType), traitInfo, err)
|
||||
continue
|
||||
return err
|
||||
}
|
||||
ioStreams.Infof(" - %s%s: %s", emojiSucceed, white.Sprint(traitType), traitInfo)
|
||||
// workload Must found
|
||||
ioStreams.Infof(" Traits:\n")
|
||||
workloadStatus, _ := getWorkloadStatusFromApp(remoteApp, compName)
|
||||
for _, tr := range workloadStatus.Traits {
|
||||
if tr.Message != "" {
|
||||
if tr.Healthy {
|
||||
ioStreams.Infof(" - %s%s: %s", emojiSucceed, white.Sprint(tr.Type), tr.Message)
|
||||
} else {
|
||||
ioStreams.Infof(" - %s%s: %s", emojiFail, white.Sprint(tr.Type), tr.Message)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var message string
|
||||
for _, v := range comp.Traits {
|
||||
if v.Name == tr.Type {
|
||||
traitData, _ := util.RawExtension2Map(&v.Properties)
|
||||
for k, v := range traitData {
|
||||
message += fmt.Sprintf("%v=%v\n\t\t", k, v)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
ioStreams.Infof(" - %s%s: %s", emojiSucceed, white.Sprint(tr.Type), message)
|
||||
}
|
||||
ioStreams.Info("")
|
||||
ioStreams.Infof(" Last Deployment:\n")
|
||||
ioStreams.Infof(" Created at: %v\n", remoteApp.CreationTimestamp)
|
||||
}
|
||||
ioStreams.Info("")
|
||||
ioStreams.Infof(" Last Deployment:\n")
|
||||
ioStreams.Infof(" Created at: %v\n", appConfig.CreationTimestamp)
|
||||
ioStreams.Infof(" Updated at: %v\n", app.UpdateTime.Format(time.RFC3339))
|
||||
return nil
|
||||
}
|
||||
|
||||
func traitCheckLoop(ctx context.Context, c client.Client, reference runtimev1alpha1.TypedReference, compName string, appConfig *v1alpha2.ApplicationConfiguration, app *api.Application, timeout time.Duration) (string, string, error) {
|
||||
tr, err := oam2.GetUnstructured(ctx, c, appConfig.Namespace, reference)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
traitType, ok := tr.GetLabels()[oam.TraitTypeLabel]
|
||||
if !ok {
|
||||
message, err := oam2.GetStatusFromObject(tr)
|
||||
return traitType, message, err
|
||||
}
|
||||
|
||||
checker := oam2.GetChecker(traitType, c)
|
||||
|
||||
// Health Check Loop For Trait
|
||||
var message string
|
||||
sHealthCheck := newTrackingSpinner(fmt.Sprintf("Checking %s status ...", traitType))
|
||||
sHealthCheck.Start()
|
||||
defer sHealthCheck.Stop()
|
||||
CheckLoop:
|
||||
for {
|
||||
time.Sleep(trackingInterval)
|
||||
var check oam2.CheckStatus
|
||||
check, message, err = checker.Check(ctx, reference, compName, appConfig, app)
|
||||
if err != nil {
|
||||
message = red.Sprintf("%s check failed!", traitType)
|
||||
return traitType, message, err
|
||||
}
|
||||
if check == oam2.StatusDone {
|
||||
break CheckLoop
|
||||
}
|
||||
if time.Since(tr.GetCreationTimestamp().Time) >= timeout {
|
||||
return traitType, fmt.Sprintf("Checking timeout: %s", message), nil
|
||||
}
|
||||
}
|
||||
return traitType, message, nil
|
||||
}
|
||||
|
||||
func healthCheckLoop(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (HealthStatus, string, error) {
|
||||
// Health Check Loop For Workload
|
||||
var healthInfo string
|
||||
@@ -251,14 +216,6 @@ HealthCheckLoop:
|
||||
return healthStatus, healthInfo, nil
|
||||
}
|
||||
|
||||
func tryGetWorkloadStatus(ctx context.Context, c client.Client, ns string, wlRef runtimev1alpha1.TypedReference) (string, error) {
|
||||
workload, err := oam2.GetUnstructured(ctx, c, ns, wlRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oam2.GetStatusFromObject(workload)
|
||||
}
|
||||
|
||||
func printTrackingDeployStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) (CompStatus, error) {
|
||||
sDeploy := newTrackingSpinnerWithDelay("Checking Status ...", trackingInterval)
|
||||
sDeploy.Start()
|
||||
@@ -314,30 +271,22 @@ func TrackDeployStatus(ctx context.Context, c client.Client, compName, appName s
|
||||
return compStatusDeploying, "", nil
|
||||
}
|
||||
|
||||
// trackHealthCheckingStatus will check health status from health scope
|
||||
func trackHealthCheckingStatus(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (CompStatus, HealthStatus, string, error) {
|
||||
app, appConfig, err := getAppConfig(ctx, c, compName, appName, env)
|
||||
app, err := loadRemoteApplication(c, env.Namespace, appName)
|
||||
if err != nil {
|
||||
return compStatusUnknown, HealthStatusNotDiagnosed, "", err
|
||||
}
|
||||
if app == nil || appConfig == nil {
|
||||
return compStatusUnknown, HealthStatusNotDiagnosed, "", errors.New(ErrNotLoadAppConfig)
|
||||
}
|
||||
|
||||
wlStatus, foundWlStatus := getWorkloadStatusFromAppConfig(appConfig, compName)
|
||||
// make sure component already initilized
|
||||
if !foundWlStatus {
|
||||
if len(appConfig.Status.Conditions) < 1 {
|
||||
// still reconciling
|
||||
return compStatusUnknown, HealthStatusUnknown, "", nil
|
||||
}
|
||||
appConfigConditionMsg := appConfig.Status.GetCondition(runtimev1alpha1.TypeSynced).Message
|
||||
return compStatusUnknown, HealthStatusUnknown, "", fmt.Errorf(ErrFmtNotInitialized, appConfigConditionMsg)
|
||||
if len(app.Status.Conditions) < 1 {
|
||||
// still reconciling
|
||||
return compStatusUnknown, HealthStatusUnknown, "", nil
|
||||
}
|
||||
// check whether referenced a HealthScope
|
||||
var healthScopeName string
|
||||
for _, v := range wlStatus.Scopes {
|
||||
if v.Reference.Kind == kindHealthScope {
|
||||
healthScopeName = v.Reference.Name
|
||||
for _, v := range app.Spec.Components {
|
||||
if len(v.Scopes) > 0 {
|
||||
healthScopeName = v.Scopes[api.DefaultHealthScopeKey]
|
||||
}
|
||||
}
|
||||
var healthStatus HealthStatus
|
||||
@@ -360,38 +309,14 @@ func trackHealthCheckingStatus(ctx context.Context, c client.Client, compName, a
|
||||
return compStatusHealthCheckDone, healthStatus, wlhc.Diagnosis, nil
|
||||
}
|
||||
if healthStatus == HealthStatusUnhealthy {
|
||||
cTime := appConfig.GetCreationTimestamp()
|
||||
cTime := app.GetCreationTimestamp()
|
||||
if time.Since(cTime.Time) <= healthCheckBufferTime {
|
||||
return compStatusHealthChecking, HealthStatusUnknown, "", nil
|
||||
}
|
||||
return compStatusHealthCheckDone, healthStatus, wlhc.Diagnosis, nil
|
||||
}
|
||||
}
|
||||
// No health scope specified or health status is unknown , try get status from workload
|
||||
statusInfo, err := tryGetWorkloadStatus(ctx, c, env.Namespace, wlStatus.Reference)
|
||||
if err != nil {
|
||||
return compStatusUnknown, HealthStatusUnknown, "", err
|
||||
}
|
||||
return compStatusHealthCheckDone, HealthStatusNotDiagnosed, statusInfo, nil
|
||||
}
|
||||
|
||||
func getAppConfig(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (*api.Application, *v1alpha2.ApplicationConfiguration, error) {
|
||||
var app *api.Application
|
||||
var err error
|
||||
if appName != "" {
|
||||
app, err = appfile.LoadApplication(env.Name, appName)
|
||||
} else {
|
||||
app, err = appfile.MatchAppByComp(env.Name, compName)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
appConfig, err := appfile.GetAppConfig(ctx, c, app, env)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return app, appConfig, nil
|
||||
return compStatusHealthCheckDone, HealthStatusNotDiagnosed, "", nil
|
||||
}
|
||||
|
||||
func getApp(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (*api.Application, *v1alpha2.Application, error) {
|
||||
@@ -413,14 +338,14 @@ func getApp(ctx context.Context, c client.Client, compName, appName string, env
|
||||
return app, appObj, nil
|
||||
}
|
||||
|
||||
func getWorkloadStatusFromAppConfig(appConfig *v1alpha2.ApplicationConfiguration, compName string) (v1alpha2.WorkloadStatus, bool) {
|
||||
func getWorkloadStatusFromApp(app *v1alpha2.Application, compName string) (v1alpha2.ApplicationComponentStatus, bool) {
|
||||
foundWlStatus := false
|
||||
wlStatus := v1alpha2.WorkloadStatus{}
|
||||
if appConfig == nil {
|
||||
wlStatus := v1alpha2.ApplicationComponentStatus{}
|
||||
if app == nil {
|
||||
return wlStatus, foundWlStatus
|
||||
}
|
||||
for _, v := range appConfig.Status.Workloads {
|
||||
if v.ComponentName == compName {
|
||||
for _, v := range app.Status.Services {
|
||||
if v.Name == compName {
|
||||
wlStatus = v
|
||||
foundWlStatus = true
|
||||
break
|
||||
|
||||
@@ -94,7 +94,7 @@ func (i *infoCmd) run(ioStreams cmdutil.IOStreams) error {
|
||||
return fmt.Errorf("fail to get cluster chartPath: %w", err)
|
||||
}
|
||||
ioStreams.Info("Versions:")
|
||||
ioStreams.Infof("oam-kubernetes-runtime: %s \n", clusterVersion)
|
||||
ioStreams.Infof("kubevela: %s \n", clusterVersion)
|
||||
// TODO(wonderflow): we should print all helm charts installed by vela, including plugins
|
||||
|
||||
return nil
|
||||
@@ -123,6 +123,7 @@ func NewInstallCommand(c types.Args, chartContent string, ioStreams cmdutil.IOSt
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeStart,
|
||||
},
|
||||
Deprecated: "vela install is DEPRECATED and we will remove it after Kubevela 1.0. Please use helm chart instead",
|
||||
}
|
||||
|
||||
flag := cmd.Flags()
|
||||
@@ -282,7 +283,7 @@ func GetOAMReleaseVersion(ns string) (string, error) {
|
||||
return result.Chart.AppVersion(), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("oam-kubernetes-runtime not found in your kubernetes cluster, try `vela install` to install")
|
||||
return "", errors.New("kubevela chart not found in your kubernetes cluster, refer to 'https://kubevela.io/#/en/install' for installation")
|
||||
}
|
||||
|
||||
// PrintTrackVelaRuntimeStatus prints status of installing vela-core runtime
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common/rollout/workloads"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// the default time to check back if we still have work to do
|
||||
const rolloutReconcileRequeueTime = 5 * time.Second
|
||||
|
||||
// Controller is the controller that controls the rollout plan resource
|
||||
type Controller struct {
|
||||
client client.Client
|
||||
recorder event.Recorder
|
||||
parentController oam.Object
|
||||
|
||||
rolloutSpec *v1alpha1.RolloutPlan
|
||||
rolloutStatus v1alpha1.RolloutStatus
|
||||
|
||||
targetWorkload *unstructured.Unstructured
|
||||
sourceWorkload *unstructured.Unstructured
|
||||
}
|
||||
|
||||
// NewRolloutPlanController creates a RolloutPlanController
|
||||
func NewRolloutPlanController(client client.Client, parentController oam.Object, recorder event.Recorder,
|
||||
rolloutSpec *v1alpha1.RolloutPlan,
|
||||
rolloutStatus v1alpha1.RolloutStatus, targetWorkload,
|
||||
sourceWorkload *unstructured.Unstructured) *Controller {
|
||||
return &Controller{
|
||||
client: client,
|
||||
parentController: parentController,
|
||||
recorder: recorder,
|
||||
rolloutSpec: rolloutSpec,
|
||||
rolloutStatus: rolloutStatus,
|
||||
targetWorkload: targetWorkload,
|
||||
sourceWorkload: sourceWorkload,
|
||||
}
|
||||
}
|
||||
|
||||
// Reconcile reconciles a rollout plan
|
||||
func (r *Controller) Reconcile(ctx context.Context) (res reconcile.Result, status v1alpha1.RolloutStatus) {
|
||||
klog.InfoS("Reconcile the rollout plan", "rollout Spec", r.rolloutSpec,
|
||||
"target workload", klog.KObj(r.targetWorkload))
|
||||
if r.sourceWorkload != nil {
|
||||
klog.InfoS("we will do rolling upgrades", "source workload", klog.KObj(r.sourceWorkload))
|
||||
}
|
||||
klog.InfoS("rollout spec ", "rollout state", r.rolloutStatus.RollingState, "batch rolling state",
|
||||
r.rolloutStatus.BatchRollingState, "current batch", r.rolloutStatus.CurrentBatch, "upgraded Replicas",
|
||||
r.rolloutStatus.UpgradedReplicas)
|
||||
|
||||
defer klog.InfoS("Finished reconciling rollout plan", "rollout state", status.RollingState,
|
||||
"batch rolling state", status.BatchRollingState, "current batch", status.CurrentBatch,
|
||||
"upgraded Replicas", status.UpgradedReplicas, "reconcile result ", res)
|
||||
|
||||
status = r.rolloutStatus
|
||||
|
||||
defer func() {
|
||||
if status.RollingState == v1alpha1.RolloutFailedState ||
|
||||
status.RollingState == v1alpha1.RolloutSucceedState {
|
||||
// no need to requeue if we reach the terminal states
|
||||
res = reconcile.Result{}
|
||||
} else {
|
||||
res = reconcile.Result{
|
||||
RequeueAfter: rolloutReconcileRequeueTime,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
workloadController, err := r.GetWorkloadController()
|
||||
if err != nil {
|
||||
r.rolloutStatus.RolloutFailed(err.Error())
|
||||
r.recorder.Event(r.parentController, event.Warning("Unsupported workload", err))
|
||||
return
|
||||
}
|
||||
|
||||
switch r.rolloutStatus.RollingState {
|
||||
case v1alpha1.VerifyingState:
|
||||
status = *workloadController.Verify(ctx)
|
||||
|
||||
case v1alpha1.InitializingState:
|
||||
// TODO: call the pre-rollout webhooks
|
||||
status = *workloadController.Initialize(ctx)
|
||||
|
||||
case v1alpha1.RollingInBatchesState:
|
||||
status = r.reconcileBatchInRolling(ctx, workloadController)
|
||||
|
||||
case v1alpha1.FinalisingState:
|
||||
// TODO: call the post-rollout webhooks
|
||||
status = *workloadController.Finalize(ctx)
|
||||
|
||||
case v1alpha1.RolloutSucceedState:
|
||||
// Nothing to do
|
||||
|
||||
case v1alpha1.RolloutFailedState:
|
||||
// Nothing to do
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("illegal rollout status %+v", r.rolloutStatus))
|
||||
}
|
||||
|
||||
return res, status
|
||||
}
|
||||
|
||||
// reconcile logic when we are in the middle of rollout
|
||||
func (r *Controller) reconcileBatchInRolling(ctx context.Context, workloadController workloads.WorkloadController) (
|
||||
status v1alpha1.RolloutStatus) {
|
||||
|
||||
if r.rolloutSpec.Paused {
|
||||
r.recorder.Event(r.parentController, event.Normal("Rollout paused", "Rollout paused"))
|
||||
r.rolloutStatus.SetConditions(v1alpha1.NewPositiveCondition("Paused"))
|
||||
return r.rolloutStatus
|
||||
}
|
||||
|
||||
// makes sure that the current batch and replica count in the status are validate
|
||||
replicas, err := workloadController.Size(ctx)
|
||||
if err != nil {
|
||||
r.rolloutStatus.RolloutRetry(err.Error())
|
||||
return r.rolloutStatus
|
||||
}
|
||||
r.validateRollingBatchStatus(int(replicas))
|
||||
|
||||
switch r.rolloutStatus.BatchRollingState {
|
||||
case v1alpha1.BatchInitializingState:
|
||||
// TODO: call the pre-batch webhook
|
||||
|
||||
case v1alpha1.BatchInRollingState:
|
||||
// still rolling the batch, the batch rolling is not completed yet
|
||||
status = *workloadController.RolloutOneBatchPods(ctx)
|
||||
|
||||
case v1alpha1.BatchVerifyingState:
|
||||
// verifying if the application is ready to roll
|
||||
// need to check if they meet the availability requirements in the rollout spec.
|
||||
// TODO: evaluate any metrics/analysis
|
||||
status = *workloadController.CheckOneBatchPods(ctx)
|
||||
|
||||
case v1alpha1.BatchFinalizingState:
|
||||
// all the pods in the are available
|
||||
r.finalizeOneBatch()
|
||||
|
||||
case v1alpha1.BatchReadyState:
|
||||
// all the pods in the are upgraded and their state are ready
|
||||
// wait to move to the next batch if there are any
|
||||
r.tryMovingToNextBatch()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("illegal status %+v", r.rolloutStatus))
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// check if we can move to the next batch
|
||||
func (r *Controller) tryMovingToNextBatch() {
|
||||
if r.rolloutSpec.BatchPartition == nil || *r.rolloutSpec.BatchPartition > r.rolloutStatus.CurrentBatch {
|
||||
klog.InfoS("ready to rollout the next batch", "current batch", r.rolloutStatus.CurrentBatch)
|
||||
r.rolloutStatus.CurrentBatch++
|
||||
r.rolloutStatus.StateTransition(v1alpha1.BatchRolloutApprovedEvent)
|
||||
} else {
|
||||
klog.V(common.LogDebug).InfoS("the current batch is waiting to move on", "current batch",
|
||||
r.rolloutStatus.CurrentBatch)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Controller) finalizeOneBatch() {
|
||||
// TODO: call the post-batch webhooks if there are any
|
||||
currentBatch := int(r.rolloutStatus.CurrentBatch)
|
||||
if currentBatch == len(r.rolloutSpec.RolloutBatches)-1 {
|
||||
// this is the last batch, mark the rollout finalized
|
||||
r.rolloutStatus.StateTransition(v1alpha1.AllBatchFinishedEvent)
|
||||
r.recorder.Event(r.parentController, event.Normal("all batches rolled out",
|
||||
fmt.Sprintf("upgrade pod = %d, total ready pod = %d", r.rolloutStatus.UpgradedReplicas,
|
||||
r.rolloutStatus.UpgradedReadyReplicas)))
|
||||
} else {
|
||||
klog.InfoS("finished one batch rollout", "current batch", r.rolloutStatus.CurrentBatch)
|
||||
// th
|
||||
r.recorder.Event(r.parentController, event.Normal("Batch finalized",
|
||||
fmt.Sprintf("the batch num = %d is ready", r.rolloutStatus.CurrentBatch)))
|
||||
r.rolloutStatus.StateTransition(v1alpha1.FinishedOneBatchEvent)
|
||||
}
|
||||
}
|
||||
|
||||
// verify that the upgradedReplicas and current batch in the status are valid according to the spec
|
||||
func (r *Controller) validateRollingBatchStatus(totalSize int) bool {
|
||||
status := r.rolloutStatus
|
||||
spec := r.rolloutSpec
|
||||
podCount := 0
|
||||
if spec.BatchPartition != nil && *spec.BatchPartition < status.CurrentBatch {
|
||||
klog.ErrorS(fmt.Errorf("the current batch value in the status is greater than the batch partition"),
|
||||
"batch partition", *spec.BatchPartition, "current batch status", status.CurrentBatch)
|
||||
return false
|
||||
}
|
||||
upgradedReplicas := int(status.UpgradedReplicas)
|
||||
currentBatch := int(status.CurrentBatch)
|
||||
// calculate the lower bound of the possible pod count just before the current batch
|
||||
for i, r := range spec.RolloutBatches {
|
||||
if i < currentBatch {
|
||||
batchSize, _ := intstr.GetValueFromIntOrPercent(&r.Replicas, totalSize, true)
|
||||
podCount += batchSize
|
||||
}
|
||||
}
|
||||
// the recorded number should be at least as much as the all the pods before the current batch
|
||||
if podCount > upgradedReplicas {
|
||||
klog.ErrorS(fmt.Errorf("the upgraded replica in the status is too small"), "upgraded num status",
|
||||
upgradedReplicas, "pods in all the previous batches", podCount)
|
||||
return false
|
||||
}
|
||||
// calculate the upper bound with the current batch
|
||||
if currentBatch == len(spec.RolloutBatches)-1 {
|
||||
// avoid round up problems
|
||||
podCount = totalSize
|
||||
} else {
|
||||
batchSize, _ := intstr.GetValueFromIntOrPercent(&spec.RolloutBatches[currentBatch].Replicas,
|
||||
totalSize, true)
|
||||
podCount += batchSize
|
||||
}
|
||||
// the recorded number should be not as much as the all the pods including the active batch
|
||||
if podCount < upgradedReplicas {
|
||||
klog.ErrorS(fmt.Errorf("the upgraded replica in the status is too large"), "upgraded num status",
|
||||
upgradedReplicas, "pods in the batches including the current batch", podCount)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetWorkloadController pick the right workload controller to work on the workload
|
||||
func (r *Controller) GetWorkloadController() (workloads.WorkloadController, error) {
|
||||
kind := r.targetWorkload.GetObjectKind().GroupVersionKind().Kind
|
||||
target := types.NamespacedName{
|
||||
Namespace: r.targetWorkload.GetNamespace(),
|
||||
Name: r.targetWorkload.GetName(),
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case "CloneSet":
|
||||
return workloads.NewCloneSetController(r.client, r.recorder, r.parentController,
|
||||
r.rolloutSpec, &r.rolloutStatus, target), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("the workload kind `%s` is not supported", kind)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// ReconcileRolloutPlan generates the rollout plan and reconcile it
|
||||
func ReconcileRolloutPlan(ctx context.Context, client client.Client, rolloutSpec *v1alpha1.RolloutPlan,
|
||||
targetWorkload, sourceWorkload *unstructured.Unstructured) error {
|
||||
klog.InfoS("generate the rollout plan", "rollout Spec", rolloutSpec,
|
||||
"target workload", klog.KObj(targetWorkload))
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package workloads
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// CloneSetController is responsible for handle Cloneset type of workloads
|
||||
type CloneSetController struct {
|
||||
client client.Client
|
||||
recorder event.Recorder
|
||||
parentController oam.Object
|
||||
|
||||
rolloutSpec *v1alpha1.RolloutPlan
|
||||
rolloutStatus *v1alpha1.RolloutStatus
|
||||
workloadNamespacedName types.NamespacedName
|
||||
cloneSet *kruise.CloneSet
|
||||
}
|
||||
|
||||
// NewCloneSetController creates a new Cloneset controller
|
||||
func NewCloneSetController(client client.Client, recorder event.Recorder, parentController oam.Object,
|
||||
rolloutSpec *v1alpha1.RolloutPlan, rolloutStatus *v1alpha1.RolloutStatus, workloadName types.NamespacedName) *CloneSetController {
|
||||
return &CloneSetController{
|
||||
client: client,
|
||||
recorder: recorder,
|
||||
parentController: parentController,
|
||||
rolloutSpec: rolloutSpec,
|
||||
rolloutStatus: rolloutStatus,
|
||||
workloadNamespacedName: workloadName,
|
||||
}
|
||||
}
|
||||
|
||||
// Size fetches the Cloneset and returns the replicas (not the actual number of pods)
|
||||
func (c *CloneSetController) Size(ctx context.Context) (int32, error) {
|
||||
if c.cloneSet == nil {
|
||||
err := c.fetchCloneSet(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
// default is 1
|
||||
if c.cloneSet.Spec.Replicas == nil {
|
||||
return 1, nil
|
||||
}
|
||||
return *c.cloneSet.Spec.Replicas, nil
|
||||
}
|
||||
|
||||
// Verify verifies that the target rollout resource is consistent with the rollout spec
|
||||
func (c *CloneSetController) Verify(ctx context.Context) *v1alpha1.RolloutStatus {
|
||||
var verifyErr error
|
||||
defer func() {
|
||||
if verifyErr != nil {
|
||||
klog.Error(verifyErr)
|
||||
c.recorder.Event(c.parentController, event.Warning("VerifyFailed", verifyErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if verifyErr = c.fetchCloneSet(ctx); verifyErr != nil {
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// make sure that there are changes in the pod template
|
||||
targetHash := c.cloneSet.Status.UpdateRevision
|
||||
if targetHash == c.rolloutStatus.LastAppliedPodTemplateIdentifier {
|
||||
verifyErr = fmt.Errorf("there is no difference between the source and target, hash = %s", targetHash)
|
||||
c.rolloutStatus.RolloutFailed(verifyErr.Error())
|
||||
return c.rolloutStatus
|
||||
}
|
||||
// record the new pod template hash
|
||||
c.rolloutStatus.NewPodTemplateIdentifier = targetHash
|
||||
|
||||
// check if the rollout spec is compatible with the current state
|
||||
totalReplicas, _ := c.Size(ctx)
|
||||
|
||||
// check if the target spec is the same as the Cloneset replicas
|
||||
if verifyErr = c.verifyBatchSizes(totalReplicas); verifyErr != nil {
|
||||
c.rolloutStatus.RolloutFailed(verifyErr.Error())
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// the rollout batch partition is either automatic or zero
|
||||
if c.rolloutSpec.BatchPartition != nil && *c.rolloutSpec.BatchPartition != 0 {
|
||||
verifyErr = fmt.Errorf("the rollout plan has to start from zero, partition= %d", *c.rolloutSpec.BatchPartition)
|
||||
c.rolloutStatus.RolloutFailed(verifyErr.Error())
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// the number of old version in the Cloneset equals to the total number
|
||||
oldVersionPod, _ := intstr.GetValueFromIntOrPercent(c.cloneSet.Spec.UpdateStrategy.Partition, int(totalReplicas),
|
||||
true)
|
||||
if oldVersionPod != int(totalReplicas) {
|
||||
verifyErr = fmt.Errorf("the cloneset was still in the middle of updating, number of old pods= %d", oldVersionPod)
|
||||
c.rolloutStatus.RolloutFailed(verifyErr.Error())
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// mark the rollout verified
|
||||
c.recorder.Event(c.parentController, event.Normal("Verified",
|
||||
"Rollout spec and the CloneSet resource are verified"))
|
||||
c.rolloutStatus.StateTransition(v1alpha1.RollingSpecVerifiedEvent)
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// Initialize makes sure that
|
||||
func (c *CloneSetController) Initialize(ctx context.Context) *v1alpha1.RolloutStatus {
|
||||
if c.fetchCloneSet(ctx) != nil {
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// mark the rollout initialized, there is nothing we need to do for Cloneset for now
|
||||
c.recorder.Event(c.parentController, event.Normal("Initialized", "Rollout resource are initialized"))
|
||||
c.rolloutStatus.StateTransition(v1alpha1.RollingInitializedEvent)
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// RolloutOneBatchPods calculates the number of pods we can upgrade once according to the rollout spec
|
||||
// and then set the partition accordingly
|
||||
func (c *CloneSetController) RolloutOneBatchPods(ctx context.Context) *v1alpha1.RolloutStatus {
|
||||
// calculate what's the total pods that should be upgraded given the currentBatch in the status
|
||||
cloneSetSize, _ := c.Size(ctx)
|
||||
newPodTarget := c.calculateNewPodTarget(int(cloneSetSize))
|
||||
// set the Partition as the desired number of pods in old revisions.
|
||||
clonePatch := client.MergeFrom(c.cloneSet.DeepCopyObject())
|
||||
c.cloneSet.Spec.UpdateStrategy.Partition = &intstr.IntOrString{Type: intstr.Int,
|
||||
IntVal: cloneSetSize - int32(newPodTarget)}
|
||||
// patch the Cloneset
|
||||
if err := c.client.Patch(ctx, c.cloneSet, clonePatch, client.FieldOwner(c.parentController.GetUID())); err != nil {
|
||||
c.recorder.Event(c.parentController, event.Warning("Failed to patch update the Cloneset", err))
|
||||
c.rolloutStatus.RolloutRetry(err.Error())
|
||||
return c.rolloutStatus
|
||||
}
|
||||
// record the upgrade
|
||||
klog.InfoS("upgraded one batch", "current batch", c.rolloutStatus.CurrentBatch)
|
||||
c.recorder.Event(c.parentController, event.Normal("Rollout",
|
||||
fmt.Sprintf("upgraded the batch num = %d", c.rolloutStatus.CurrentBatch)))
|
||||
c.rolloutStatus.StateTransition(v1alpha1.BatchRolloutVerifyingEvent)
|
||||
c.rolloutStatus.UpgradedReplicas = int32(newPodTarget)
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// CheckOneBatchPods checks to see if the pods are all available according to
|
||||
func (c *CloneSetController) CheckOneBatchPods(ctx context.Context) *v1alpha1.RolloutStatus {
|
||||
cloneSetSize, _ := c.Size(ctx)
|
||||
newPodTarget := c.calculateNewPodTarget(int(cloneSetSize))
|
||||
// get the number of ready pod from cloneset
|
||||
readyPodCount := int(c.cloneSet.Status.UpdatedReadyReplicas)
|
||||
currentBatch := c.rolloutSpec.RolloutBatches[c.rolloutStatus.CurrentBatch]
|
||||
unavail := 0
|
||||
if currentBatch.MaxUnavailable != nil {
|
||||
unavail, _ = intstr.GetValueFromIntOrPercent(currentBatch.MaxUnavailable, int(cloneSetSize), true)
|
||||
}
|
||||
klog.V(common.LogDebug).InfoS("checking the rolling out progress", "current batch", currentBatch,
|
||||
"new pod count target", newPodTarget, "new ready pod count", readyPodCount,
|
||||
"max unavailable pod allowed", unavail)
|
||||
c.rolloutStatus.UpgradedReadyReplicas = int32(readyPodCount)
|
||||
if unavail+readyPodCount >= newPodTarget {
|
||||
// record the successful upgrade
|
||||
klog.InfoS("pods are ready", "current batch", currentBatch)
|
||||
c.recorder.Event(c.parentController, event.Normal("Batch Available",
|
||||
fmt.Sprintf("the batch num = %d is available", c.rolloutStatus.CurrentBatch)))
|
||||
c.rolloutStatus.StateTransition(v1alpha1.OneBatchAvailableEvent)
|
||||
} else {
|
||||
// continue to verify
|
||||
klog.V(common.LogDebug).InfoS("the batch is not ready yet", "current batch", currentBatch)
|
||||
c.rolloutStatus.StateTransition(v1alpha1.BatchRolloutVerifyingEvent)
|
||||
}
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// FinalizeOneBatch makes sure that the rollout status are updated correctly
|
||||
func (c *CloneSetController) FinalizeOneBatch(ctx context.Context) *v1alpha1.RolloutStatus {
|
||||
// nothing to do for now
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
// Finalize makes sure the Cloneset is all upgraded
|
||||
func (c *CloneSetController) Finalize(ctx context.Context) *v1alpha1.RolloutStatus {
|
||||
if c.fetchCloneSet(ctx) != nil {
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
c.rolloutStatus.StateTransition(v1alpha1.RollingFinalizedEvent)
|
||||
|
||||
return c.rolloutStatus
|
||||
}
|
||||
|
||||
/* --------------------
|
||||
The functions below are helper functions
|
||||
--------------------- */
|
||||
// check if the replicas in all the rollout batches add up to the right number
|
||||
func (c *CloneSetController) verifyBatchSizes(totalReplicas int32) error {
|
||||
// the target size has to be the same as the cloneset size
|
||||
if c.rolloutSpec.TargetSize != nil && *c.rolloutSpec.TargetSize != totalReplicas {
|
||||
return fmt.Errorf("the rollout plan is attempting to scale the cloneset, target = %d, cloneset size = %d",
|
||||
*c.rolloutSpec.TargetSize, totalReplicas)
|
||||
}
|
||||
// use a common function to check if the sum of all the batches can match the cloneset size
|
||||
err := VerifySumOfBatchSizes(c.rolloutSpec, totalReplicas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CloneSetController) fetchCloneSet(ctx context.Context) error {
|
||||
// get the cloneSet
|
||||
workload := kruise.CloneSet{}
|
||||
err := c.client.Get(ctx, c.workloadNamespacedName, &workload)
|
||||
if err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
c.recorder.Event(c.parentController, event.Warning("Failed to get the Cloneset", err))
|
||||
}
|
||||
c.rolloutStatus.RolloutRetry(err.Error())
|
||||
return err
|
||||
}
|
||||
c.cloneSet = &workload
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CloneSetController) calculateNewPodTarget(cloneSetSize int) int {
|
||||
currentBatch := int(c.rolloutStatus.CurrentBatch)
|
||||
newPodTarget := 0
|
||||
if currentBatch == len(c.rolloutSpec.RolloutBatches)-1 {
|
||||
// special handle the last batch, we ignore the rest of the batch in case there are rounding errors
|
||||
klog.InfoS("use the cloneset size as the total pod target for the last rolling batch",
|
||||
"current batch", currentBatch, "new version pod target", newPodTarget)
|
||||
newPodTarget = cloneSetSize
|
||||
} else {
|
||||
for i, r := range c.rolloutSpec.RolloutBatches {
|
||||
batchSize, _ := intstr.GetValueFromIntOrPercent(&r.Replicas, cloneSetSize, true)
|
||||
if i <= currentBatch {
|
||||
newPodTarget += batchSize
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
klog.InfoS("Calculated the number of new version pod", "current batch", currentBatch,
|
||||
"new version pod target", newPodTarget)
|
||||
}
|
||||
return newPodTarget
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package workloads
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// VerifySumOfBatchSizes verifies that the the sum of all the batch replicas is valid given the total replica
|
||||
// each batch replica can be absolute or a percentage
|
||||
func VerifySumOfBatchSizes(rolloutSpec *v1alpha1.RolloutPlan, totalReplicas int32) error {
|
||||
// if not set, the sum of all the batch sizes minus the last batch cannot be more than the totalReplicas
|
||||
// if not set, the sum of all the batch sizes minus the last batch cannot be more than the totalReplicas
|
||||
totalRollout := 0
|
||||
for i := 0; i < len(rolloutSpec.RolloutBatches)-1; i++ {
|
||||
rb := rolloutSpec.RolloutBatches[i]
|
||||
batchSize, _ := intstr.GetValueFromIntOrPercent(&rb.Replicas, int(totalReplicas), true)
|
||||
totalRollout += batchSize
|
||||
}
|
||||
if totalRollout >= int(totalReplicas) {
|
||||
return fmt.Errorf("the rollout plan batch size mismatch, total batch size = %d, totalReplicas size = %d",
|
||||
totalRollout, totalReplicas)
|
||||
}
|
||||
|
||||
// include the last batch if it has an int value
|
||||
// we ignore the last batch percentage since it is very likely to cause rounding errors
|
||||
lastBatch := rolloutSpec.RolloutBatches[len(rolloutSpec.RolloutBatches)-1]
|
||||
if lastBatch.Replicas.Type == intstr.Int {
|
||||
totalRollout += int(lastBatch.Replicas.IntVal)
|
||||
// now that they should be the same
|
||||
if totalRollout != int(totalReplicas) {
|
||||
return fmt.Errorf("the rollout plan batch size mismatch, total batch size = %d, totalReplicas size = %d",
|
||||
totalRollout, totalReplicas)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package workloads
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
)
|
||||
|
||||
// WorkloadController is the interface that all type of cloneSet controller implements
|
||||
type WorkloadController interface {
|
||||
// Size returns the total number of pods in the resources according to the spec
|
||||
Size(ctx context.Context) (int32, error)
|
||||
|
||||
// Verify makes sure that the resources can be upgraded according to the rollout plan
|
||||
// it returns new rollout status
|
||||
Verify(ctx context.Context) *v1alpha1.RolloutStatus
|
||||
|
||||
// Initialize make sure that the resource is ready to be upgraded.
|
||||
Initialize(ctx context.Context) *v1alpha1.RolloutStatus
|
||||
|
||||
// RolloutOneBatchPods tries to upgrade pods in the resources following the rollout plan
|
||||
// it will upgrade as many pods as the rollout plan allows at once, the routine does not block on any operations.
|
||||
// Instead, we rely on the go-client's requeue mechanism to drive this towards the spec goal
|
||||
// it returns the number of pods upgraded in this round
|
||||
RolloutOneBatchPods(ctx context.Context) *v1alpha1.RolloutStatus
|
||||
|
||||
// CheckOneBatchPods tries to upgrade pods in the resources following the rollout plan
|
||||
// it will upgrade as many pods as the rollout plan allows at once, the routine does not block on any operations.
|
||||
// Instead, we rely on the go-client's requeue mechanism to drive this towards the spec goal
|
||||
// it returns the number of pods upgraded in this round
|
||||
CheckOneBatchPods(ctx context.Context) *v1alpha1.RolloutStatus
|
||||
|
||||
// FinalizeOneBatch makes sure that the rollout can start the next batch
|
||||
// it also needs to handle the corner cases around the very last batch
|
||||
FinalizeOneBatch(ctx context.Context) *v1alpha1.RolloutStatus
|
||||
|
||||
// Finalize makes sure the resources are in a good final state.
|
||||
// For example, we may remove the source object to prevent scalar traits to ever work
|
||||
// and we will call the finalize rollout web hooks
|
||||
Finalize(ctx context.Context) *v1alpha1.RolloutStatus
|
||||
}
|
||||
@@ -16,15 +16,33 @@ limitations under the License.
|
||||
|
||||
package core_oam_dev
|
||||
|
||||
// ApplyOnceOnlyMode enumerates ApplyOnceOnly modes.
|
||||
type ApplyOnceOnlyMode string
|
||||
|
||||
const (
|
||||
// ApplyOnceOnlyOff indicates workloads and traits should always be affected.
|
||||
// It means ApplyOnceOnly is disabled.
|
||||
ApplyOnceOnlyOff ApplyOnceOnlyMode = "off"
|
||||
|
||||
// ApplyOnceOnlyOn indicates workloads and traits should not be affected
|
||||
// if no spec change is made in the ApplicationConfiguration.
|
||||
ApplyOnceOnlyOn = "on"
|
||||
|
||||
// ApplyOnceOnlyForce is a more strong case for ApplyOnceOnly, the workload
|
||||
// and traits won't be affected if no spec change is made in the ApplicationConfiguration,
|
||||
// even if the workload or trait has been deleted from cluster.
|
||||
ApplyOnceOnlyForce = "force"
|
||||
)
|
||||
|
||||
// Args args used by controller
|
||||
type Args struct {
|
||||
// RevisionLimit is the maximum number of revisions that will be maintained.
|
||||
// The default value is 50.
|
||||
RevisionLimit int
|
||||
|
||||
// ApplyOnceOnly indicates whether workloads and traits should be
|
||||
// ApplyMode indicates whether workloads and traits should be
|
||||
// affected if no spec change is made in the ApplicationConfiguration.
|
||||
ApplyOnceOnly bool
|
||||
ApplyMode ApplyOnceOnlyMode
|
||||
|
||||
// CustomRevisionHookURL is a webhook which will let oam-runtime to call with AC+Component info
|
||||
// The webhook server will return a customized component revision for oam-runtime
|
||||
|
||||
@@ -24,8 +24,11 @@ import (
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
@@ -76,13 +79,13 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
app.Status.Phase = v1alpha2.ApplicationRollingOut
|
||||
app.Status.SetConditions(readyCondition("Rolling"))
|
||||
// do not process apps still in rolling out
|
||||
return ctrl.Result{RequeueAfter: RolloutReconcileWaitTime}, r.Status().Update(ctx, app)
|
||||
return ctrl.Result{RequeueAfter: RolloutReconcileWaitTime}, r.UpdateStatus(ctx, app)
|
||||
}
|
||||
|
||||
applog.Info("Start Rendering")
|
||||
|
||||
app.Status.Phase = v1alpha2.ApplicationRendering
|
||||
handler := &reter{r.Client, app, applog}
|
||||
handler := &appHandler{r, app, applog}
|
||||
|
||||
app.Status.Conditions = []v1alpha1.Condition{}
|
||||
|
||||
@@ -109,9 +112,8 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
}
|
||||
|
||||
app.Status.SetConditions(readyCondition("Built"))
|
||||
|
||||
applog.Info("apply applicationconfig & component to the cluster")
|
||||
// apply applicationconfig & component to the cluster
|
||||
applog.Info("apply appConfig & component to the cluster")
|
||||
// apply appConfig & component to the cluster
|
||||
if err := handler.apply(ctx, ac, comps); err != nil {
|
||||
handler.l.Error(err, "[Handle apply]")
|
||||
app.Status.SetConditions(errorCondition("Applied", err))
|
||||
@@ -120,13 +122,22 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
|
||||
app.Status.SetConditions(readyCondition("Applied"))
|
||||
|
||||
app.Status.Phase = v1alpha2.ApplicationHealthChecking
|
||||
applog.Info("check application health status")
|
||||
// check application health status
|
||||
if err := handler.healthCheck(appfile); err != nil {
|
||||
appCompStatus, healthy, err := handler.statusAggregate(appfile)
|
||||
if err != nil {
|
||||
app.Status.SetConditions(errorCondition("HealthCheck", err))
|
||||
return handler.Err(err)
|
||||
}
|
||||
if !healthy {
|
||||
app.Status.SetConditions(errorCondition("HealthCheck", errors.New("not healthy")))
|
||||
|
||||
app.Status.Services = appCompStatus
|
||||
// unhealthy will check again after 10s
|
||||
return ctrl.Result{RequeueAfter: time.Second * 10}, r.Status().Update(ctx, app)
|
||||
}
|
||||
app.Status.Services = appCompStatus
|
||||
app.Status.SetConditions(readyCondition("HealthCheck"))
|
||||
app.Status.Phase = v1alpha2.ApplicationRunning
|
||||
// Gather status of components
|
||||
@@ -140,7 +151,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
})
|
||||
}
|
||||
app.Status.Components = refComps
|
||||
return ctrl.Result{}, r.Status().Update(ctx, app)
|
||||
return ctrl.Result{}, r.UpdateStatus(ctx, app)
|
||||
}
|
||||
|
||||
// SetupWithManager install to manager
|
||||
@@ -151,6 +162,18 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// UpdateStatus updates v1alpha2.Application's Status with retry.RetryOnConflict
|
||||
func (r *Reconciler) UpdateStatus(ctx context.Context, app *v1alpha2.Application, opts ...client.UpdateOption) error {
|
||||
status := app.DeepCopy().Status
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
if err = r.Get(ctx, types.NamespacedName{Namespace: app.Namespace, Name: app.Name}, app); err != nil {
|
||||
return
|
||||
}
|
||||
app.Status = status
|
||||
return r.Status().Update(ctx, app, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
// Setup adds a controller that reconciles ApplicationDeployment.
|
||||
func Setup(mgr ctrl.Manager, _ core.Args, _ logging.Logger) error {
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
|
||||
@@ -25,12 +25,11 @@ import (
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -87,7 +86,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
},
|
||||
}
|
||||
|
||||
var getExpDeployment = func(compName string) *v1.Deployment {
|
||||
var getExpDeployment = func(compName, appName string) *v1.Deployment {
|
||||
return &v1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
@@ -96,6 +95,8 @@ var _ = Describe("Test Application Controller", func() {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"workload.oam.dev/type": "worker",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
},
|
||||
},
|
||||
Spec: v1.DeploymentSpec{
|
||||
@@ -125,18 +126,23 @@ var _ = Describe("Test Application Controller", func() {
|
||||
},
|
||||
}
|
||||
appWithTrait.Spec.Components[0].Name = "myweb3"
|
||||
expectScalerTrait := unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "scaler",
|
||||
expectScalerTrait := func(compName, appName string) unstructured.Unstructured {
|
||||
return unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": appName,
|
||||
},
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicaCount": int64(2),
|
||||
},
|
||||
}}
|
||||
"spec": map[string]interface{}{
|
||||
"replicaCount": int64(2),
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
appWithTraitAndScope := appWithTrait.DeepCopy()
|
||||
appWithTraitAndScope.SetName("app-with-trait-and-scope")
|
||||
appWithTraitAndScope.Spec.Components[0].Scopes = map[string]string{"healthscopes.core.oam.dev": "appWithTraitAndScope-default-health"}
|
||||
@@ -189,7 +195,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
})
|
||||
|
||||
It("app-without-trait will only create workload", func() {
|
||||
expDeployment := getExpDeployment("myweb2")
|
||||
expDeployment := getExpDeployment("myweb2", appwithNoTrait.Name)
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test",
|
||||
@@ -230,13 +236,14 @@ var _ = Describe("Test Application Controller", func() {
|
||||
gotD := &v1.Deployment{}
|
||||
|
||||
Expect(json.Unmarshal(component.Spec.Workload.Raw, gotD)).Should(BeNil())
|
||||
Expect(gotD).Should(BeEquivalentTo(expDeployment))
|
||||
fmt.Println(cmp.Diff(expDeployment, gotD))
|
||||
Expect(assert.ObjectsAreEqual(expDeployment, gotD)).Should(BeEquivalentTo(true))
|
||||
By("Delete Application, clean the resource")
|
||||
Expect(k8sClient.Delete(ctx, appwithNoTrait)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("app-with-config will create workload with config data", func() {
|
||||
expConfigDeployment := getExpDeployment("myweb1")
|
||||
expConfigDeployment := getExpDeployment("myweb1", appwithConfig.Name)
|
||||
expConfigDeployment.SetAnnotations(map[string]string{"c1": "v1", "c2": "v2"})
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -283,7 +290,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
})
|
||||
|
||||
It("app-with-trait will create workload and trait", func() {
|
||||
expDeployment := getExpDeployment("myweb3")
|
||||
expDeployment := getExpDeployment("myweb3", appWithTrait.Name)
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test-with-trait",
|
||||
@@ -314,7 +321,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
|
||||
gotTrait := unstructured.Unstructured{}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait("myweb3", app.Name)))
|
||||
|
||||
By("Check component created as expected")
|
||||
component := &v1alpha2.Component{}
|
||||
@@ -336,13 +343,14 @@ var _ = Describe("Test Application Controller", func() {
|
||||
|
||||
It("app-with-composedworkload-trait will create workload and trait", func() {
|
||||
compName := "myweb-composed-3"
|
||||
expDeployment := getExpDeployment(compName)
|
||||
var appname = "app-with-composedworkload-trait"
|
||||
expDeployment := getExpDeployment(compName, appname)
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test-with-composedworkload-trait",
|
||||
},
|
||||
}
|
||||
var appname = "app-with-composedworkload-trait"
|
||||
|
||||
appWithComposedWorkload := appwithNoTrait.DeepCopy()
|
||||
appWithComposedWorkload.Spec.Components[0].WorkloadType = "webserver"
|
||||
appWithComposedWorkload.SetName(appname)
|
||||
@@ -384,7 +392,12 @@ var _ = Describe("Test Application Controller", func() {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{"trait.oam.dev/type": "AuxiliaryWorkload"},
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "AuxiliaryWorkload",
|
||||
"app.oam.dev/name": "app-with-composedworkload-trait",
|
||||
"app.oam.dev/component": "myweb-composed-3",
|
||||
"trait.oam.dev/resource": "service",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"ports": []interface{}{
|
||||
@@ -402,7 +415,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
By("Check the second trait should be scaler")
|
||||
gotTrait = unstructured.Unstructured{}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[1].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait("myweb-composed-3", app.Name)))
|
||||
|
||||
By("Check component created as expected")
|
||||
component := &v1alpha2.Component{}
|
||||
@@ -426,7 +439,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
})
|
||||
|
||||
It("app-with-trait-and-scope will create workload, trait and scope", func() {
|
||||
expDeployment := getExpDeployment("myweb4")
|
||||
expDeployment := getExpDeployment("myweb4", appWithTraitAndScope.Name)
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test-with-trait-scope",
|
||||
@@ -457,7 +470,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
|
||||
gotTrait := unstructured.Unstructured{}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait("myweb4", app.Name)))
|
||||
|
||||
Expect(appConfig.Spec.Components[0].Scopes[0].ScopeReference).Should(BeEquivalentTo(v1alpha1.TypedReference{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
@@ -484,7 +497,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
})
|
||||
|
||||
It("app with two components and update", func() {
|
||||
expDeployment := getExpDeployment("myweb5")
|
||||
expDeployment := getExpDeployment("myweb5", appWithTwoComp.Name)
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-two-comps",
|
||||
@@ -519,7 +532,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
|
||||
gotTrait := unstructured.Unstructured{}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait("myweb5", app.Name)))
|
||||
|
||||
Expect(appConfig.Spec.Components[0].Scopes[0].ScopeReference).Should(BeEquivalentTo(v1alpha1.TypedReference{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
@@ -543,7 +556,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Expect(json.Unmarshal(component5.Spec.Workload.Raw, gotD)).Should(BeNil())
|
||||
Expect(gotD).Should(BeEquivalentTo(expDeployment))
|
||||
|
||||
expDeployment6 := getExpDeployment("myweb6")
|
||||
expDeployment6 := getExpDeployment("myweb6", app.Name)
|
||||
expDeployment6.SetAnnotations(map[string]string{"c1": "v1", "c2": "v2"})
|
||||
expDeployment6.Spec.Template.Spec.Containers[0].Image = "busybox2"
|
||||
component6 := &v1alpha2.Component{}
|
||||
@@ -586,7 +599,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
}, appConfig)).Should(BeNil())
|
||||
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait("myweb5", app.Name)))
|
||||
|
||||
Expect(appConfig.Spec.Components[0].Scopes[0].ScopeReference).Should(BeEquivalentTo(v1alpha1.TypedReference{
|
||||
APIVersion: "core.oam.dev/v1alpha2",
|
||||
@@ -608,7 +621,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
expDeployment.Spec.Template.Spec.Containers[0].Image = "busybox3"
|
||||
Expect(gotD).Should(BeEquivalentTo(expDeployment))
|
||||
|
||||
expDeployment7 := getExpDeployment("myweb7")
|
||||
expDeployment7 := getExpDeployment("myweb7", app.Name)
|
||||
component7 := &v1alpha2.Component{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{
|
||||
Namespace: app.Namespace,
|
||||
@@ -631,7 +644,8 @@ var _ = Describe("Test Application Controller", func() {
|
||||
It("app-with-trait will create workload and trait with http task", func() {
|
||||
s := NewMock()
|
||||
defer s.Close()
|
||||
expectScalerTrait.Object["spec"].(map[string]interface{})["token"] = "test-token"
|
||||
expTrait := expectScalerTrait(appWithTrait.Spec.Components[0].Name, appWithTrait.Name)
|
||||
expTrait.Object["spec"].(map[string]interface{})["token"] = "test-token"
|
||||
|
||||
By("change trait definition with http task")
|
||||
ntd, otd := &v1alpha2.TraitDefinition{}, &v1alpha2.TraitDefinition{}
|
||||
@@ -671,7 +685,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
|
||||
gotTrait := unstructured.Unstructured{}
|
||||
Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotTrait)).Should(BeNil())
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expectScalerTrait))
|
||||
Expect(gotTrait).Should(BeEquivalentTo(expTrait))
|
||||
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
@@ -690,8 +704,10 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "scaler"}, otd)).Should(BeNil())
|
||||
ntd.ResourceVersion = otd.ResourceVersion
|
||||
Expect(k8sClient.Update(ctx, ntd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
compName := "myweb-health"
|
||||
expDeployment := getExpDeployment(compName, appWithTrait.Name)
|
||||
|
||||
expDeployment := getExpDeployment("myweb6")
|
||||
By("create the new namespace")
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test-with-health",
|
||||
@@ -699,23 +715,29 @@ var _ = Describe("Test Application Controller", func() {
|
||||
}
|
||||
appWithTrait.SetNamespace(ns.Name)
|
||||
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
|
||||
|
||||
app := appWithTrait.DeepCopy()
|
||||
app.Spec.Components[0].Name = compName
|
||||
expDeployment.Name = app.Name
|
||||
expDeployment.Namespace = ns.Name
|
||||
expDeployment.Labels[oam.LabelAppName] = app.Name
|
||||
expDeployment.Labels[oam.LabelAppComponent] = compName
|
||||
expDeployment.Labels["app.oam.dev/resourceType"] = "WORKLOAD"
|
||||
Expect(k8sClient.Create(ctx, expDeployment)).Should(BeNil())
|
||||
expectScalerTrait.SetName(app.Name)
|
||||
expectScalerTrait.SetNamespace(app.Namespace)
|
||||
expectScalerTrait.SetLabels(map[string]string{
|
||||
oam.LabelAppName: app.Name,
|
||||
"trait.oam.dev/type": "scaler",
|
||||
expTrait := expectScalerTrait(compName, app.Name)
|
||||
expTrait.SetName(app.Name)
|
||||
expTrait.SetNamespace(app.Namespace)
|
||||
expTrait.SetLabels(map[string]string{
|
||||
oam.LabelAppName: app.Name,
|
||||
"trait.oam.dev/type": "scaler",
|
||||
"app.oam.dev/component": "myweb-health",
|
||||
})
|
||||
(expectScalerTrait.Object["spec"].(map[string]interface{}))["workloadRef"] = map[string]interface{}{
|
||||
(expTrait.Object["spec"].(map[string]interface{}))["workloadRef"] = map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"name": app.Name,
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, &expectScalerTrait)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &expTrait)).Should(BeNil())
|
||||
|
||||
By("enrich the status of deployment and scaler trait")
|
||||
expDeployment.Status.Replicas = 1
|
||||
@@ -726,13 +748,13 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Namespace: app.Namespace,
|
||||
Name: app.Name,
|
||||
}, got)).Should(BeNil())
|
||||
expectScalerTrait.Object["status"] = v1alpha1.ConditionedStatus{
|
||||
expTrait.Object["status"] = v1alpha1.ConditionedStatus{
|
||||
Conditions: []v1alpha1.Condition{{
|
||||
Status: corev1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
}},
|
||||
}
|
||||
Expect(k8sClient.Status().Update(ctx, &expectScalerTrait)).Should(BeNil())
|
||||
Expect(k8sClient.Status().Update(ctx, &expTrait)).Should(BeNil())
|
||||
tGot := &unstructured.Unstructured{}
|
||||
tGot.SetAPIVersion("core.oam.dev/v1alpha2")
|
||||
tGot.SetKind("ManualScalerTrait")
|
||||
@@ -750,15 +772,28 @@ var _ = Describe("Test Application Controller", func() {
|
||||
reconcileRetry(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
|
||||
By("Check App running successfully")
|
||||
checkApp := &v1alpha2.Application{}
|
||||
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Phase).Should(Equal(v1alpha2.ApplicationRunning))
|
||||
|
||||
Eventually(func() string {
|
||||
_, err := reconciler.Reconcile(reconcile.Request{NamespacedName: appKey})
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
checkApp := &v1alpha2.Application{}
|
||||
err = k8sClient.Get(ctx, appKey, checkApp)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if checkApp.Status.Phase != v1alpha2.ApplicationRunning {
|
||||
fmt.Println(checkApp.Status.Conditions)
|
||||
}
|
||||
return string(checkApp.Status.Phase)
|
||||
}(), 5*time.Second, time.Second).Should(BeEquivalentTo(v1alpha2.ApplicationRunning))
|
||||
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("app with rolling out annotation", func() {
|
||||
By("crreat application with rolling out annotation")
|
||||
By("create application with rolling out annotation")
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-test-with-rollout",
|
||||
@@ -787,11 +822,170 @@ var _ = Describe("Test Application Controller", func() {
|
||||
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("app with health policy and custom status for workload", func() {
|
||||
By("change workload and trait definition with health policy")
|
||||
nwd := &v1alpha2.WorkloadDefinition{}
|
||||
wDDefJson, _ := yaml.YAMLToJSON([]byte(wdDefWithHealthStatusYaml))
|
||||
Expect(json.Unmarshal(wDDefJson, nwd)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, nwd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
ntd := &v1alpha2.TraitDefinition{}
|
||||
tDDefJson, _ := yaml.YAMLToJSON([]byte(tDDefWithHealthStatusYaml))
|
||||
Expect(json.Unmarshal(tDDefJson, ntd)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, ntd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
compName := "myweb-health-status"
|
||||
appWithTraitHealthStatus := appWithTrait.DeepCopy()
|
||||
appWithTraitHealthStatus.Name = "app-trait-health-status"
|
||||
expDeployment := getExpDeployment(compName, appWithTraitHealthStatus.Name)
|
||||
|
||||
By("create the new namespace")
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-test-with-health-status",
|
||||
},
|
||||
}
|
||||
appWithTraitHealthStatus.SetNamespace(ns.Name)
|
||||
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
|
||||
|
||||
app := appWithTraitHealthStatus.DeepCopy()
|
||||
app.Spec.Components[0].Name = compName
|
||||
app.Spec.Components[0].WorkloadType = "nworker"
|
||||
app.Spec.Components[0].Settings = runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox3","lives":"3","enemies":"alain"}`)}
|
||||
app.Spec.Components[0].Traits[0].Name = "ingress"
|
||||
app.Spec.Components[0].Traits[0].Properties = runtime.RawExtension{Raw: []byte(`{"domain":"example.com","http":{"/":80}}`)}
|
||||
|
||||
expDeployment.Name = app.Name
|
||||
expDeployment.Namespace = ns.Name
|
||||
expDeployment.Labels[oam.LabelAppName] = app.Name
|
||||
expDeployment.Labels[oam.LabelAppComponent] = compName
|
||||
expDeployment.Labels["app.oam.dev/resourceType"] = "WORKLOAD"
|
||||
Expect(k8sClient.Create(ctx, expDeployment)).Should(BeNil())
|
||||
|
||||
expWorkloadTrait := unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "AuxiliaryWorkload",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": app.Name,
|
||||
"trait.oam.dev/resource": "gameconfig",
|
||||
},
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"enemies": "alien",
|
||||
"lives": "3",
|
||||
},
|
||||
}}
|
||||
expWorkloadTrait.SetName("myweb-health-statusgame-config")
|
||||
expWorkloadTrait.SetNamespace(app.Namespace)
|
||||
Expect(k8sClient.Create(ctx, &expWorkloadTrait)).Should(BeNil())
|
||||
|
||||
expTrait := unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "networking.k8s.io/v1beta1",
|
||||
"kind": "Ingress",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "ingress",
|
||||
"trait.oam.dev/resource": "ingress",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": app.Name,
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"rules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"host": "example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
expTrait.SetName(compName)
|
||||
expTrait.SetNamespace(app.Namespace)
|
||||
Expect(k8sClient.Create(ctx, &expTrait)).Should(BeNil())
|
||||
|
||||
expTrait2 := unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/type": "ingress",
|
||||
"trait.oam.dev/resource": "service",
|
||||
"app.oam.dev/component": compName,
|
||||
"app.oam.dev/name": app.Name,
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"clusterIP": "10.0.0.4",
|
||||
"ports": []interface{}{
|
||||
map[string]interface{}{
|
||||
"port": 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
expTrait2.SetName(app.Name)
|
||||
expTrait2.SetNamespace(app.Namespace)
|
||||
Expect(k8sClient.Create(ctx, &expTrait2)).Should(BeNil())
|
||||
|
||||
By("enrich the status of deployment and ingress trait")
|
||||
expDeployment.Status.Replicas = 1
|
||||
expDeployment.Status.ReadyReplicas = 1
|
||||
Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil())
|
||||
got := &v1.Deployment{}
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{
|
||||
Namespace: app.Namespace,
|
||||
Name: app.Name,
|
||||
}, got)).Should(BeNil())
|
||||
|
||||
By("apply appfile")
|
||||
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
|
||||
appKey := client.ObjectKey{
|
||||
Name: app.Name,
|
||||
Namespace: app.Namespace,
|
||||
}
|
||||
reconcileRetry(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
|
||||
By("Check App running successfully")
|
||||
checkApp := &v1alpha2.Application{}
|
||||
Eventually(func() string {
|
||||
_, err := reconciler.Reconcile(reconcile.Request{NamespacedName: appKey})
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
err = k8sClient.Get(ctx, appKey, checkApp)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if checkApp.Status.Phase != v1alpha2.ApplicationRunning {
|
||||
fmt.Println(checkApp.Status.Conditions)
|
||||
}
|
||||
return string(checkApp.Status.Phase)
|
||||
}(), 5*time.Second, time.Second).Should(BeEquivalentTo(v1alpha2.ApplicationRunning))
|
||||
Expect(checkApp.Status.Services).Should(BeEquivalentTo([]v1alpha2.ApplicationComponentStatus{
|
||||
{
|
||||
Name: compName,
|
||||
Healthy: true,
|
||||
Message: "type: busybox,\t enemies:alien",
|
||||
Traits: []v1alpha2.ApplicationTraitStatus{
|
||||
{
|
||||
Type: "ingress",
|
||||
Healthy: true,
|
||||
Message: "type: ClusterIP,\t clusterIP:10.0.0.4,\t ports:80,\t domainexample.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
func reconcileRetry(r reconcile.Reconciler, req reconcile.Request) {
|
||||
Eventually(func() error {
|
||||
_, err := r.Reconcile(req)
|
||||
if err != nil {
|
||||
fmt.Println("reconcile err: ", err)
|
||||
}
|
||||
return err
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
}
|
||||
@@ -970,7 +1164,7 @@ spec:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
healthPolicy: |
|
||||
isHealth: output.status.readyReplicas == output.status.replicas
|
||||
isHealth: context.output.status.readyReplicas == context.output.status.replicas
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
@@ -1018,6 +1212,72 @@ spec:
|
||||
|
||||
cmd?: [...string]
|
||||
}
|
||||
`
|
||||
wdDefWithHealthStatusYaml = `apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: nworker
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
status:
|
||||
healthPolicy: |
|
||||
isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)
|
||||
customStatus: |-
|
||||
message: "type: " + context.output.spec.template.spec.containers[0].image + ",\t enemies:" + context.outputs.gameconfig.data.enemies
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
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
|
||||
envFrom: [{
|
||||
configMapRef: name: context.name + "game-config"
|
||||
}]
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputs: gameconfig: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name + "game-config"
|
||||
}
|
||||
data: {
|
||||
enemies: parameter.enemies
|
||||
lives: parameter.lives
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
lives: string
|
||||
enemies: string
|
||||
}
|
||||
`
|
||||
tDDefYaml = `
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
@@ -1108,7 +1368,7 @@ spec:
|
||||
workloadRefPath: spec.workloadRef
|
||||
extension:
|
||||
healthPolicy: |
|
||||
isHealth: output.status.conditions[0].status == "True"
|
||||
isHealth: context.output.status.conditions[0].status == "True"
|
||||
template: |-
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
@@ -1122,6 +1382,60 @@ spec:
|
||||
replicas: *1 | int
|
||||
}
|
||||
`
|
||||
|
||||
tDDefWithHealthStatusYaml = `apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
name: ingress
|
||||
spec:
|
||||
status:
|
||||
customStatus: |-
|
||||
message: "type: "+ context.outputs.service.spec.type +",\t clusterIP:"+ context.outputs.service.spec.clusterIP+",\t ports:"+ "\(context.outputs.service.spec.ports[0].port)"+",\t domain"+context.outputs.ingress.spec.rules[0].host
|
||||
healthPolicy: |
|
||||
isHealth: len(context.outputs.service.spec.clusterIP) > 0
|
||||
template: |
|
||||
parameter: {
|
||||
domain: string
|
||||
http: [string]: int
|
||||
}
|
||||
// trait template can have multiple outputs in one trait
|
||||
outputs: service: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
spec: {
|
||||
selector:
|
||||
app: context.name
|
||||
ports: [
|
||||
for k, v in parameter.http {
|
||||
port: v
|
||||
targetPort: v
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
outputs: ingress: {
|
||||
apiVersion: "networking.k8s.io/v1beta1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: context.name
|
||||
spec: {
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: {
|
||||
paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
backend: {
|
||||
serviceName: context.name
|
||||
servicePort: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func NewMock() *httptest.Server {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -38,14 +39,14 @@ func readyCondition(tpy string) runtimev1alpha1.Condition {
|
||||
}
|
||||
}
|
||||
|
||||
type reter struct {
|
||||
c client.Client
|
||||
type appHandler struct {
|
||||
r *Reconciler
|
||||
app *v1alpha2.Application
|
||||
l logr.Logger
|
||||
}
|
||||
|
||||
func (ret *reter) Err(err error) (ctrl.Result, error) {
|
||||
nerr := ret.c.Status().Update(context.Background(), ret.app)
|
||||
func (ret *appHandler) Err(err error) (ctrl.Result, error) {
|
||||
nerr := ret.r.UpdateStatus(context.Background(), ret.app)
|
||||
if err == nil && nerr == nil {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
@@ -57,8 +58,8 @@ func (ret *reter) Err(err error) (ctrl.Result, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ret *reter) apply(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, comps []*v1alpha2.Component) error {
|
||||
// set ownerReference for ApplicationConfiguration and Components created by Application
|
||||
// apply will set ownerReference for ApplicationConfiguration and Components created by Application
|
||||
func (ret *appHandler) apply(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, comps []*v1alpha2.Component) error {
|
||||
owners := []metav1.OwnerReference{{
|
||||
APIVersion: v1alpha2.SchemeGroupVersion.String(),
|
||||
Kind: v1alpha2.ApplicationKind,
|
||||
@@ -73,27 +74,62 @@ func (ret *reter) apply(ctx context.Context, ac *v1alpha2.ApplicationConfigurati
|
||||
return ret.Sync(ctx, ac, comps)
|
||||
}
|
||||
|
||||
func (ret *reter) healthCheck(appfile *appfile.Appfile) error {
|
||||
func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.ApplicationComponentStatus, bool, error) {
|
||||
var appStatus []v1alpha2.ApplicationComponentStatus
|
||||
var healthy = true
|
||||
for _, wl := range appfile.Workloads {
|
||||
pCtx := process.NewContext(wl.Name)
|
||||
var status = v1alpha2.ApplicationComponentStatus{
|
||||
Name: wl.Name,
|
||||
Healthy: true,
|
||||
}
|
||||
pCtx := process.NewContext(wl.Name, appfile.Name)
|
||||
if err := wl.EvalContext(pCtx); err != nil {
|
||||
return err
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate context error", appfile.Name, wl.Name)
|
||||
}
|
||||
for _, tr := range wl.Traits {
|
||||
if err := tr.EvalContext(pCtx); err != nil {
|
||||
return err
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate context error", appfile.Name, wl.Name, tr.Name)
|
||||
}
|
||||
}
|
||||
if err := wl.EvalHealth(pCtx, ret.c, appfile.Name); err != nil {
|
||||
return err
|
||||
|
||||
workloadHealth, err := wl.EvalHealth(pCtx, ret.r, ret.app.Namespace)
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appfile.Name, wl.Name)
|
||||
}
|
||||
if !workloadHealth {
|
||||
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
|
||||
status.Healthy = false
|
||||
healthy = false
|
||||
}
|
||||
status.Message, err = wl.EvalStatus(pCtx, ret.r, ret.app.Namespace)
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate workload status message error", appfile.Name, wl.Name)
|
||||
}
|
||||
var traitStatusList []v1alpha2.ApplicationTraitStatus
|
||||
for _, trait := range wl.Traits {
|
||||
if err := trait.EvalHealth(pCtx, ret.c, appfile.Name); err != nil {
|
||||
return err
|
||||
var traitStatus = v1alpha2.ApplicationTraitStatus{
|
||||
Type: trait.Name,
|
||||
Healthy: true,
|
||||
}
|
||||
traitHealth, err := trait.EvalHealth(pCtx, ret.r, ret.app.Namespace)
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, check health error", appfile.Name, wl.Name, trait.Name)
|
||||
}
|
||||
if !traitHealth {
|
||||
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
|
||||
traitStatus.Healthy = false
|
||||
healthy = false
|
||||
}
|
||||
traitStatus.Message, err = trait.EvalStatus(pCtx, ret.r, ret.app.Namespace)
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate status message error", appfile.Name, wl.Name, trait.Name)
|
||||
}
|
||||
traitStatusList = append(traitStatusList, traitStatus)
|
||||
}
|
||||
status.Traits = traitStatusList
|
||||
appStatus = append(appStatus, status)
|
||||
}
|
||||
return nil
|
||||
return appStatus, healthy, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateComponent will create if not exist and update if exists.
|
||||
@@ -129,14 +165,14 @@ func CreateOrUpdateAppConfig(ctx context.Context, client client.Client, appConfi
|
||||
}
|
||||
|
||||
// Sync perform synchronization operations
|
||||
func (ret *reter) Sync(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, comps []*v1alpha2.Component) error {
|
||||
func (ret *appHandler) Sync(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, comps []*v1alpha2.Component) error {
|
||||
for _, comp := range comps {
|
||||
if err := CreateOrUpdateComponent(ctx, ret.c, comp.DeepCopy()); err != nil {
|
||||
if err := CreateOrUpdateComponent(ctx, ret.r, comp.DeepCopy()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := CreateOrUpdateAppConfig(ctx, ret.c, ac); err != nil {
|
||||
if err := CreateOrUpdateAppConfig(ctx, ret.r, ac); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -155,7 +191,7 @@ func (ret *reter) Sync(ctx context.Context, ac *v1alpha2.ApplicationConfiguratio
|
||||
}
|
||||
// Component not exits in current Application, should be deleted
|
||||
var oldC = &v1alpha2.Component{ObjectMeta: metav1.ObjectMeta{Name: comp.Name, Namespace: ac.Namespace}}
|
||||
if err := ret.c.Delete(ctx, oldC); err != nil {
|
||||
if err := ret.r.Delete(ctx, oldC); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
+119
-55
@@ -27,6 +27,8 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
@@ -102,22 +104,22 @@ func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error {
|
||||
Complete(NewReconciler(mgr, dm,
|
||||
WithLogger(l.WithValues("controller", name)),
|
||||
WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
|
||||
WithApplyOnceOnly(args.ApplyOnceOnly)))
|
||||
WithApplyOnceOnlyMode(args.ApplyMode)))
|
||||
}
|
||||
|
||||
// An OAMApplicationReconciler reconciles OAM ApplicationConfigurations by rendering and
|
||||
// instantiating their Components and Traits.
|
||||
type OAMApplicationReconciler struct {
|
||||
client client.Client
|
||||
components ComponentRenderer
|
||||
workloads WorkloadApplicator
|
||||
gc GarbageCollector
|
||||
scheme *runtime.Scheme
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
preHooks map[string]ControllerHooks
|
||||
postHooks map[string]ControllerHooks
|
||||
applyOnceOnly bool
|
||||
client client.Client
|
||||
components ComponentRenderer
|
||||
workloads WorkloadApplicator
|
||||
gc GarbageCollector
|
||||
scheme *runtime.Scheme
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
preHooks map[string]ControllerHooks
|
||||
postHooks map[string]ControllerHooks
|
||||
applyOnceOnlyMode core.ApplyOnceOnlyMode
|
||||
}
|
||||
|
||||
// A ReconcilerOption configures a Reconciler.
|
||||
@@ -174,11 +176,11 @@ func WithPosthook(name string, hook ControllerHooks) ReconcilerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithApplyOnceOnly indicates whether workloads and traits should be
|
||||
// WithApplyOnceOnlyMode indicates whether workloads and traits should be
|
||||
// affected if no spec change is made in the ApplicationConfiguration.
|
||||
func WithApplyOnceOnly(applyOnceOnly bool) ReconcilerOption {
|
||||
func WithApplyOnceOnlyMode(mode core.ApplyOnceOnlyMode) ReconcilerOption {
|
||||
return func(r *OAMApplicationReconciler) {
|
||||
r.applyOnceOnly = applyOnceOnly
|
||||
r.applyOnceOnlyMode = mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,11 +202,12 @@ func NewReconciler(m ctrl.Manager, dm discoverymapper.DiscoveryMapper, o ...Reco
|
||||
rawClient: m.GetClient(),
|
||||
dm: dm,
|
||||
},
|
||||
gc: GarbageCollectorFn(eligible),
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
preHooks: make(map[string]ControllerHooks),
|
||||
postHooks: make(map[string]ControllerHooks),
|
||||
gc: GarbageCollectorFn(eligible),
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
preHooks: make(map[string]ControllerHooks),
|
||||
postHooks: make(map[string]ControllerHooks),
|
||||
applyOnceOnlyMode: core.ApplyOnceOnlyOff,
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
@@ -244,7 +247,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
"error", err, "requeue-after", result.RequeueAfter)
|
||||
r.record.Event(ac, event.Warning(reasonCannotFinalizeWorkloads, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errFinalizeWorkloads)))
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
return reconcile.Result{}, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
@@ -259,12 +262,12 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
r.record.Event(ac, event.Warning(reasonCannotExecutePosthooks, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errExecutePosthooks)))
|
||||
result = exeResult
|
||||
returnErr = errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
returnErr = errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
return
|
||||
}
|
||||
r.record.Event(ac, event.Normal(reasonExecutePosthook, "Successfully executed a posthook", "posthook name", name))
|
||||
}
|
||||
returnErr = errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
returnErr = errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
|
||||
// Make sure if error occurs, reconcile will not happen too frequency
|
||||
if returnErr != nil && result.RequeueAfter < shortWait {
|
||||
@@ -279,7 +282,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
log.Debug("Failed to execute pre-hooks", "hook name", name, "error", err, "requeue-after", result.RequeueAfter)
|
||||
r.record.Event(ac, event.Warning(reasonCannotExecutePrehooks, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errExecutePrehooks)))
|
||||
return result, errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
return result, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
r.record.Event(ac, event.Normal(reasonExecutePrehook, "Successfully executed a prehook", "prehook name ", name))
|
||||
}
|
||||
@@ -291,20 +294,17 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
log.Info("Cannot render components", "error", err, "requeue-after", time.Now().Add(shortWait))
|
||||
r.record.Event(ac, event.Warning(reasonCannotRenderComponents, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errRenderComponents)))
|
||||
return errResult, errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
return errResult, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
log.Debug("Successfully rendered components", "workloads", len(workloads))
|
||||
r.record.Event(ac, event.Normal(reasonRenderComponents, "Successfully rendered components", "workloads", strconv.Itoa(len(workloads))))
|
||||
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID())}
|
||||
if r.applyOnceOnly {
|
||||
applyOpts = append(applyOpts, applyOnceOnly())
|
||||
}
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode)}
|
||||
if err := r.workloads.Apply(ctx, ac.Status.Workloads, workloads, applyOpts...); err != nil {
|
||||
log.Debug("Cannot apply components", "error", err, "requeue-after", time.Now().Add(shortWait))
|
||||
r.record.Event(ac, event.Warning(reasonCannotApplyComponents, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errApplyComponents)))
|
||||
return errResult, errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
return errResult, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
log.Debug("Successfully applied components", "workloads", len(workloads))
|
||||
r.record.Event(ac, event.Normal(reasonApplyComponents, "Successfully applied components", "workloads", strconv.Itoa(len(workloads))))
|
||||
@@ -324,7 +324,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
log.Debug("Cannot garbage collect component", "error", err, "requeue-after", time.Now().Add(shortWait))
|
||||
record.Event(ac, event.Warning(reasonCannotGGComponents, err))
|
||||
ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errGCComponent)))
|
||||
return errResult, errors.Wrap(r.client.Status().Update(ctx, ac), errUpdateAppConfigStatus)
|
||||
return errResult, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus)
|
||||
}
|
||||
log.Debug("Garbage collected resource")
|
||||
record.Event(ac, event.Normal(reasonGGComponent, "Successfully garbage collected component"))
|
||||
@@ -344,11 +344,24 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco
|
||||
return reconcile.Result{RequeueAfter: waitTime}, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates v1alpha2.ApplicationConfiguration's Status with retry.RetryOnConflict
|
||||
func (r *OAMApplicationReconciler) UpdateStatus(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, opts ...client.UpdateOption) error {
|
||||
status := ac.DeepCopy().Status
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
if err = r.client.Get(ctx, types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name}, ac); err != nil {
|
||||
return
|
||||
}
|
||||
ac.Status = status
|
||||
return r.client.Status().Update(ctx, ac, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *OAMApplicationReconciler) updateStatus(ctx context.Context, ac, acPatch *v1alpha2.ApplicationConfiguration, workloads []Workload) {
|
||||
ac.Status.Workloads = make([]v1alpha2.WorkloadStatus, len(workloads))
|
||||
historyWorkloads := make([]v1alpha2.HistoryWorkload, 0)
|
||||
for i, w := range workloads {
|
||||
ac.Status.Workloads[i] = workloads[i].Status()
|
||||
ac.Status.Workloads[i].ObservedGeneration = ac.GetGeneration()
|
||||
if !w.RevisionEnabled {
|
||||
continue
|
||||
}
|
||||
@@ -576,36 +589,87 @@ func (e *GenerationUnchanged) Error() string {
|
||||
"Please ignore this error in other logic.")
|
||||
}
|
||||
|
||||
func applyOnceOnly() apply.ApplyOption {
|
||||
return func(ctx context.Context, current, desired runtime.Object) error {
|
||||
if current == nil {
|
||||
// applyOnceOnly is an ApplyOption that controls the applying mechanism for workload and trait.
|
||||
// More detail refers to the ApplyOnceOnlyMode type annotation
|
||||
func applyOnceOnly(ac *v1alpha2.ApplicationConfiguration, mode core.ApplyOnceOnlyMode) apply.ApplyOption {
|
||||
return func(_ context.Context, existing, desired runtime.Object) error {
|
||||
if mode == core.ApplyOnceOnlyOff {
|
||||
return nil
|
||||
}
|
||||
// ApplyOption only works for update/patch operation and will be ignored
|
||||
// if the object doesn't exist before.
|
||||
c, _ := current.(metav1.Object)
|
||||
|
||||
d, _ := desired.(metav1.Object)
|
||||
if c == nil || d == nil {
|
||||
return errors.Errorf("invalid object being applied: %q ",
|
||||
if d == nil {
|
||||
return errors.Errorf("cannot access metadata of object being applied: %q",
|
||||
desired.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
cLabels, dLabels := c.GetLabels(), d.GetLabels()
|
||||
if dLabels[oam.LabelOAMResourceType] == oam.ResourceTypeWorkload ||
|
||||
dLabels[oam.LabelOAMResourceType] == oam.ResourceTypeTrait {
|
||||
// check whether spec changes occur on the workload or trait,
|
||||
// according to annotations and lables
|
||||
if c.GetAnnotations()[oam.AnnotationAppGeneration] !=
|
||||
d.GetAnnotations()[oam.AnnotationAppGeneration] {
|
||||
return nil
|
||||
}
|
||||
if cLabels[oam.LabelAppComponentRevision] != dLabels[oam.LabelAppComponentRevision] ||
|
||||
cLabels[oam.LabelAppComponent] != dLabels[oam.LabelAppComponent] ||
|
||||
cLabels[oam.LabelAppName] != dLabels[oam.LabelAppName] {
|
||||
return nil
|
||||
}
|
||||
// return an error to abort current apply
|
||||
return &GenerationUnchanged{}
|
||||
dLabels := d.GetLabels()
|
||||
dAnnots := d.GetAnnotations()
|
||||
if dLabels[oam.LabelOAMResourceType] != oam.ResourceTypeWorkload &&
|
||||
dLabels[oam.LabelOAMResourceType] != oam.ResourceTypeTrait {
|
||||
// this ApplyOption only works for workload and trait
|
||||
// skip if the resource is not workload nor trait, e.g., scope
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
// the resource doesn't exist (maybe not created before, or created but deleted by others)
|
||||
if existing == nil {
|
||||
if mode != core.ApplyOnceOnlyForce {
|
||||
// non-force mode will always create the resource if not exist.
|
||||
return nil
|
||||
}
|
||||
|
||||
createdBefore := false
|
||||
for _, w := range ac.Status.Workloads {
|
||||
// traverse recorded workloads to find the one matching applied resource
|
||||
if w.Reference.GetObjectKind().GroupVersionKind() == desired.GetObjectKind().GroupVersionKind() &&
|
||||
w.Reference.Name == d.GetName() {
|
||||
// the workload matches applied resource
|
||||
createdBefore = true
|
||||
}
|
||||
if !createdBefore {
|
||||
// the workload is not matched, then traverse its traits to find matching one
|
||||
for _, t := range w.Traits {
|
||||
if t.Reference.GetObjectKind().GroupVersionKind() == desired.GetObjectKind().GroupVersionKind() &&
|
||||
t.Reference.Name == d.GetName() {
|
||||
// the trait matches applied resource
|
||||
createdBefore = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't use if-else here because it will miss the case that the resource is a trait
|
||||
if createdBefore {
|
||||
// the resource was created before and appconfig status recorded the resource version applied
|
||||
// if recored ObservedGeneration and ComponentRevisionName both equal to the applied resource's,
|
||||
// that means its spec is not changed
|
||||
if (strconv.Itoa(int(w.ObservedGeneration)) != dAnnots[oam.AnnotationAppGeneration]) ||
|
||||
(w.ComponentRevisionName != dLabels[oam.LabelAppComponentRevision]) {
|
||||
// its spec is changed, so re-create the resource
|
||||
return nil
|
||||
}
|
||||
// its spec is not changed, so return an error to abort creating it
|
||||
return &GenerationUnchanged{}
|
||||
}
|
||||
}
|
||||
// no recorded workloads nor traits matches the applied resource
|
||||
// that means the resource is not created before, so create it
|
||||
return nil
|
||||
}
|
||||
|
||||
// the resource already exists
|
||||
e, _ := existing.(metav1.Object)
|
||||
if e == nil {
|
||||
return errors.Errorf("cannot access metadata of existing object: %q",
|
||||
existing.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
eLabels := e.GetLabels()
|
||||
// if existing reource's (observed)AppConfigGeneration and ComponentRevisionName both equal to the applied one's,
|
||||
// that means its spec is not changed
|
||||
if (e.GetAnnotations()[oam.AnnotationAppGeneration] != dAnnots[oam.AnnotationAppGeneration]) ||
|
||||
(eLabels[oam.LabelAppComponentRevision] != dLabels[oam.LabelAppComponentRevision]) {
|
||||
// its spec is changed, so apply new configuration to it
|
||||
return nil
|
||||
}
|
||||
// its spec is not changed, return an error to abort applying it
|
||||
return &GenerationUnchanged{}
|
||||
}
|
||||
}
|
||||
|
||||
+244
-37
@@ -16,6 +16,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
@@ -31,21 +32,25 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
traitSpecValue2 = "test2"
|
||||
)
|
||||
var (
|
||||
ctx = context.Background()
|
||||
cw v1alpha2.ContainerizedWorkload
|
||||
component v1alpha2.Component
|
||||
fakeTrait *unstructured.Unstructured
|
||||
appConfig v1alpha2.ApplicationConfiguration
|
||||
ctx = context.Background()
|
||||
cw v1alpha2.ContainerizedWorkload
|
||||
component v1alpha2.Component
|
||||
fakeTrait *unstructured.Unstructured
|
||||
appConfig v1alpha2.ApplicationConfiguration
|
||||
cwObjKey = client.ObjectKey{
|
||||
Name: compName,
|
||||
Namespace: namespace,
|
||||
}
|
||||
traitObjKey = client.ObjectKey{
|
||||
Name: traitName,
|
||||
Namespace: namespace,
|
||||
}
|
||||
appConfigKey = client.ObjectKey{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
}
|
||||
req = reconcile.Request{NamespacedName: appConfigKey}
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
ns corev1.Namespace
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
@@ -112,19 +117,12 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
},
|
||||
}
|
||||
|
||||
logf.Log.Info("Start to run a test, clean up previous resources")
|
||||
// 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")
|
||||
Eventually(
|
||||
// gomega has a bug that can't take nil as the actual input, so has to make it a func
|
||||
func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Name: namespace}, &corev1.Namespace{})
|
||||
},
|
||||
time.Second*120, time.Millisecond*500).Should(&util.NotFoundMatcher{})
|
||||
|
||||
By("Create namespace")
|
||||
ns = corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Create(ctx, &ns)
|
||||
@@ -142,24 +140,33 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
return k8sClient.Get(ctx, appConfigKey, &appConfig)
|
||||
}, time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
|
||||
By("Enable ApplyOnceOnly")
|
||||
reconciler.applyOnceOnly = true
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
logf.Log.Info("Clean up previous resources")
|
||||
Expect(k8sClient.DeleteAllOf(ctx, &appConfig, client.InNamespace(namespace))).Should(Succeed())
|
||||
Expect(k8sClient.DeleteAllOf(ctx, &cw, client.InNamespace(namespace))).Should(Succeed())
|
||||
Expect(k8sClient.DeleteAllOf(ctx, &component, client.InNamespace(namespace))).Should(Succeed())
|
||||
var deleteTrait unstructured.Unstructured
|
||||
deleteTrait.SetAPIVersion("example.com/v1")
|
||||
deleteTrait.SetKind("Foo")
|
||||
Expect(k8sClient.DeleteAllOf(ctx, &deleteTrait, client.InNamespace(namespace))).Should(Succeed())
|
||||
// restore as default value
|
||||
reconciler.applyOnceOnly = false
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyOff
|
||||
})
|
||||
|
||||
When("Change workload/trait instance bypass ApplicationConfiguration", func() {
|
||||
It("should keep workload instanced not changed by reconciliation", func() {
|
||||
When("ApplyOnceOnly is enabled", func() {
|
||||
It("should not revert changes of workload/trait made by others", func() {
|
||||
By("Enable ApplyOnceOnly")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyOn
|
||||
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Name: compName, Namespace: namespace}, &cwObj)
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cwObj.Spec.Containers[0].Image).Should(Equal(image1))
|
||||
|
||||
@@ -168,7 +175,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
fooObj.SetAPIVersion("example.com/v1")
|
||||
fooObj.SetKind("Foo")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Name: traitName, Namespace: namespace}, fooObj)
|
||||
return k8sClient.Get(ctx, traitObjKey, fooObj)
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
fooObjV, _, _ := unstructured.NestedString(fooObj.Object, "spec", "key")
|
||||
Expect(fooObjV).Should(Equal(traitSpecValue1))
|
||||
@@ -184,7 +191,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
By("Get updated workload instance & Check workload spec")
|
||||
updateCwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: compName, Namespace: namespace}, &updateCwObj); err != nil {
|
||||
if err := k8sClient.Get(ctx, cwObjKey, &updateCwObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return updateCwObj.Spec.Containers[0].Image
|
||||
@@ -195,7 +202,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
updatedFooObj.SetAPIVersion("example.com/v1")
|
||||
updatedFooObj.SetKind("Foo")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: traitName, Namespace: namespace}, updatedFooObj); err != nil {
|
||||
if err := k8sClient.Get(ctx, traitObjKey, updatedFooObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
v, _, _ := unstructured.NestedString(fooObj.Object, "spec", "key")
|
||||
@@ -209,7 +216,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
By("Check workload is not changed by reconciliation")
|
||||
updateCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: compName, Namespace: namespace}, &updateCwObj); err != nil {
|
||||
if err := k8sClient.Get(ctx, cwObjKey, &updateCwObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return updateCwObj.Spec.Containers[0].Image
|
||||
@@ -220,7 +227,7 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
updatedFooObj.SetAPIVersion("example.com/v1")
|
||||
updatedFooObj.SetKind("Foo")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: traitName, Namespace: namespace}, updatedFooObj); err != nil {
|
||||
if err := k8sClient.Get(ctx, traitObjKey, updatedFooObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
v, _, _ := unstructured.NestedString(updatedFooObj.Object, "spec", "key")
|
||||
@@ -228,13 +235,13 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
}, 3*time.Second, time.Second).Should(Equal(traitSpecValue2))
|
||||
|
||||
By("Disable ApplyOnceOnly & Reconcile again")
|
||||
reconciler.applyOnceOnly = false
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyOff
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
|
||||
By("Check workload is changed by reconciliation")
|
||||
updateCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: compName, Namespace: namespace}, &updateCwObj); err != nil {
|
||||
if err := k8sClient.Get(ctx, cwObjKey, &updateCwObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return updateCwObj.Spec.Containers[0].Image
|
||||
@@ -245,13 +252,213 @@ var _ = Describe("Test apply (workloads/traits) once only", func() {
|
||||
updatedFooObj.SetAPIVersion("example.com/v1")
|
||||
updatedFooObj.SetKind("Foo")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: traitName, Namespace: namespace}, updatedFooObj); err != nil {
|
||||
if err := k8sClient.Get(ctx, traitObjKey, updatedFooObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
v, _, _ := unstructured.NestedString(updatedFooObj.Object, "spec", "key")
|
||||
return v
|
||||
}, 3*time.Second, time.Second).Should(Equal(traitSpecValue1))
|
||||
})
|
||||
|
||||
It("should re-create workload/trait if it's delete by others", func() {
|
||||
By("Enable ApplyOnceOnly")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyOn
|
||||
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Delete the workload")
|
||||
Expect(k8sClient.Delete(ctx, &cwObj)).Should(Succeed())
|
||||
Expect(k8sClient.Get(ctx, cwObjKey, &v1alpha2.ContainerizedWorkload{})).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
By("Check workload is created by reconciliation")
|
||||
recreatedCwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Expect(k8sClient.Get(ctx, cwObjKey, &recreatedCwObj)).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("ApplyOnceOnlyForce is enabled", func() {
|
||||
It("should not revert changes of workload/trait made by others", func() {
|
||||
By("Enable ApplyOnceOnlyForce")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce
|
||||
|
||||
By("Get workload instance & Check workload spec")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 5*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cwObj.Spec.Containers[0].Image).Should(Equal(image1))
|
||||
|
||||
By("Get trait instance & Check trait spec")
|
||||
fooObj := &unstructured.Unstructured{}
|
||||
fooObj.SetAPIVersion("example.com/v1")
|
||||
fooObj.SetKind("Foo")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, traitObjKey, fooObj)
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
fooObjV, _, _ := unstructured.NestedString(fooObj.Object, "spec", "key")
|
||||
Expect(fooObjV).Should(Equal(traitSpecValue1))
|
||||
|
||||
By("Modify workload spec & Apply changed workload")
|
||||
cwObj.Spec.Containers[0].Image = image2
|
||||
Expect(k8sClient.Patch(ctx, &cwObj, client.Merge)).Should(Succeed())
|
||||
|
||||
By("Modify trait spec & Apply changed trait")
|
||||
unstructured.SetNestedField(fooObj.Object, traitSpecValue2, "spec", "key")
|
||||
Expect(k8sClient.Patch(ctx, fooObj, client.Merge)).Should(Succeed())
|
||||
|
||||
By("Get updated workload instance & Check workload spec")
|
||||
updateCwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, cwObjKey, &updateCwObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return updateCwObj.Spec.Containers[0].Image
|
||||
}, 3*time.Second, time.Second).Should(Equal(image2))
|
||||
|
||||
By("Get updated trait instance & Check trait spec")
|
||||
updatedFooObj := &unstructured.Unstructured{}
|
||||
updatedFooObj.SetAPIVersion("example.com/v1")
|
||||
updatedFooObj.SetKind("Foo")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, traitObjKey, updatedFooObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
v, _, _ := unstructured.NestedString(fooObj.Object, "spec", "key")
|
||||
return v
|
||||
}, 3*time.Second, time.Second).Should(Equal(traitSpecValue2))
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
By("Check workload is not changed by reconciliation")
|
||||
updateCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, cwObjKey, &updateCwObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return updateCwObj.Spec.Containers[0].Image
|
||||
}, 3*time.Second, time.Second).Should(Equal(image2))
|
||||
|
||||
By("Check trait is not changed by reconciliation")
|
||||
updatedFooObj = &unstructured.Unstructured{}
|
||||
updatedFooObj.SetAPIVersion("example.com/v1")
|
||||
updatedFooObj.SetKind("Foo")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, traitObjKey, updatedFooObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
v, _, _ := unstructured.NestedString(updatedFooObj.Object, "spec", "key")
|
||||
return v
|
||||
}, 3*time.Second, time.Second).Should(Equal(traitSpecValue2))
|
||||
|
||||
By("Disable ApplyOnceOnly & Reconcile again")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyOff
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
|
||||
By("Check workload is changed by reconciliation")
|
||||
updateCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, cwObjKey, &updateCwObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
return updateCwObj.Spec.Containers[0].Image
|
||||
}, 3*time.Second, time.Second).Should(Equal(image1))
|
||||
|
||||
By("Check trait is changed by reconciliation")
|
||||
updatedFooObj = &unstructured.Unstructured{}
|
||||
updatedFooObj.SetAPIVersion("example.com/v1")
|
||||
updatedFooObj.SetKind("Foo")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(ctx, traitObjKey, updatedFooObj); err != nil {
|
||||
return ""
|
||||
}
|
||||
v, _, _ := unstructured.NestedString(updatedFooObj.Object, "spec", "key")
|
||||
return v
|
||||
}, 3*time.Second, time.Second).Should(Equal(traitSpecValue1))
|
||||
})
|
||||
|
||||
It("should not re-create workload/trait if it's delete by others", func() {
|
||||
By("Enable ApplyOnceOnlyForce")
|
||||
reconciler.applyOnceOnlyMode = core.ApplyOnceOnlyForce
|
||||
|
||||
By("Get workload instance")
|
||||
cwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, cwObjKey, &cwObj)
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Get trait instance & Check trait spec")
|
||||
fooObj := unstructured.Unstructured{}
|
||||
fooObj.SetAPIVersion("example.com/v1")
|
||||
fooObj.SetKind("Foo")
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, traitObjKey, &fooObj)
|
||||
}, 3*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Delete the workload")
|
||||
Expect(k8sClient.Delete(ctx, &cwObj)).Should(Succeed())
|
||||
Expect(k8sClient.Get(ctx, cwObjKey, &v1alpha2.ContainerizedWorkload{})).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Delete the trait")
|
||||
Expect(k8sClient.Delete(ctx, &fooObj)).Should(Succeed())
|
||||
Expect(k8sClient.Get(ctx, traitObjKey, &fooObj)).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Reconcile")
|
||||
Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil())
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
By("Check workload is not re-created by reconciliation")
|
||||
recreatedCwObj := v1alpha2.ContainerizedWorkload{}
|
||||
Expect(k8sClient.Get(ctx, cwObjKey, &recreatedCwObj)).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Check trait is not re-created by reconciliation")
|
||||
recreatedFooObj := unstructured.Unstructured{}
|
||||
recreatedFooObj.SetAPIVersion("example.com/v1")
|
||||
recreatedFooObj.SetKind("Foo")
|
||||
Expect(k8sClient.Get(ctx, traitObjKey, &recreatedFooObj)).Should(util.NotFoundMatcher{})
|
||||
|
||||
By("Update Appconfig to trigger generation augment")
|
||||
unstructured.SetNestedField(fakeTrait.Object, "newvalue", "spec", "key")
|
||||
appConfig = v1alpha2.ApplicationConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.ApplicationConfigurationSpec{
|
||||
Components: []v1alpha2.ApplicationConfigurationComponent{
|
||||
{
|
||||
ComponentName: compName,
|
||||
Traits: []v1alpha2.ComponentTrait{
|
||||
{Trait: runtime.RawExtension{Object: fakeTrait}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Patch(ctx, &appConfig, client.Merge)).Should(Succeed())
|
||||
|
||||
By("Reconcile")
|
||||
reconcileRetry(reconciler, req)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
By("Check workload is re-created by reconciliation")
|
||||
recreatedCwObj = v1alpha2.ContainerizedWorkload{}
|
||||
Expect(k8sClient.Get(ctx, cwObjKey, &recreatedCwObj)).Should(Succeed())
|
||||
|
||||
By("Check trait is re-created by reconciliation")
|
||||
recreatedFooObj = unstructured.Unstructured{}
|
||||
recreatedFooObj.SetAPIVersion("example.com/v1")
|
||||
recreatedFooObj.SetKind("Foo")
|
||||
Expect(k8sClient.Get(ctx, traitObjKey, &recreatedFooObj)).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user