From ef5d3c35c6420ff5367e9a7a2aaa608a1da6bf18 Mon Sep 17 00:00:00 2001 From: Harry Zhang Date: Thu, 28 Jan 2021 12:29:01 -0800 Subject: [PATCH 01/38] Explain KubeVela --- docs/en/introduction.md | 2 +- docs/en/platform-engineers/overview.md | 100 ++++++++++++++++++++++++- docs/resources/kubevela-runtime.png | Bin 0 -> 241788 bytes 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 docs/resources/kubevela-runtime.png diff --git a/docs/en/introduction.md b/docs/en/introduction.md index 85c3b9387..2a54466a9 100644 --- a/docs/en/introduction.md +++ b/docs/en/introduction.md @@ -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. diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index 12ecc468d..646d9bd78 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -1,3 +1,101 @@ # KubeVela for Platform Builders -TBD: this documentation is still work in progress. \ No newline at end of file +This documentation explains how KubeVela works in perspective of platform team. + +## KubeVela Runtime + +The KubeVela runtime is the core component of KubeVela, it is a Kubernetes addon composed by several parts. + +The first part of this runtime is "encapsulation engine". This component supports various of encapsulation modules to create a single user facing abstraction named `Application` that allows end user to fill in parameters to instantiate the module. At the meantime, it also provides a set of interfaces for platform team to define and customize the module (i.e. CUE, Helm, or Terraform modules, etc). The implementation of abstraction engine is powered by Open Application Model. + +The second part is "deployment engine", it is responsible for progressive rollout of the application following given rollout strategy (e.g. canary, blue-green, etc) claimed in `AppDeployment`. + +![alt](../../resources/kubevela-runtime.png) + +### Encapsulation Engine + +As a platform builder, the encapsulation engine is essential to create any end user facing platform with Kubernetes, i.e. we want to lower the bar for end users by creating higher level abstractions. + +One typical example is we will want to encapsulate a Kubernetes `Deployment` and `Service` into a module probably named *Web Service*, and let end users to instantiate this module by simply filling in the needed 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 for creating such abstraction. + +Hence, the encapsulation engine of KubeVela is designed to help to make building abstractions easy, in a highly extensible approach. + +#### Build Extensible Abstraction + +First of all, with KubeVela, you will never create monolithic abstraction which is restricted and can't be extended. In detail, the encapsulation engine introduced a extensible app-centric model behind the abstraction, this makes the abstraction is essentially assembled by components (workload modules) and traits (operational modules), a example is like below: + +```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: "xxxxx" +``` + +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 requirement, the platform team could customize module template in definitions by any time. + +#### A Unified Abstraction For All + +KubeVela intends to support any possible module type by natural, for example `CUE`, `Terraform`, `Helm`, etc and of course by a plain `Kubernetes CRD`. In order to define modules and parameters orgnized, we also introduced a [`catalog` structure](https://github.com/oam-dev/kubevela/blob/master/design/vela-core/APIServer-Catalog.md#catalog-structure). KubeVela will load such catalog via Git repo URL. + +```console +/catalog/ # a catalog consists of multiple packages +|-- + |-- v1.0 # a package consists of multiple versions + |-- metadata.yaml + |-- definitions/ + |-- xxx-workload.yaml + |-- xxx-trait.yaml + |-- conditions/ + |-- check-crd.yaml + |-- hooks/ + |-- pre-install.yaml + |-- modules.yaml # could be helm, terraform, etc. + |-- v2.0 +|-- +``` + +Hence, it's straightforward that you could use KubeVela to create unified abstraction that can deploy any kind of resource, including cloud services, as long as they could be encapsulated by a module and placed in the catalog above. Actually, in the `application-sample` above it defined a OSS bucket on Alibaba Cloud for the other component to consume, this is powered by Terraform module. + +#### 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 a 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 dangerous in production because renaming will lead to the app restart). While the more painful part it, 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 it's also 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 your custom workload type which doesn't follow the pattern of `Deployment`. For example, normally it doesn't make sense to replicate a `StatefulSet` instance to two copies during rollout, which means the users have to maintain the name, revision, label, selector, app instancs 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 by hand, especially in the scenario of rollout or traffic splitting. In nutshell, all the needed Kubernetes resources are now encapsulated in a single abstraction, and KubeVela will maintain the instance name, revision, 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 behavior of how to do revision, the details about all above metadata behind the abstraction. + +Thus, all those metadata including instance names, labels, selectors, revisions, etc now become the automatically maintained information and a standard contract that any day 2 operation controller such as Istio and rollout can rely on. This is the key to ensure our platform could provide user friendly experience but keep "transparent" to all the following operation behaviors. + +### Deployment Engine + +The deployment engine is one of the operation controllers provided by KubeVela to handle progressive rollout of the application. More contents about it will come later. diff --git a/docs/resources/kubevela-runtime.png b/docs/resources/kubevela-runtime.png new file mode 100644 index 0000000000000000000000000000000000000000..a91b94ed27939016bf31dce584ad7f4a6b1d13ff GIT binary patch literal 241788 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�ee~~bUMF9#e0U=n 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 E0o3cVY5)KL literal 0 HcmV?d00001 From d1c93ed90c067f446788a94633bf4738e2b6a205 Mon Sep 17 00:00:00 2001 From: "Lei Zhang (Harry)" Date: Thu, 28 Jan 2021 19:09:44 -0800 Subject: [PATCH 02/38] Update docs/en/platform-engineers/overview.md Co-authored-by: Zheng Xi Zhou --- docs/en/platform-engineers/overview.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index 646d9bd78..9b1d688c5 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -22,7 +22,7 @@ Hence, the encapsulation engine of KubeVela is designed to help to make building #### Build Extensible Abstraction -First of all, with KubeVela, you will never create monolithic abstraction which is restricted and can't be extended. In detail, the encapsulation engine introduced a extensible app-centric model behind the abstraction, this makes the abstraction is essentially assembled by components (workload modules) and traits (operational modules), a example is like below: +First of all, with KubeVela, you will never create monolithic abstraction which is restricted and can't be extended. In detail, the encapsulation engine introduced an extensible app-centric model behind the abstraction, this makes the abstraction is essentially assembled by components (workload modules) and traits (operational modules), an example is like below: ```yaml apiVersion: core.oam.dev/v1alpha2 @@ -86,15 +86,15 @@ In KubeVela, the encapsulation engine is intended to be implemented in a [Kubern #### 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 dangerous in production because renaming will lead to the app restart). While the more painful part it, 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 it's also the only revision mechanism our Istio controller could count on. +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 your custom workload type which doesn't follow the pattern of `Deployment`. For example, normally it doesn't make sense to replicate a `StatefulSet` instance to two copies during rollout, which means the users have to maintain the name, revision, label, selector, app instancs in a totally different approach from `Deployment`. +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 by hand, especially in the scenario of rollout or traffic splitting. In nutshell, all the needed Kubernetes resources are now encapsulated in a single abstraction, and KubeVela will maintain the instance name, revision, 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 behavior of how to do revision, the details about all above metadata 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 including instance names, labels, selectors, revisions, etc now become the automatically maintained information and a standard contract that any day 2 operation controller such as Istio and rollout can rely on. This is the key to ensure our platform could provide user friendly experience but keep "transparent" to all the following operation behaviors. +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. ### Deployment Engine From 7f64974701be79bfc3e23a1f1dcd33434bbd95ac Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 1 Feb 2021 01:40:59 -0800 Subject: [PATCH 03/38] This PR spells out the rollout states (#972) * add rollout state transition: * address comments --- .../v1alpha1/rollout_plan_types.go | 48 ++-- .../common/rollout/rollout_plan_controller.go | 28 +++ .../common/rollout/rollout_plan_init.go | 19 -- .../common/rollout/rollout_state.go | 210 ++++++++++++++++++ .../rollout/workloads/cloneset_controller.go | 32 +++ .../common/rollout/workloads/controller.go | 28 +++ .../common/rollout/workloads/factory.go | 47 ++++ .../applicationdeployment_controller.go | 16 +- 8 files changed, 384 insertions(+), 44 deletions(-) create mode 100644 pkg/controller/common/rollout/rollout_plan_controller.go delete mode 100644 pkg/controller/common/rollout/rollout_plan_init.go create mode 100644 pkg/controller/common/rollout/rollout_state.go create mode 100644 pkg/controller/common/rollout/workloads/cloneset_controller.go create mode 100644 pkg/controller/common/rollout/workloads/controller.go create mode 100644 pkg/controller/common/rollout/workloads/factory.go diff --git a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go index e4abc3838..46bae00c0 100644 --- a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go +++ b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go @@ -34,38 +34,42 @@ 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" + // PausedState rollout is stopped, the batch rolling is not completed + PausedState RollingState = "paused" + // 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 + // 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. 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" + BatchVerifyingState BatchRollingState = "batchVerifying" + // BatchVerifyFailedState indicates that the batch didn't get the manual or automatic approval + BatchVerifyFailedState BatchRollingState = "batchVerifyFailed" + // BatchReadyState indicates that all the pods in the are upgraded and its state is ready + BatchReadyState BatchRollingState = "batchReady" + // BatchAvailableState indicates that all the pods in the are available, we can move on to the next batch + BatchAvailableState BatchRollingState = "batchAvailable" ) // RolloutPlan fines the details of the rollout plan diff --git a/pkg/controller/common/rollout/rollout_plan_controller.go b/pkg/controller/common/rollout/rollout_plan_controller.go new file mode 100644 index 000000000..63632766a --- /dev/null +++ b/pkg/controller/common/rollout/rollout_plan_controller.go @@ -0,0 +1,28 @@ +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" + "github.com/oam-dev/kubevela/pkg/controller/common/rollout/workloads" +) + +// ReconcileRolloutPlan generates the rollout plan and reconcile it +func ReconcileRolloutPlan(ctx context.Context, client client.Client, rolloutSpec *v1alpha1.RolloutPlan, + targetWorkload, sourceWorkload *unstructured.Unstructured, rolloutStatus *v1alpha1.RolloutStatus) (v1alpha1.RolloutStatus, error) { + klog.InfoS("generate the rollout plan", "rollout Spec", rolloutSpec, + "target workload", klog.KObj(targetWorkload)) + if sourceWorkload != nil { + klog.InfoS("we will do rolling upgrades", "source workload", klog.KObj(sourceWorkload)) + } + klog.Info("check the rollout status ", "rollout state", rolloutStatus.RollingState, "batch rolling state", + rolloutStatus.BatchRollingState) + + wf := workloads.NewWorkloadControllerFactory(ctx, client, rolloutSpec, targetWorkload, sourceWorkload) + wf.GetController(targetWorkload.GroupVersionKind()) + return *rolloutStatus, nil +} diff --git a/pkg/controller/common/rollout/rollout_plan_init.go b/pkg/controller/common/rollout/rollout_plan_init.go deleted file mode 100644 index 321238ed8..000000000 --- a/pkg/controller/common/rollout/rollout_plan_init.go +++ /dev/null @@ -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 -} diff --git a/pkg/controller/common/rollout/rollout_state.go b/pkg/controller/common/rollout/rollout_state.go new file mode 100644 index 000000000..2b438744d --- /dev/null +++ b/pkg/controller/common/rollout/rollout_state.go @@ -0,0 +1,210 @@ +package rollout + +import ( + "fmt" + + "k8s.io/klog/v2" + + "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" +) + +type rolloutEvent string + +const ( + // 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" + + // rollingFailedEvent indicates that the rolling is paused + rollingPausedEvent rolloutEvent = "rollingFailedEvent" + + // rollingResumedEvent indicates that the rolling is resumed + rollingResumedEvent rolloutEvent = "rollingResumedEvent" + + // rollingFinalizedEvent indicates that we have finalized the rollout which includes but not + // limited to the resource garbage collection + rollingFinalizedEvent rolloutEvent = "allBatchFinishedEvent" + + // rollingFailedEvent indicates that we encountered an unexpected error during upgrading + rollingFailedEvent rolloutEvent = "rollingFailedEvent" + + // 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" + + // 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" + + // batchRolloutContinueEvent indicates that we need to continue to upgrade the pods in the batch + batchRolloutContinueEvent rolloutEvent = "batchRolloutContinueEvent" + + // batchRolloutWaitingEvent indicates that we are waiting for the approval of resume one batch + batchRolloutWaitingEvent rolloutEvent = "batchWaitRolloutEvent" + + // batchRolloutApprovedEvent indicates that we are waiting for the approval of the + batchRolloutApprovedEvent rolloutEvent = "batchWaitRolloutEvent" + + // batchRolloutFailedEvent indicates that we are waiting for the approval of the + batchRolloutFailedEvent rolloutEvent = "batchRolloutFailedEvent" + + // workloadModifiedEvent indicates that the res + workloadModifiedEvent rolloutEvent = "workloadModifiedEvent" +) + +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" + +// StateMachineTransition 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 StateMachineTransition(rolloutStatus *v1alpha1.RolloutStatus, event rolloutEvent) error { + rollingState := rolloutStatus.RollingState + batchRollingState := rolloutStatus.BatchRollingState + defer klog.InfoS("try to execute a rollout state transition", + "pre rolling state", rollingState, + "pre batch rolling state", batchRollingState, + "post rolling state", rolloutStatus.RollingState, + "post batch rolling state", rolloutStatus.BatchRollingState) + + // we first process the global event + if event == rollingFailedEvent { + rolloutStatus.RollingState = v1alpha1.RolloutFailedState + return nil + } + if event == rollingPausedEvent { + rolloutStatus.RollingState = v1alpha1.PausedState + return nil + } + + switch rollingState { + case v1alpha1.VerifyingState: + if event == rollingSpecVerifiedEvent { + rolloutStatus.RollingState = v1alpha1.InitializingState + return nil + } + return fmt.Errorf(invalidRollingStateTransition, rollingState, event) + + case v1alpha1.InitializingState: + if event == rollingInitializedEvent { + rolloutStatus.RollingState = v1alpha1.RollingInBatchesState + return nil + } + return fmt.Errorf(invalidRollingStateTransition, rollingState, event) + + case v1alpha1.PausedState: + if event == rollingResumedEvent { + // we don't know where it was last time, need to start from beginning + // since we don't change the batch rolling state when we pause + // we should be able to resume if it was rolling before paused + rolloutStatus.RollingState = v1alpha1.VerifyingState + return nil + } + return fmt.Errorf(invalidBatchRollingStateTransition, rollingState, event) + + case v1alpha1.RollingInBatchesState: + return batchStateTransition(rolloutStatus, batchRollingState, event) + + case v1alpha1.FinalisingState: + if event == rollingFinalizedEvent { + rolloutStatus.RollingState = v1alpha1.RolloutSucceedState + return nil + } + return fmt.Errorf(invalidRollingStateTransition, rollingState, event) + + case v1alpha1.RolloutSucceedState: + if event == workloadModifiedEvent { + rolloutStatus.RollingState = v1alpha1.VerifyingState + return nil + } + if event == rollingFinalizedEvent { + // no op + return nil + } + return fmt.Errorf(invalidRollingStateTransition, rollingState, event) + + case v1alpha1.RolloutFailedState: + if event == workloadModifiedEvent { + rolloutStatus.RollingState = v1alpha1.VerifyingState + return nil + } + if event == rollingFailedEvent { + // no op + return nil + } + return fmt.Errorf(invalidRollingStateTransition, rollingState, event) + + default: + return fmt.Errorf("invalid rolling state %s", rollingState) + } +} + +// batchStateTransition handles the state transition when the rollout is in action +func batchStateTransition(rolloutStatus *v1alpha1.RolloutStatus, + batchRollingState v1alpha1.BatchRollingState, event rolloutEvent) error { + switch batchRollingState { + case v1alpha1.BatchInitializingState: + if event == initializedOneBatchEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchInRollingState + return nil + } + return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) + + case v1alpha1.BatchInRollingState: + if event == batchRolloutWaitingEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchVerifyingState + return nil + } + if event == batchRolloutContinueEvent { + // no op + return nil + } + if event == batchRolloutApprovedEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchReadyState + return nil + } + return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) + + case v1alpha1.BatchVerifyingState: + if event == batchRolloutApprovedEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchReadyState + return nil + } + if event == batchRolloutFailedEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchVerifyFailedState + rolloutStatus.RollingState = v1alpha1.RolloutFailedState + return nil + } + return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) + + case v1alpha1.BatchReadyState: + if event == oneBatchAvailableEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchAvailableState + return nil + } + return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) + + case v1alpha1.BatchAvailableState: + if event == finishedOneBatchEvent { + rolloutStatus.BatchRollingState = v1alpha1.BatchInitializingState + return nil + } + if event == allBatchFinishedEvent { + // transition out of the batch loop + rolloutStatus.RollingState = v1alpha1.FinalisingState + return nil + } + return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) + + default: + return fmt.Errorf("invalid batch rolling state %s", batchRollingState) + } +} diff --git a/pkg/controller/common/rollout/workloads/cloneset_controller.go b/pkg/controller/common/rollout/workloads/cloneset_controller.go new file mode 100644 index 000000000..e1e07e773 --- /dev/null +++ b/pkg/controller/common/rollout/workloads/cloneset_controller.go @@ -0,0 +1,32 @@ +package workloads + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" +) + +// CloneSetController is responsible for handle Cloneset type of workloads +type CloneSetController struct { + client client.Client + rolloutSpec *v1alpha1.RolloutPlan + targetWorkload *unstructured.Unstructured +} + +// Initialize first verify that the cloneset status is compatible with the rollout spec +// it then set the cloneset partition the same as the replicas (no new pod) and add an annotation +func (c *CloneSetController) Initialize() (int32, error) { + return 0, nil +} + +// RolloutPods calculates the number of pods we can upgrade once according to the rollout spec +// and then set the partition accordingly +func (c *CloneSetController) RolloutPods() (int32, error) { + return 0, nil +} + +// Finalize makes sure the Cloneset is all upgraded and +func (c *CloneSetController) Finalize() error { + return nil +} diff --git a/pkg/controller/common/rollout/workloads/controller.go b/pkg/controller/common/rollout/workloads/controller.go new file mode 100644 index 000000000..5d4f1d782 --- /dev/null +++ b/pkg/controller/common/rollout/workloads/controller.go @@ -0,0 +1,28 @@ +package workloads + +// WorkloadController is the interface that all type of workload controller implements +type WorkloadController interface { + // Initialize makes sure that the resources can be upgraded according to the rollout plan + // it returns the number of available pods that are upgrade (with the new spec) + Initialize() (int32, error) + + // RolloutPods 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 + RolloutPods() (int32, error) + + /* + GetMetadata() (string, map[string]int32, error) + + SyncStatus() error + + SetStatusFailedChecks() error + + ScaleToZero() error + */ + // 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 + // or we may add an annotation to indicate the upgrade finished time + Finalize() error +} diff --git a/pkg/controller/common/rollout/workloads/factory.go b/pkg/controller/common/rollout/workloads/factory.go new file mode 100644 index 000000000..2dbb2a9e5 --- /dev/null +++ b/pkg/controller/common/rollout/workloads/factory.go @@ -0,0 +1,47 @@ +package workloads + +import ( + "context" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" +) + +// WorkloadControllerFactory is the factory that creates controllers for different types of workload +type WorkloadControllerFactory struct { + client client.Client + rolloutSpec *v1alpha1.RolloutPlan + targetWorkload *unstructured.Unstructured + sourceWorkload *unstructured.Unstructured +} + +// NewWorkloadControllerFactory creates a WorkloadControllerFactory +func NewWorkloadControllerFactory(ctx context.Context, client client.Client, rolloutSpec *v1alpha1.RolloutPlan, + targetWorkload, sourceWorkload *unstructured.Unstructured) *WorkloadControllerFactory { + return &WorkloadControllerFactory{ + client: client, + rolloutSpec: rolloutSpec, + targetWorkload: targetWorkload, + sourceWorkload: sourceWorkload, + } +} + +// GetController generates the controller depends on the workload type +func (f *WorkloadControllerFactory) GetController(kind schema.GroupVersionKind) WorkloadController { + cloneSetCtrl := &CloneSetController{ + client: f.client, + rolloutSpec: f.rolloutSpec, + targetWorkload: f.targetWorkload, + } + + switch kind.Kind { + case "CloneSet": + return cloneSetCtrl + + default: + return cloneSetCtrl + } +} diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go b/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go index 898e34e82..457f86ab9 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go @@ -16,6 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" "github.com/oam-dev/kubevela/pkg/controller/common/rollout" controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application" @@ -42,6 +43,7 @@ type Reconciler struct { // Reconcile is the main logic of applicationdeployment controller func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { var appDeploy corev1alpha2.ApplicationDeployment + requeueAfterTime := 5 * time.Second ctx, cancel := context.WithTimeout(context.TODO(), reconcileTimeOut) defer cancel() if err := r.Get(ctx, req.NamespacedName, &appDeploy); err != nil { @@ -52,6 +54,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } klog.InfoS("Start to reconcile ", "application deployment", klog.KObj(&appDeploy)) + // TODO: check if the target/source has changed r.handleFinalizer(&appDeploy) // Get the target application @@ -88,7 +91,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { klog.ErrorS(err, "cannot fetch the workloads to upgrade", "workload Type", workloadType, "workload GVK", *workloadGVK, "target application", klog.KRef(req.Namespace, targetAppName), "source application", klog.KRef(req.Namespace, sourceAppName)) - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{RequeueAfter: requeueAfterTime}, client.IgnoreNotFound(err) } klog.InfoS("get the target workload we need to work on", "targetWorkload", klog.KObj(targetWorkload)) @@ -108,13 +111,20 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } // reconcile the rollout part of the spec given the target and source workload - err = rollout.ReconcileRolloutPlan(ctx, r, &appDeploy.Spec.RolloutPlan, targetWorkload, sourceWorkload) + rolloutStatus, err := rollout.ReconcileRolloutPlan(ctx, r, &appDeploy.Spec.RolloutPlan, targetWorkload, + sourceWorkload, &appDeploy.Status) if err != nil { klog.ErrorS(err, "cannot reconcile the rollout plan", "rollout spec", appDeploy.Spec.RolloutPlan) return ctrl.Result{}, err } - return ctrl.Result{}, nil + appDeploy.Status = rolloutStatus + if rolloutStatus.RollingState == v1alpha1.RolloutFailedState || + rolloutStatus.RollingState == v1alpha1.RolloutSucceedState { + // we don't need to keep checking the rollout too frequently if the rollout is at a terminal state + requeueAfterTime = 30 * time.Second + } + return ctrl.Result{RequeueAfter: requeueAfterTime}, r.Update(ctx, &appDeploy) } func (r *Reconciler) handleFinalizer(appDeploy *corev1alpha2.ApplicationDeployment) { From 86db8de125bd07828d41fdf1886dffe3bb222aa3 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Mon, 1 Feb 2021 17:45:02 +0800 Subject: [PATCH 04/38] Change "vela show" function name Also updated help vela help message --- pkg/commands/cli.go | 4 ++-- pkg/commands/show.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/commands/cli.go b/pkg/commands/cli.go index 657ddde2c..4301ff347 100644 --- a/pkg/commands/cli.go +++ b/pkg/commands/cli.go @@ -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), diff --git a/pkg/commands/show.go b/pkg/commands/show.go index de2437cc8..6d8423c2c 100644 --- a/pkg/commands/show.go +++ b/pkg/commands/show.go @@ -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", From f737379738c7a8be5d55976bb7ba7e4ca95cd8f2 Mon Sep 17 00:00:00 2001 From: Holger Protzek <3481523+hprotzek@users.noreply.github.com> Date: Tue, 2 Feb 2021 02:52:20 +0100 Subject: [PATCH 05/38] Added ingressClass to route trait (#947) * Added ingressClass to route trait * typo Co-authored-by: Jianbo Sun * run make reviewable * fixed ingress test Co-authored-by: Jianbo Sun --- apis/standard.oam.dev/v1alpha1/route_types.go | 3 +++ charts/vela-core/crds/standard.oam.dev_routes.yaml | 3 +++ design/vela-core/route.md | 7 +++++-- docs/en/developers/references/traits/route.md | 3 ++- docs/examples/registry/route.yaml | 6 ++++-- .../vela-core-legacy/crds/standard.oam.dev_routes.yaml | 3 +++ .../v1alpha1/routes/ingress/nginx_ingress.go | 2 +- .../v1alpha1/routes/ingress/nginx_ingress_test.go | 3 ++- 8 files changed, 23 insertions(+), 7 deletions(-) diff --git a/apis/standard.oam.dev/v1alpha1/route_types.go b/apis/standard.oam.dev/v1alpha1/route_types.go index c06fff06b..802b87ede 100644 --- a/apis/standard.oam.dev/v1alpha1/route_types.go +++ b/apis/standard.oam.dev/v1alpha1/route_types.go @@ -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 diff --git a/charts/vela-core/crds/standard.oam.dev_routes.yaml b/charts/vela-core/crds/standard.oam.dev_routes.yaml index 033067044..d9ab0eccc 100644 --- a/charts/vela-core/crds/standard.oam.dev_routes.yaml +++ b/charts/vela-core/crds/standard.oam.dev_routes.yaml @@ -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 diff --git a/design/vela-core/route.md b/design/vela-core/route.md index e0ad14aaa..1dd444bb9 100644 --- a/design/vela-core/route.md +++ b/design/vela-core/route.md @@ -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`. - diff --git a/docs/en/developers/references/traits/route.md b/docs/en/developers/references/traits/route.md index 0816a1204..76b36fce1 100644 --- a/docs/en/developers/references/traits/route.md +++ b/docs/en/developers/references/traits/route.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 diff --git a/docs/examples/registry/route.yaml b/docs/examples/registry/route.yaml index 509c7feff..cba56f0ba 100644 --- a/docs/examples/registry/route.yaml +++ b/docs/examples/registry/route.yaml @@ -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 } diff --git a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml index b95fd166c..bdc833f37 100644 --- a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml +++ b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml @@ -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 diff --git a/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress.go b/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress.go index a6a200ba1..ca3e5bb51 100644 --- a/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress.go +++ b/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress.go @@ -121,7 +121,7 @@ func (*Nginx) Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress { var annotations = make(map[string]string) - annotations["kubernetes.io/ingress.class"] = TypeNginx + annotations["kubernetes.io/ingress.class"] = routeTrait.Spec.IngressClass // SSL if routeTrait.Spec.TLS != nil { diff --git a/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress_test.go b/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress_test.go index f1670dccd..b1f08368e 100644 --- a/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress_test.go +++ b/pkg/controller/standard.oam.dev/v1alpha1/routes/ingress/nginx_ingress_test.go @@ -46,6 +46,7 @@ func TestConstruct(t *testing.T) { }, }, }, + IngressClass: "nginx-private", }, }, exp: []*v1beta1.Ingress{ @@ -57,7 +58,7 @@ func TestConstruct(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "trait-test-myrule1", Annotations: map[string]string{ - "kubernetes.io/ingress.class": "nginx", + "kubernetes.io/ingress.class": "nginx-private", "cert-manager.io/issuer": "test-issuer", }, OwnerReferences: []metav1.OwnerReference{ From 942115a1c353c0065dd3c91b4ebce46d167814bd Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Tue, 2 Feb 2021 15:37:40 +0800 Subject: [PATCH 06/38] Set KubeVela as verified publisher of ArtifactHub (#986) * Set KubeVeal as verified publisher of ArtifactHub Set KubeVela team as a verified publisher of Artifacthub, Add README to the repo and add helm badge in github readme To impleted #977 * Update hack/artifacthub/artifacthub-repo.yml Co-authored-by: Ryan Zhang * Update hack/artifacthub/artifacthub-repo.yml Co-authored-by: Jianbo Sun * add more owners * add README to chart Co-authored-by: Jianbo Sun Co-authored-by: Ryan Zhang --- .github/workflows/registry.yml | 6 ++++++ README.md | 1 + hack/artifacthub/artifacthub-repo.yml | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 hack/artifacthub/artifacthub-repo.yml diff --git a/.github/workflows/registry.yml b/.github/workflows/registry.yml index af42620c2..a2e71845c 100644 --- a/.github/workflows/registry.yml +++ b/.github/workflows/registry.yml @@ -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: @@ -95,6 +96,11 @@ jobs: 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/ + - name: add artifacthub stuff to the repo + run: | + rsync docs/en/install.md charts/vela-core/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 ./oss - name: Package helm charts run: | helm package charts/vela-core --destination .oss/ diff --git a/README.md b/README.md index c6c85895b..502161406 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![Releases](https://img.shields.io/github/release/oam-dev/kubevela/all.svg?style=flat-square)](https://github.com/oam-dev/kubevela/releases) [![TODOs](https://img.shields.io/endpoint?url=https://api.tickgit.com/badge?repo=github.com/oam-dev/kubevela)](https://www.tickgit.com/browse?repo=github.com/oam-dev/kubevela) [![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Foam_dev)](https://twitter.com/oam_dev) +[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kubevela)](https://artifacthub.io/packages/search?repo=kubevela) ![alt](docs/resources/KubeVela-03.png) diff --git a/hack/artifacthub/artifacthub-repo.yml b/hack/artifacthub/artifacthub-repo.yml new file mode 100644 index 000000000..1cffa6cbd --- /dev/null +++ b/hack/artifacthub/artifacthub-repo.yml @@ -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 From 671c73a0704e4f13851f3c427cb22799f0b253e1 Mon Sep 17 00:00:00 2001 From: 96RadhikaJadhav Date: Tue, 2 Feb 2021 14:20:11 +0530 Subject: [PATCH 07/38] Fixed wrong chart name --- pkg/commands/system.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/system.go b/pkg/commands/system.go index cdc22d143..5f3df940a 100644 --- a/pkg/commands/system.go +++ b/pkg/commands/system.go @@ -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 @@ -282,7 +282,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 not found in your kubernetes cluster, try `vela install` to install") } // PrintTrackVelaRuntimeStatus prints status of installing vela-core runtime From dd6810314e96da7f8ec656ff7029b1c49aadcf73 Mon Sep 17 00:00:00 2001 From: lj176172 Date: Fri, 29 Jan 2021 19:21:47 +0800 Subject: [PATCH 08/38] status app --- pkg/appfile/parser.go | 38 +++++++++++++ pkg/commands/init.go | 7 ++- pkg/commands/status.go | 97 +++++++++++++--------------------- pkg/dsl/definition/template.go | 54 +++++++++++++++++++ pkg/dsl/model/instance.go | 17 +++++- pkg/oam/util/template.go | 21 +++++--- 6 files changed, 166 insertions(+), 68 deletions(-) diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index a8c1fd8cd..392a7048a 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -77,6 +77,7 @@ type Trait struct { Params map[string]interface{} Template string Health string + Status string } // EvalContext eval trait template and set result to context @@ -84,6 +85,11 @@ func (trait *Trait) EvalContext(ctx process.Context) error { return definition.NewTDTemplater(trait.Name, trait.Template, "").Params(trait.Params).Complete(ctx) } +// EvalStatus eval trait status +func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) { + return definition.NewTDTemplater(trait.Name, "", "").Status(ctx, cli, ns, trait.Status) +} + // 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() @@ -188,6 +194,7 @@ func (p *Parser) parseTrait(name string, properties map[string]interface{}) (*Tr Params: properties, Template: templ.TemplateStr, Health: templ.Health, + Status: templ.CustomStatus, }, nil } @@ -244,6 +251,37 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a return appconfig, components, nil } +// PrintApplicationComponents print appComponent status for application +func PrintApplicationComponents(app *Appfile, cli client.Client, ns string, + printer func(compName string, appName string, traitsStatus map[string]string) error) error { + + for _, wl := range app.Workloads { + traitsStatus := map[string]string{} + pCtx, err := PrepareProcessContext(cli, wl, app.Name, ns) + if err != nil { + return err + } + for _, tr := range wl.Traits { + if err := tr.EvalContext(pCtx); err != nil { + return err + } + } + + for _, tr := range wl.Traits { + status, err := tr.EvalStatus(pCtx, cli, ns) + if err != nil { + return errors.WithMessagef(err, "[%s.%s] eval error", wl.Name, tr.Name) + } + + traitsStatus[tr.Name] = status + } + if err := printer(wl.Name, app.Name, traitsStatus); err != nil { + return err + } + } + return nil +} + // evalWorkloadWithContext evaluate the workload's template to generate component and ACComponent func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) { base, assists := pCtx.Output() diff --git a/pkg/commands/init.go b/pkg/commands/init.go index b0dff468d..c40305676 100644 --- a/pkg/commands/init.go +++ b/pkg/commands/init.go @@ -19,6 +19,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/discoverymapper" "github.com/oam-dev/kubevela/pkg/plugins" "github.com/oam-dev/kubevela/pkg/serverlib" "github.com/oam-dev/kubevela/pkg/utils/env" @@ -103,8 +104,12 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { if deployStatus != compStatusDeployed { return nil } + dm, err := discoverymapper.New(c.Config) + if err != nil { + return err + } - return printComponentStatus(context.Background(), o.client, o.IOStreams, o.workloadName, o.appName, o.Env) + return printAppStatus(context.Background(), newClient, dm, ioStreams, o.appName, o.Env, cmd) }, Annotations: map[string]string{ types.TagCommandType: types.TypeStart, diff --git a/pkg/commands/status.go b/pkg/commands/status.go index a46e26302..154cf662f 100644 --- a/pkg/commands/status.go +++ b/pkg/commands/status.go @@ -19,7 +19,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" + "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" oam2 "github.com/oam-dev/kubevela/pkg/serverlib" ) @@ -103,7 +103,11 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma if err != nil { return err } - return printAppStatus(ctx, newClient, ioStreams, appName, env, cmd) + dm, err := discoverymapper.New(c.Config) + if err != nil { + return err + } + return printAppStatus(ctx, newClient, dm, ioStreams, appName, env, cmd) }, Annotations: map[string]string{ types.TagCommandType: types.TypeApp, @@ -114,17 +118,12 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma return cmd } -func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error { +func printAppStatus(ctx context.Context, c client.Client, dm discoverymapper.DiscoveryMapper, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error { app, err := appfile.LoadApplication(env.Name, appName) 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() @@ -136,16 +135,36 @@ func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOSt cmd.Printf("Services:\n\n") - for _, svcName := range targetServices { - if err := printComponentStatus(ctx, c, ioStreams, svcName, appName, env); err != nil { - return err - } + remoteApp, err := loadRemoteApplication(c, namespace, appName) + if err != nil { + return err } - return nil + parser := appfile.NewApplicationParser(c, dm) + appFile, err := parser.GenerateAppFile(appName, remoteApp) + if err != nil { + return err + } + return appfile.PrintApplicationComponents(appFile, c, namespace, componentPrinter(ctx, c, ioStreams, env)) } -func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) error { +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 componentPrinter(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, env *types.EnvMeta) func(compName string, appName string, traitsStatus map[string]string) error { + return func(compName string, appName string, traitsStatus map[string]string) error { + return printComponentStatus(ctx, c, ioStreams, compName, appName, env, traitsStatus) + } +} + +func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName string, appName string, env *types.EnvMeta, traitsStatus map[string]string) error { app, appConfig, err := getAppConfig(ctx, c, compName, appName, env) if err != nil { return err @@ -173,14 +192,9 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti // 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) - if err != nil { - ioStreams.Infof(" - %s%s: %s, err: %v", emojiFail, white.Sprint(traitType), traitInfo, err) - continue - } - ioStreams.Infof(" - %s%s: %s", emojiSucceed, white.Sprint(traitType), traitInfo) + + for traitType, traitInfo := range traitsStatus { + ioStreams.Infof(" - %s: %s", white.Sprint(traitType), traitInfo) } ioStreams.Info("") ioStreams.Infof(" Last Deployment:\n") @@ -189,43 +203,6 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti 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 diff --git a/pkg/dsl/definition/template.go b/pkg/dsl/definition/template.go index 4014072de..51f713d70 100644 --- a/pkg/dsl/definition/template.go +++ b/pkg/dsl/definition/template.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/build" @@ -25,6 +26,8 @@ const ( OutputFieldName = "output" // OutputsFieldName is the name of the struct contains the map[string]CR data OutputsFieldName = "outputs" + // OutputObjectPath is the path of output object in template + OutputObjectPath = "path" // PatchFieldName is the name of the struct contains the patch of CR data PatchFieldName = "patch" ) @@ -45,6 +48,7 @@ type Template interface { Complete(ctx process.Context) error Output(ctx process.Context, client client.Client, name string) Template HealthCheck() error + Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) } type def struct { @@ -174,6 +178,11 @@ func (wd *workloadDef) HealthCheck() error { return nil } +// Status get workload status +func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) { + return "", nil +} + type traitDef struct { def } @@ -232,6 +241,7 @@ func (td *traitDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "traitDef %s new Assist", td.name) } + other.SetTag(OutputObjectPath, OutputFieldName) ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) } @@ -247,6 +257,7 @@ func (td *traitDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "traitDef %s new Assists(%s)", td.name, fieldInfo.Name) } + other.SetTag(OutputObjectPath, strings.Join([]string{OutputsFieldName, fieldInfo.Name}, ".")) ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) } } @@ -266,6 +277,49 @@ func (td *traitDef) Complete(ctx process.Context) error { return nil } +// Status get trait status by handleTempl +func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) { + _, assists := ctx.Output() + var root = map[string]interface{}{} + for _, assist := range assists { + if assist.Type != td.name { + continue + } + traitRef, err := assist.Ins.Unstructured() + if err != nil { + return "", err + } + + if err := cli.Get(context.Background(), client.ObjectKey{ + Namespace: ns, + Name: traitRef.GetName(), + }, traitRef); err != nil { + return "", err + } + + paths := strings.Split(assist.Ins.GetTag(OutputObjectPath), ".") + + x := traitRef.Object + for i := len(paths) - 1; i >= 0; i-- { + x = map[string]interface{}{paths[i]: x} + } + for k, v := range x { + root[k] = v + } + } + + bt, _ := json.Marshal(root) + var buff = "context: " + string(bt) + + buff += "\n" + handleTempl + var r cue.Runtime + inst, err := r.Compile("-", buff) + if err != nil { + return "", err + } + return inst.Lookup("output").String() +} + // Output fetch the trait cr and set result to context func (td *traitDef) Output(ctx process.Context, client client.Client, name string) Template { _, assists := ctx.Output() diff --git a/pkg/dsl/model/instance.go b/pkg/dsl/model/instance.go index 467b7221e..35a2774bf 100644 --- a/pkg/dsl/model/instance.go +++ b/pkg/dsl/model/instance.go @@ -16,11 +16,14 @@ type Instance interface { IsBase() bool Unify(other Instance) error Compile() ([]byte, error) + SetTag(k, v string) + GetTag(k string) string } type instance struct { v string base bool + tags map[string]string } // String return instance's cue format string @@ -33,6 +36,16 @@ func (inst *instance) IsBase() bool { return inst.base } +// SetTag add or update tag for model +func (inst *instance) SetTag(k, v string) { + inst.tags[k] = v +} + +// GetTag get the tag of model by key +func (inst *instance) GetTag(k string) string { + return inst.tags[k] +} + func (inst *instance) Compile() ([]byte, error) { var r cue.Runtime cueInst, err := r.Compile("-", inst.v) @@ -82,6 +95,7 @@ func NewBase(v cue.Value) (Instance, error) { return &instance{ v: vs, base: true, + tags: map[string]string{}, }, nil } @@ -92,7 +106,8 @@ func NewOther(v cue.Value) (Instance, error) { return nil, err } return &instance{ - v: vs, + v: vs, + tags: map[string]string{}, }, nil } diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index 3149c3a37..441989605 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -18,6 +18,7 @@ import ( type Template struct { TemplateStr string Health string + CustomStatus string CapabilityCategory types.CapabilityCategory } @@ -46,7 +47,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if wd.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTemplAndHealth(wd.Spec.Extension.Raw) + tmpl, err := getTempl(wd.Spec.Extension.Raw) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -65,7 +66,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if td.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTemplAndHealth(td.Spec.Extension.Raw) + tmpl, err := getTempl(td.Spec.Extension.Raw) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -81,15 +82,23 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e return nil, fmt.Errorf("kind(%s) of %s not supported", kd, key) } -func getTemplAndHealth(raw []byte) (*Template, error) { +func getTempl(raw []byte) (*Template, error) { _tmp := map[string]interface{}{} if err := json.Unmarshal(raw, &_tmp); err != nil { return nil, err } - var health string + var ( + health string + status string + ) if _, ok := _tmp["healthPolicy"]; ok { health = fmt.Sprint(_tmp["healthPolicy"]) } - return &Template{TemplateStr: fmt.Sprint(_tmp["template"]), - Health: health}, nil + if _, ok := _tmp["customStatus"]; ok { + status = fmt.Sprint(_tmp["customStatus"]) + } + return &Template{ + TemplateStr: fmt.Sprint(_tmp["template"]), + Health: health, + CustomStatus: status}, nil } From 02f214b76614094448de98173cf891bca09be8c1 Mon Sep 17 00:00:00 2001 From: roywang Date: Tue, 2 Feb 2021 22:58:16 +0900 Subject: [PATCH 09/38] use dummy trait definition in appconfig webhook Signed-off-by: roywang --- .../applicationconfiguration/helper.go | 32 +++++++++-- .../applicationconfiguration/helper_test.go | 44 +++++++++++++++ .../validating_handler.go | 33 +---------- .../validating_handler_test.go | 56 ------------------- 4 files changed, 72 insertions(+), 93 deletions(-) diff --git a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper.go b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper.go index 8c526c408..2455ff44e 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" @@ -21,6 +22,7 @@ const ( errFmtUnmarshalWorkload = "cannot unmarshal workload of component %q" errFmtUnmarshalTrait = "cannot unmarshal trait of component %q" errFmtGetWorkloadDefinition = "cannot get workload definition of component %q" + errFmtCheckTrait = "failed checking trait of component %q" ) // ValidatingAppConfig is used for validating ApplicationConfiguration @@ -91,17 +93,22 @@ func (v *ValidatingAppConfig) PrepareForValidation(ctx context.Context, c client tmpT := ValidatingTrait{} tmpT.componentTrait = t // get trait content from raw - var tContentObject map[string]interface{} - if err := json.Unmarshal(t.Trait.Raw, &tContentObject); err != nil { + tContent := unstructured.Unstructured{} + if err := json.Unmarshal(t.Trait.Raw, &tContent.Object); err != nil { return errors.Wrapf(err, errFmtUnmarshalTrait, tmp.compName) } - tContent := unstructured.Unstructured{ - Object: tContentObject, + + if err := checkTraitObj(&tContent); err != nil { + return errors.Wrapf(err, errFmtCheckTrait, tmp.compName) } + // get trait definition tDef, err := util.FetchTraitDefinition(ctx, c, dm, &tContent) if err != nil { - return errors.Wrapf(err, errFmtGetTraitDefinition, tmp.compName) + if !k8serrors.IsNotFound(err) { + return errors.Wrapf(err, errFmtGetTraitDefinition, tmp.compName) + } + tDef = util.GetDummyTraitDefinition(&tContent) } tmpT.traitContent = tContent tmpT.traitDefinition = *tDef @@ -112,6 +119,21 @@ func (v *ValidatingAppConfig) PrepareForValidation(ctx context.Context, c client return nil } +// checkTraitObj checks trait whether it's muated correctly and has GVK. +// Further validation on traits should provieded by validators but not here. +func checkTraitObj(t *unstructured.Unstructured) error { + if t.Object[TraitTypeField] != nil { + return errors.New("the trait contains 'name' info that should be mutated to GVK") + } + if t.Object[TraitSpecField] != nil { + return errors.New("the trait contains 'properties' info that should be mutated to spec") + } + if len(t.GetAPIVersion()) == 0 || len(t.GetKind()) == 0 { + return errors.New("the trait data missing GVK") + } + return nil +} + // checkParams will check whether exist parameter assigning value to workload name func checkParams(cp []v1alpha2.ComponentParameter, cpv []v1alpha2.ComponentParameterValue) (bool, string) { targetParams := make(map[string]bool) diff --git a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper_test.go b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper_test.go index 00b6f4730..4582a487c 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper_test.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/helper_test.go @@ -6,11 +6,55 @@ import ( "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" ) +func TestCheckTraitObj(t *testing.T) { + traitWithName := unstructured.Unstructured{ + Object: make(map[string]interface{}), + } + unstructured.SetNestedField(traitWithName.Object, "test", TraitTypeField) + + traitWithProperties := unstructured.Unstructured{ + Object: make(map[string]interface{}), + } + unstructured.SetNestedField(traitWithProperties.Object, "test", TraitSpecField) + + traitWithoutGVK := unstructured.Unstructured{} + traitWithoutGVK.SetAPIVersion("") + traitWithoutGVK.SetKind("") + + tests := []struct { + caseName string + traitContent unstructured.Unstructured + want string + }{ + { + caseName: "the trait contains 'name' info that should be mutated to GVK", + traitContent: traitWithName, + want: "the trait contains 'name' info", + }, + { + caseName: "the trait contains 'properties' info that should be mutated to spec", + traitContent: traitWithProperties, + want: "the trait contains 'properties' info", + }, + { + caseName: "the trait data missing GVK", + traitContent: traitWithoutGVK, + want: "the trait data missing GVK", + }, + } + + for _, tc := range tests { + result := checkTraitObj(&tc.traitContent) + assert.Contains(t, result.Error(), tc.want, fmt.Sprintf("Test case: %q", tc.caseName)) + } +} + func TestCheckParams(t *testing.T) { wlNameValue := "wlName" pName := "wlnameParam" diff --git a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler.go b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler.go index 5657d2326..8e91b3091 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler.go @@ -10,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/klog" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -96,7 +95,7 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a } vAppConfig := &ValidatingAppConfig{} if err := vAppConfig.PrepareForValidation(ctx, h.Client, h.Mapper, obj); err != nil { - klog.Info("failed init appConfig before validation ", " name: ", obj.Name, " errMsg: ", err.Error()) + klog.Info("failed preparing information before validation ", " name: ", obj.Name, " errMsg: ", err.Error()) return admission.Denied(err.Error()) } for _, validator := range h.Validators { @@ -110,35 +109,6 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a return admission.ValidationResponse(true, "") } -// ValidateTraitObjectFn validates the ApplicationConfiguration on creation/update -func ValidateTraitObjectFn(_ context.Context, v ValidatingAppConfig) []error { - klog.Info("validate applicationConfiguration", "name", v.appConfig.Name) - var allErrs field.ErrorList - for cidx, comp := range v.validatingComps { - for idx, tr := range comp.validatingTraits { - fldPath := field.NewPath("spec").Child("components").Index(cidx).Child("traits").Index(idx).Child("trait") - content := tr.traitContent.Object - if content[TraitTypeField] != nil { - allErrs = append(allErrs, field.Invalid(fldPath, string(tr.componentTrait.Trait.Raw), - "the trait contains 'name' info that should be mutated to GVK")) - } - if content[TraitSpecField] != nil { - allErrs = append(allErrs, field.Invalid(fldPath, string(tr.componentTrait.Trait.Raw), - "the trait contains 'properties' info that should be mutated to spec")) - } - if len(tr.traitContent.GetAPIVersion()) == 0 || len(tr.traitContent.GetKind()) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath, content, - fmt.Sprintf("the trait data missing GVK, api = %s, kind = %s,", - tr.traitContent.GetAPIVersion(), tr.traitContent.GetKind()))) - } - } - } - if len(allErrs) > 0 { - return allErrs.ToAggregate().Errors() - } - return nil -} - // ValidateRevisionNameFn validates revisionName and componentName are assigned both. func ValidateRevisionNameFn(_ context.Context, v ValidatingAppConfig) []error { klog.Info("validate revisionName in applicationConfiguration", "name", v.appConfig.Name) @@ -313,7 +283,6 @@ func RegisterValidatingHandler(mgr manager.Manager) error { server.Register("/validating-core-oam-dev-v1alpha2-applicationconfigurations", &webhook.Admission{Handler: &ValidatingHandler{ Mapper: mapper, Validators: []AppConfigValidator{ - AppConfigValidateFunc(ValidateTraitObjectFn), AppConfigValidateFunc(ValidateRevisionNameFn), AppConfigValidateFunc(ValidateWorkloadNameForVersioningFn), AppConfigValidateFunc(ValidateTraitAppliableToWorkloadFn), diff --git a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler_test.go b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler_test.go index 88fe3ecc3..1eeaaaf1a 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler_test.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration/validating_handler_test.go @@ -11,7 +11,6 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -75,61 +74,6 @@ func TestValidateRevisionNameFn(t *testing.T) { } } -func TestValidateTraitObjectFn(t *testing.T) { - traitWithName := unstructured.Unstructured{ - Object: make(map[string]interface{}), - } - unstructured.SetNestedField(traitWithName.Object, "test", TraitTypeField) - - traitWithProperties := unstructured.Unstructured{ - Object: make(map[string]interface{}), - } - unstructured.SetNestedField(traitWithProperties.Object, "test", TraitSpecField) - - traitWithoutGVK := unstructured.Unstructured{} - traitWithoutGVK.SetAPIVersion("") - traitWithoutGVK.SetKind("") - - tests := []struct { - caseName string - traitContent unstructured.Unstructured - want string - }{ - { - caseName: "the trait contains 'name' info that should be mutated to GVK", - traitContent: traitWithName, - want: "the trait contains 'name' info", - }, - { - caseName: "the trait contains 'properties' info that should be mutated to spec", - traitContent: traitWithProperties, - want: "the trait contains 'properties' info", - }, - { - caseName: "the trait data missing GVK", - traitContent: traitWithoutGVK, - want: "the trait data missing GVK", - }, - } - - for _, tc := range tests { - vAppConfig := ValidatingAppConfig{ - validatingComps: []ValidatingComponent{ - { - validatingTraits: []ValidatingTrait{ - { - traitContent: tc.traitContent, - }, - }, - }, - }, - } - allErrs := ValidateTraitObjectFn(ctx, vAppConfig) - result := utilerrors.NewAggregate(allErrs).Error() - assert.Contains(t, result, tc.want, fmt.Sprintf("Test case: %q", tc.caseName)) - } -} - func TestValidateWorkloadNameForVersioningFn(t *testing.T) { workloadName := "wl-name" wlWithName := unstructured.Unstructured{} From 78a145fa7495ea17bd0b1d179c40c93258ca727f Mon Sep 17 00:00:00 2001 From: "Lei Zhang (Harry)" Date: Tue, 2 Feb 2021 10:31:30 -0800 Subject: [PATCH 10/38] Update docs/en/platform-engineers/overview.md Co-authored-by: Jianbo Sun --- docs/en/platform-engineers/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index 9b1d688c5..53a7fb305 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -80,7 +80,7 @@ Hence, it's straightforward that you could use KubeVela to create unified abstra 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 a 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. +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. From 5a2305b83eb5f23dcff1f4ab76cc911315447212 Mon Sep 17 00:00:00 2001 From: "Lei Zhang (Harry)" Date: Tue, 2 Feb 2021 10:31:50 -0800 Subject: [PATCH 11/38] Update docs/en/platform-engineers/overview.md Co-authored-by: Jianbo Sun --- docs/en/platform-engineers/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index 53a7fb305..318fa66f7 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -51,7 +51,7 @@ spec: bucket: "xxxxx" ``` -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 requirement, the platform team could customize module template in definitions by any time. +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 module template in definitions by any time. #### A Unified Abstraction For All From 5ae47074d1e9d359fc00ea672f191ed269c1fa37 Mon Sep 17 00:00:00 2001 From: "Lei Zhang (Harry)" Date: Tue, 2 Feb 2021 10:32:01 -0800 Subject: [PATCH 12/38] Update docs/en/platform-engineers/overview.md Co-authored-by: Jianbo Sun --- docs/en/platform-engineers/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index 318fa66f7..f8d842a4f 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -48,7 +48,7 @@ spec: image: "nginx" - name: bar type: aliyun-oss # component type - bucket: "xxxxx" + bucket: "my-bucket" ``` 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 module template in definitions by any time. From 62611896e1a02918ef33dc5dadc512fd66c0f7ca Mon Sep 17 00:00:00 2001 From: roy wang Date: Sun, 24 Jan 2021 18:05:21 +0900 Subject: [PATCH 13/38] validating webhook for TraitDefinition add unit test fix lint issues Signed-off-by: roy wang --- charts/vela-core/templates/webhook.yaml | 22 +++ pkg/webhook/core.oam.dev/register.go | 4 + .../traitdefinition/validating_handler.go | 140 ++++++++++++++++++ .../validating_handler_test.go | 123 +++++++++++++++ .../traitdefinition/validator_test.go | 69 +++++++++ 5 files changed, 358 insertions(+) create mode 100644 pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go create mode 100644 pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler_test.go create mode 100644 pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validator_test.go diff --git a/charts/vela-core/templates/webhook.yaml b/charts/vela-core/templates/webhook.yaml index f1cc75456..cafa1c831 100644 --- a/charts/vela-core/templates/webhook.yaml +++ b/charts/vela-core/templates/webhook.yaml @@ -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: diff --git a/pkg/webhook/core.oam.dev/register.go b/pkg/webhook/core.oam.dev/register.go index 0432c20eb..6b5a42919 100644 --- a/pkg/webhook/core.oam.dev/register.go +++ b/pkg/webhook/core.oam.dev/register.go @@ -7,6 +7,7 @@ import ( "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/applicationconfiguration" "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/applicationdeployment" "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/component" + "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition" ) // Register will be called in main and register all validation handlers @@ -17,6 +18,9 @@ func Register(mgr manager.Manager) error { if err := applicationconfiguration.RegisterValidatingHandler(mgr); err != nil { return err } + if err := traitdefinition.RegisterValidatingHandler(mgr); err != nil { + return err + } applicationconfiguration.RegisterMutatingHandler(mgr) applicationdeployment.RegisterMutatingHandler(mgr) if err := component.RegisterMutatingHandler(mgr); err != nil { diff --git a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go new file mode 100644 index 000000000..eb5df8312 --- /dev/null +++ b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go @@ -0,0 +1,140 @@ +package traitdefinition + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/pkg/errors" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" +) + +const ( + errValidateDefRef = "error occurs when validating definition reference" + + failInfoDefRefOmitted = "if definition reference is omitted, patch or output with GVK is required" +) + +var traitDefGVR = v1alpha2.SchemeGroupVersion.WithResource("traitdefinitions") + +// ValidatingHandler handles validation of trait definition +type ValidatingHandler struct { + Client client.Client + Mapper discoverymapper.DiscoveryMapper + + // Decoder decodes object + Decoder *admission.Decoder + // Validators validate objects + Validators []TraitDefValidator +} + +// TraitDefValidator validate trait definition +type TraitDefValidator interface { + Validate(context.Context, v1alpha2.TraitDefinition) error +} + +// TraitDefValidatorFn implements TraitDefValidator +type TraitDefValidatorFn func(context.Context, v1alpha2.TraitDefinition) error + +// Validate implements TraitDefValidator method +func (fn TraitDefValidatorFn) Validate(ctx context.Context, td v1alpha2.TraitDefinition) error { + return fn(ctx, td) +} + +var _ admission.Handler = &ValidatingHandler{} + +// Handle validate trait definition +func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response { + obj := &v1alpha2.TraitDefinition{} + if req.Resource.String() != traitDefGVR.String() { + return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", traitDefGVR)) + } + + if req.Operation == admissionv1beta1.Create || req.Operation == admissionv1beta1.Update { + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + klog.Info("validating ", " name: ", obj.Name, " operation: ", string(req.Operation)) + for _, validator := range h.Validators { + if err := validator.Validate(ctx, *obj); err != nil { + klog.Info("validation failed ", " name: ", obj.Name, " errMsgi: ", err.Error()) + return admission.Denied(err.Error()) + } + } + klog.Info("validation passed ", " name: ", obj.Name, " operation: ", string(req.Operation)) + } + return admission.ValidationResponse(true, "") +} + +var _ inject.Client = &ValidatingHandler{} + +// InjectClient injects the client into the ValidatingHandler +func (h *ValidatingHandler) InjectClient(c client.Client) error { + h.Client = c + return nil +} + +var _ admission.DecoderInjector = &ValidatingHandler{} + +// InjectDecoder injects the decoder into the ValidatingHandler +func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error { + h.Decoder = d + return nil +} + +// RegisterValidatingHandler will register TraitDefinition validation to webhook +func RegisterValidatingHandler(mgr manager.Manager) error { + server := mgr.GetWebhookServer() + mapper, err := discoverymapper.New(mgr.GetConfig()) + if err != nil { + return err + } + server.Register("/validating-core-oam-dev-v1alpha2-traitdefinitions", &webhook.Admission{Handler: &ValidatingHandler{ + Mapper: mapper, + Validators: []TraitDefValidator{ + TraitDefValidatorFn(ValidateDefinitionReference), + // add more validators here + }, + }}) + return nil +} + +// ValidateDefinitionReference validates whether the trait definition is valid if +// its `.spec.reference` field is unset. +// It's valid if +// it has at least one output, and all outputs must have GVK +// or it has no output but has a patch +// or it has a patch and outputs, and all outputs must have GVK +// TODO(roywang) currently we only validate whether it contains CUE template. +// Further validation, e.g., output with GVK, valid patch, etc, remains to be done. +func ValidateDefinitionReference(_ context.Context, td v1alpha2.TraitDefinition) error { + if len(td.Spec.Reference.Name) > 0 { + return nil + } + + if td.Spec.Extension == nil || len(td.Spec.Extension.Raw) < 1 { + return errors.New(failInfoDefRefOmitted) + } + + tmp := map[string]interface{}{} + if err := json.Unmarshal(td.Spec.Extension.Raw, &tmp); err != nil { + return errors.Wrap(err, errValidateDefRef) + } + template, ok := tmp["template"] + if !ok || len(fmt.Sprint(template)) < 1 { + return errors.New(failInfoDefRefOmitted) + } + return nil +} diff --git a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler_test.go b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler_test.go new file mode 100644 index 000000000..15b4565a6 --- /dev/null +++ b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler_test.go @@ -0,0 +1,123 @@ +package traitdefinition + +import ( + "context" + "encoding/json" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/pkg/errors" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" +) + +var handler ValidatingHandler +var req admission.Request +var reqResource metav1.GroupVersionResource +var decoder *admission.Decoder +var td v1alpha2.TraitDefinition +var tdRaw []byte +var scheme = runtime.NewScheme() + +func TestTraitdefinition(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Traitdefinition Suite") +} + +var _ = BeforeSuite(func(done Done) { + td = v1alpha2.TraitDefinition{} + td.SetGroupVersionKind(v1alpha2.TraitDefinitionGroupVersionKind) + tdRaw, _ = json.Marshal(td) + + var err error + decoder, err = admission.NewDecoder(scheme) + Expect(err).Should(BeNil()) + + close(done) +}) + +var _ = Describe("Test TraitDefinition validating handler", func() { + BeforeEach(func() { + reqResource = metav1.GroupVersionResource{ + Group: v1alpha2.Group, + Version: v1alpha2.Version, + Resource: "traitdefinitions"} + handler = ValidatingHandler{} + handler.InjectDecoder(decoder) + }) + + It("Test wrong resource of admission request", func() { + wrongReqResource := metav1.GroupVersionResource{ + Group: v1alpha2.Group, + Version: v1alpha2.Version, + Resource: "foos"} + req = admission.Request{ + AdmissionRequest: admissionv1beta1.AdmissionRequest{ + Operation: admissionv1beta1.Create, + Resource: wrongReqResource, + Object: runtime.RawExtension{Raw: []byte("")}, + }, + } + resp := handler.Handle(context.TODO(), req) + Expect(resp.Allowed).Should(BeFalse()) + }) + + It("Test bad admission request", func() { + req = admission.Request{ + AdmissionRequest: admissionv1beta1.AdmissionRequest{ + Operation: admissionv1beta1.Create, + Resource: reqResource, + Object: runtime.RawExtension{Raw: []byte("bad request")}, + }, + } + resp := handler.Handle(context.TODO(), req) + Expect(resp.Allowed).Should(BeFalse()) + }) + + Context("Test create/update operation admission request", func() { + var mockValidator TraitDefValidatorFn + It("Test validation passed", func() { + // mock a validator that always validates successfully + mockValidator = func(_ context.Context, _ v1alpha2.TraitDefinition) error { + return nil + } + handler.Validators = []TraitDefValidator{ + TraitDefValidatorFn(mockValidator), + } + req = admission.Request{ + AdmissionRequest: admissionv1beta1.AdmissionRequest{ + Operation: admissionv1beta1.Create, + Resource: reqResource, + Object: runtime.RawExtension{Raw: tdRaw}, + }, + } + resp := handler.Handle(context.TODO(), req) + Expect(resp.Allowed).Should(BeTrue()) + }) + It("Test validation failed", func() { + // mock a validator that always failed + mockValidator = func(_ context.Context, _ v1alpha2.TraitDefinition) error { + return errors.New("mock validator error") + } + handler.Validators = []TraitDefValidator{ + TraitDefValidatorFn(mockValidator), + } + req = admission.Request{ + AdmissionRequest: admissionv1beta1.AdmissionRequest{ + Operation: admissionv1beta1.Create, + Resource: reqResource, + Object: runtime.RawExtension{Raw: tdRaw}, + }, + } + resp := handler.Handle(context.TODO(), req) + Expect(resp.Allowed).Should(BeFalse()) + Expect(resp.Result.Reason).Should(Equal(metav1.StatusReason("mock validator error"))) + }) + }) +}) diff --git a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validator_test.go b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validator_test.go new file mode 100644 index 000000000..16c0f023c --- /dev/null +++ b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validator_test.go @@ -0,0 +1,69 @@ +package traitdefinition + +import ( + "context" + "fmt" + "testing" + + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + + "github.com/oam-dev/kubevela/pkg/oam/util" +) + +func TestValidateDefinitionReference(t *testing.T) { + cases := map[string]struct { + reason string + template string + want error + }{ + "NoExtension": { + reason: "An error should be returned if extension is omitted", + template: "", + want: errors.New(failInfoDefRefOmitted), + }, + "HaveExtentsion_NoTemplate": { + reason: "An error should be returned if extension template is omitted", + template: ` + extension: + notemplate: |- + fakefield: fakefieldvalue`, + want: errors.New(failInfoDefRefOmitted), + }, + "HaveExtension_HaveTemplate": { + reason: "No error should be returned if have CUE template", + template: ` + extension: + template: |- + patch: { + spec: replicas: parameter.replicas + }`, + want: nil, + }, + } + + for caseName, tc := range cases { + t.Run(caseName, func(t *testing.T) { + tdStr := traitDefStringWithTemplate(tc.template) + td, err := util.UnMarshalStringToTraitDefinition(tdStr) + if err != nil { + t.Fatal("error occurs in generating TraitDefinition string", err.Error()) + } + err = ValidateDefinitionReference(context.Background(), *td) + if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\nValidateDefinitionReference: -want , +got \n%s\n", tc.reason, diff) + } + }) + } +} + +func traitDefStringWithTemplate(t string) string { + return fmt.Sprintf(` +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: scaler +spec: +%s`, t) +} From dd04402a28117d0224611322cf4dee90471819be Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Wed, 3 Feb 2021 14:27:04 +0800 Subject: [PATCH 14/38] Build KubeVela legacy chart (#983) * Build KubeVela legacy chart Also pushed it to oss bucket fix #528 * remvoe flag `--devel` * make it explictly to modify values.yaml for legacy chart --- .github/workflows/registry.yml | 28 ++++++++++++----- charts/vela-core/Chart.yaml | 2 +- legacy/README.md | 38 +++++++++++++++++++++++ legacy/charts/vela-core-legacy/Chart.yaml | 21 +++++++++++++ 4 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 legacy/README.md create mode 100644 legacy/charts/vela-core-legacy/Chart.yaml diff --git a/.github/workflows/registry.yml b/.github/workflows/registry.yml index a2e71845c..97c0999bd 100644 --- a/.github/workflows/registry.yml +++ b/.github/workflows/registry.yml @@ -64,6 +64,10 @@ 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 runs-on: ubuntu-20.04 steps: - uses: actions/checkout@master @@ -84,26 +88,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 .oss/ - name: add artifacthub stuff to the repo run: | - rsync docs/en/install.md charts/vela-core/README.md - sed -i '' "s/ARTIFACT_HUB_REPOSITORY_ID/$ARTIFACT_HUB_REPOSITORY_ID/g" hack/artifacthub/artifacthub-repo.yml + 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 ./oss - 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 .oss/ + helm package $LEGACY_HELM_CHART --destination .oss/ + helm repo index --url https://$BUCKET.$ENDPOINT/core .oss/ - name: sync local to cloud - run: ./ossutil --config-file .ossutilconfig sync .oss/ oss://kubevelacharts/core -f \ No newline at end of file + run: ./ossutil --config-file .ossutilconfig sync .oss/ oss://$BUCKET/core -f diff --git a/charts/vela-core/Chart.yaml b/charts/vela-core/Chart.yaml index 2b8ea8a43..66e3db5ca 100644 --- a/charts/vela-core/Chart.yaml +++ b/charts/vela-core/Chart.yaml @@ -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. # diff --git a/legacy/README.md b/legacy/README.md new file mode 100644 index 000000000..a80d4c882 --- /dev/null +++ b/legacy/README.md @@ -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 +``` diff --git a/legacy/charts/vela-core-legacy/Chart.yaml b/legacy/charts/vela-core-legacy/Chart.yaml new file mode 100644 index 000000000..a58bb8e9f --- /dev/null +++ b/legacy/charts/vela-core-legacy/Chart.yaml @@ -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 From dafbbbb6066a46824c75fd5dbb3e6871d405dbdf Mon Sep 17 00:00:00 2001 From: Kai Date: Wed, 3 Feb 2021 16:14:58 +0800 Subject: [PATCH 15/38] mark `vela install` as deprecated (#989) * mark `vela install` as deprecated * refactor vela install to helm install * refactor vela install to helm install fix * Update pkg/commands/system.go Co-authored-by: Jianbo Sun * Update pkg/commands/system.go Co-authored-by: Jianbo Sun * Update README.md * Update CONTRIBUTING.md Co-authored-by: Jianbo Sun * Update _sidebar.md * Update README.md * revert Co-authored-by: Jianbo Sun --- CONTRIBUTING.md | 2 +- pkg/commands/dashboard.go | 8 +++++--- pkg/commands/system.go | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcdedb727..44e53a71b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/pkg/commands/dashboard.go b/pkg/commands/dashboard.go index 253dd8161..2d69f9a64 100644 --- a/pkg/commands/dashboard.go +++ b/pkg/commands/dashboard.go @@ -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) diff --git a/pkg/commands/system.go b/pkg/commands/system.go index 5f3df940a..262a29777 100644 --- a/pkg/commands/system.go +++ b/pkg/commands/system.go @@ -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("kubevela 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 From f310665fe01f67ed6e5c1226eaa5341df9c60b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Mon, 1 Feb 2021 10:43:44 +0800 Subject: [PATCH 16/38] implement vela status in application CRD controller and refactor the health check code --- .../v1alpha2/application_types.go | 20 + .../v1alpha2/zz_generated.deepcopy.go | 42 ++ .../crds/core.oam.dev_applications.yaml | 31 ++ .../crds/core.oam.dev_applications.yaml | 31 ++ pkg/appfile/addon_test.go | 2 +- pkg/appfile/api/appfile.go | 7 +- pkg/appfile/parser.go | 100 +++-- pkg/appfile/parser_test.go | 12 +- pkg/commands/init.go | 8 +- pkg/commands/status.go | 187 ++++----- .../application/application_controller.go | 15 +- .../application_controller_test.go | 134 ++++--- .../v1alpha2/application/apply.go | 62 ++- .../v1alpha2/application/suite_test.go | 2 +- pkg/dsl/definition/template.go | 358 +++++++++--------- pkg/dsl/definition/template_test.go | 69 +++- pkg/dsl/model/instance.go | 17 +- pkg/dsl/process/handle.go | 89 +++-- pkg/dsl/process/handle_test.go | 10 +- pkg/oam/labels.go | 2 + pkg/oam/util/helper.go | 19 +- pkg/oam/util/template.go | 9 +- 22 files changed, 729 insertions(+), 497 deletions(-) diff --git a/apis/core.oam.dev/v1alpha2/application_types.go b/apis/core.oam.dev/v1alpha2/application_types.go index 1be6661a6..b2ce60a55 100644 --- a/apis/core.oam.dev/v1alpha2/application_types.go +++ b/apis/core.oam.dev/v1alpha2/application_types.go @@ -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 diff --git a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go index 3daf780b8..734506cca 100644 --- a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go @@ -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 @@ -414,6 +441,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 diff --git a/charts/vela-core/crds/core.oam.dev_applications.yaml b/charts/vela-core/crds/core.oam.dev_applications.yaml index 460de5c56..2259ac272 100644 --- a/charts/vela-core/crds/core.oam.dev_applications.yaml +++ b/charts/vela-core/crds/core.oam.dev_applications.yaml @@ -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 diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml index 02c9031a6..212456c3f 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml @@ -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 diff --git a/pkg/appfile/addon_test.go b/pkg/appfile/addon_test.go index c9c3e5dc0..6abdc6e72 100644 --- a/pkg/appfile/addon_test.go +++ b/pkg/appfile/addon_test.go @@ -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() { diff --git a/pkg/appfile/api/appfile.go b/pkg/appfile/api/appfile.go index 5c2a277d2..bb4248ff3 100644 --- a/pkg/appfile/api/appfile.go +++ b/pkg/appfile/api/appfile.go @@ -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 } diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index 392a7048a..8f9af44a9 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -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.NewTraitAbstractEngine(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,27 +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 - Status 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.NewTDTemplater(trait.Name, "", "").Status(ctx, cli, ns, trait.Status) + 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 @@ -148,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) @@ -193,8 +203,8 @@ func (p *Parser) parseTrait(name string, properties map[string]interface{}) (*Tr CapabilityCategory: templ.CapabilityCategory, Params: properties, Template: templ.TemplateStr, - Health: templ.Health, - Status: templ.CustomStatus, + HealthCheckPolicy: templ.Health, + CustomStatusFormat: templ.CustomStatus, }, nil } @@ -223,7 +233,7 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a return nil, nil, err } } - comp, acComp, err := evalWorkloadWithContext(pCtx, wl) + comp, acComp, err := evalWorkloadWithContext(pCtx, wl, app.Name, wl.Name) if err != nil { return nil, nil, err } @@ -251,52 +261,20 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a return appconfig, components, nil } -// PrintApplicationComponents print appComponent status for application -func PrintApplicationComponents(app *Appfile, cli client.Client, ns string, - printer func(compName string, appName string, traitsStatus map[string]string) error) error { - - for _, wl := range app.Workloads { - traitsStatus := map[string]string{} - pCtx, err := PrepareProcessContext(cli, wl, app.Name, ns) - if err != nil { - return err - } - for _, tr := range wl.Traits { - if err := tr.EvalContext(pCtx); err != nil { - return err - } - } - - for _, tr := range wl.Traits { - status, err := tr.EvalStatus(pCtx, cli, ns) - if err != nil { - return errors.WithMessagef(err, "[%s.%s] eval error", wl.Name, tr.Name) - } - - traitsStatus[tr.Name] = status - } - if err := printer(wl.Name, app.Name, traitsStatus); err != nil { - return err - } - } - return nil -} - // 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 } - 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 @@ -308,7 +286,15 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Comp if err != nil { return nil, nil, err } - 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, @@ -320,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} diff --git a/pkg/appfile/parser_test.go b/pkg/appfile/parser_test.go index 95f70b70d..851d3a6fb 100644 --- a/pkg/appfile/parser_test.go +++ b/pkg/appfile/parser_test.go @@ -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()) }) diff --git a/pkg/commands/init.go b/pkg/commands/init.go index c40305676..f9fd5f524 100644 --- a/pkg/commands/init.go +++ b/pkg/commands/init.go @@ -19,7 +19,6 @@ 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/discoverymapper" "github.com/oam-dev/kubevela/pkg/plugins" "github.com/oam-dev/kubevela/pkg/serverlib" "github.com/oam-dev/kubevela/pkg/utils/env" @@ -104,12 +103,7 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { if deployStatus != compStatusDeployed { return nil } - dm, err := discoverymapper.New(c.Config) - if err != nil { - return err - } - - return printAppStatus(context.Background(), newClient, dm, ioStreams, o.appName, o.Env, cmd) + return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Env, cmd) }, Annotations: map[string]string{ types.TagCommandType: types.TypeStart, diff --git a/pkg/commands/status.go b/pkg/commands/status.go index 154cf662f..ee6ec26e0 100644 --- a/pkg/commands/status.go +++ b/pkg/commands/status.go @@ -4,11 +4,11 @@ import ( "context" "fmt" "os" - "reflect" "strings" "time" - runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" + "github.com/oam-dev/kubevela/pkg/oam/util" + "github.com/fatih/color" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -19,8 +19,6 @@ 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/discoverymapper" - oam2 "github.com/oam-dev/kubevela/pkg/serverlib" ) // HealthStatus represents health status strings. @@ -46,10 +44,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 @@ -103,11 +97,7 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma if err != nil { return err } - dm, err := discoverymapper.New(c.Config) - if err != nil { - return err - } - return printAppStatus(ctx, newClient, dm, ioStreams, appName, env, cmd) + return printAppStatus(ctx, newClient, ioStreams, appName, env, cmd) }, Annotations: map[string]string{ types.TagCommandType: types.TypeApp, @@ -118,7 +108,7 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma return cmd } -func printAppStatus(ctx context.Context, c client.Client, dm discoverymapper.DiscoveryMapper, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error { +func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error { app, err := appfile.LoadApplication(env.Name, appName) if err != nil { return err @@ -134,18 +124,7 @@ func printAppStatus(ctx context.Context, c client.Client, dm discoverymapper.Dis cmd.Printf("%s\n\n", table.String()) cmd.Printf("Services:\n\n") - - remoteApp, err := loadRemoteApplication(c, namespace, appName) - if err != nil { - return err - } - - parser := appfile.NewApplicationParser(c, dm) - appFile, err := parser.GenerateAppFile(appName, remoteApp) - if err != nil { - return err - } - return appfile.PrintApplicationComponents(appFile, c, namespace, componentPrinter(ctx, c, ioStreams, env)) + return loopCheckStatus(ctx, c, ioStreams, appName, env) } func loadRemoteApplication(c client.Client, ns string, name string) (*v1alpha2.Application, error) { @@ -154,52 +133,62 @@ func loadRemoteApplication(c client.Client, ns string, name string) (*v1alpha2.A Namespace: ns, Name: name, }, app) - return app, err } -func componentPrinter(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, env *types.EnvMeta) func(compName string, appName string, traitsStatus map[string]string) error { - return func(compName string, appName string, traitsStatus map[string]string) error { - return printComponentStatus(ctx, c, ioStreams, compName, appName, env, traitsStatus) - } -} - -func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName string, appName string, env *types.EnvMeta, traitsStatus map[string]string) error { - app, appConfig, err := getAppConfig(ctx, c, compName, appName, env) +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 } - if app == nil || appConfig == nil { - return errors.New(ErrNotLoadAppConfig) - } - svc, ok := app.Services[compName] - if !ok { - return fmt.Errorf(ErrServiceNotFound, compName) - } - workloadType := svc.GetType() + 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 + 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) + + healthColor := getHealthStatusColor(healthStatus) + healthInfo = strings.ReplaceAll(healthInfo, "\n", "\n\t") // format healthInfo output + ioStreams.Infof(" %s %s\n", healthColor.Sprint(healthStatus), healthColor.Sprint(healthInfo)) + + // load it again after health check + remoteApp, err = loadRemoteApplication(c, env.Namespace, appName) + if err != nil { + return err + } + // 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.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") - - for traitType, traitInfo := range traitsStatus { - ioStreams.Infof(" - %s: %s", white.Sprint(traitType), traitInfo) - } - 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 } @@ -228,14 +217,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() @@ -291,30 +272,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, appName, env.Namespace) 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 @@ -337,38 +310,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) { @@ -390,14 +339,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 diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index 29050fa9d..067270d24 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -24,6 +24,7 @@ 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" ctrl "sigs.k8s.io/controller-runtime" @@ -82,7 +83,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { applog.Info("Start Rendering") app.Status.Phase = v1alpha2.ApplicationRendering - handler := &reter{r.Client, app, applog} + handler := &appHandler{r.Client, app, applog} app.Status.Conditions = []v1alpha1.Condition{} @@ -109,7 +110,6 @@ 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 if err := handler.apply(ctx, ac, comps); err != nil { @@ -120,12 +120,21 @@ 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.SetConditions(readyCondition("HealthCheck")) app.Status.Phase = v1alpha2.ApplicationRunning diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index 7559c2572..1e8eb11d6 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -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", @@ -792,6 +827,9 @@ var _ = Describe("Test Application Controller", func() { 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 +1008,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" @@ -1108,7 +1146,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" diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go index 164fc441d..1cd7cfd00 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go @@ -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,13 +39,13 @@ func readyCondition(tpy string) runtimev1alpha1.Condition { } } -type reter struct { +type appHandler struct { c client.Client app *v1alpha2.Application l logr.Logger } -func (ret *reter) Err(err error) (ctrl.Result, error) { +func (ret *appHandler) Err(err error) (ctrl.Result, error) { nerr := ret.c.Status().Update(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,60 @@ 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, + } + 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.c, 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.c, 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, } + traitHealth, err := trait.EvalHealth(pCtx, ret.c, 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.c, 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,7 +163,7 @@ 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 { return err diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go index dde711750..3fce396c3 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/suite_test.go @@ -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" diff --git a/pkg/dsl/definition/template.go b/pkg/dsl/definition/template.go index 51f713d70..0ccba95d5 100644 --- a/pkg/dsl/definition/template.go +++ b/pkg/dsl/definition/template.go @@ -4,21 +4,18 @@ import ( "context" "encoding/json" "fmt" - "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/build" "github.com/pkg/errors" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/pkg/dsl/model" "github.com/oam-dev/kubevela/pkg/dsl/process" "github.com/oam-dev/kubevela/pkg/dsl/task" "github.com/oam-dev/kubevela/pkg/oam" + "github.com/oam-dev/kubevela/pkg/oam/util" ) const ( @@ -26,10 +23,12 @@ const ( OutputFieldName = "output" // OutputsFieldName is the name of the struct contains the map[string]CR data OutputsFieldName = "outputs" - // OutputObjectPath is the path of output object in template - OutputObjectPath = "path" // PatchFieldName is the name of the struct contains the patch of CR data PatchFieldName = "patch" + // CustomMessage defines the custom message in definition template + CustomMessage = "message" + // HealthCheckPolicy defines the health check policy in definition template + HealthCheckPolicy = "isHealth" ) const ( @@ -38,54 +37,43 @@ const ( AuxiliaryWorkload = "AuxiliaryWorkload" ) -var ( - metadataAccessor = meta.NewAccessor() -) - -// Template defines Definition's Render interface -type Template interface { - Params(params interface{}) Template - Complete(ctx process.Context) error - Output(ctx process.Context, client client.Client, name string) Template - HealthCheck() error - Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) +// AbstractEngine defines Definition's Render interface +type AbstractEngine interface { + Params(params interface{}) AbstractEngine + Complete(ctx process.Context, abstractTemplate string) error + HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error) + Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string) (string, error) } type def struct { name string - templ string - health string params interface{} - output map[string]interface{} } type workloadDef struct { def } -// NewWDTemplater create Workload Definition templater -func NewWDTemplater(name, templ, health string) Template { +// NewWorkloadAbstractEngine create Workload Definition AbstractEngine +func NewWorkloadAbstractEngine(name string) AbstractEngine { return &workloadDef{ def: def{ name: name, - templ: templ, - health: health, params: nil, - output: nil, }, } } // Params set definition's params -func (wd *workloadDef) Params(params interface{}) Template { +func (wd *workloadDef) Params(params interface{}) AbstractEngine { wd.params = params return wd } // Complete do workload definition's rendering -func (wd *workloadDef) Complete(ctx process.Context) error { +func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string) error { bi := build.NewContext().NewInstance("", nil) - if err := bi.AddFile("-", wd.templ); err != nil { + if err := bi.AddFile("-", abstractTemplate); err != nil { return err } if wd.params != nil { @@ -95,7 +83,7 @@ func (wd *workloadDef) Complete(ctx process.Context) error { } } - if err := bi.AddFile("-", ctx.Compile("context")); err != nil { + if err := bi.AddFile("-", ctx.BaseContextFile()); err != nil { return err } insts := cue.Build([]*build.Instance{bi}) @@ -123,91 +111,146 @@ func (wd *workloadDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "parse WorkloadDefinition %s outputs(%s)", wd.name, fieldInfo.Name) } - ctx.PutAssistants(process.Assistant{Ins: other, Type: AuxiliaryWorkload}) + ctx.PutAuxiliaries(process.Auxiliary{Ins: other, Type: AuxiliaryWorkload, Name: fieldInfo.Name, IsOutputs: true}) } } } return nil } -// Output fetch the workload cr and set result to context -func (wd *workloadDef) Output(ctx process.Context, client client.Client, name string) Template { - base, _ := ctx.Output() +func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader, ns string) (map[string]interface{}, error) { + + var commonLabels = map[string]string{} + var root = map[string]interface{}{} + for k, v := range ctx.BaseContextLabels() { + root[k] = v + switch k { + case "appName": + commonLabels[oam.LabelAppName] = v + case "name": + commonLabels[oam.LabelAppComponent] = v + } + } + + base, assists := ctx.Output() componentWorkload, err := base.Unstructured() if err != nil { - return wd + return nil, err } - workloadCr, err := getObj(client, componentWorkload, name) + // workload main resource will have a unique label("app.oam.dev/resourceType"="WORKLOAD") in per component/app level + object, err := getResourceFromObj(componentWorkload, cli, ns, util.MergeMapOverrideWithDst(map[string]string{ + oam.LabelOAMResourceType: oam.ResourceTypeWorkload, + }, commonLabels), "") if err != nil { - return wd + return nil, err } - wd.output = workloadCr - return wd + root[OutputFieldName] = object + + for _, assist := range assists { + if assist.Type != AuxiliaryWorkload { + continue + } + if assist.Name == "" { + return nil, errors.New("the auxiliary of workload must have a name with format 'outputs.'") + } + traitRef, err := assist.Ins.Unstructured() + if err != nil { + return nil, err + } + // AuxiliaryWorkload will have a unique label("trait.oam.dev/resource"="name of outputs") in per component/app level + object, err := getResourceFromObj(traitRef, cli, ns, util.MergeMapOverrideWithDst(map[string]string{ + oam.TraitTypeLabel: AuxiliaryWorkload, + }, commonLabels), assist.Name) + if err != nil { + return nil, err + } + root[OutputsFieldName] = map[string]interface{}{ + assist.Name: object, + } + } + return root, nil } // HealthCheck address health check for workload -func (wd *workloadDef) HealthCheck() error { - if wd.health == "" { - return nil +func (wd *workloadDef) HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error) { + if healthPolicyTemplate == "" { + return true, nil } - bi := build.NewContext().NewInstance("", nil) - if err := bi.AddFile("-", wd.health); err != nil { - return err + templateContext, err := wd.getTemplateContext(ctx, cli, ns) + if err != nil { + return false, errors.WithMessage(err, "get template context") } - if wd.output != nil { - bt, _ := json.Marshal(wd.output) - if err := bi.AddFile(OutputFieldName, fmt.Sprintf("output: %s", string(bt))); err != nil { - return err - } - } else { - return errors.WithMessagef(errors.New("there is no workload output cr for health check"), "workload %s health check", wd.name) - } - insts := cue.Build([]*build.Instance{bi}) - for _, inst := range insts { - if err := inst.Value().Err(); err != nil { - return errors.WithMessagef(err, "workload %s check", wd.name) - } - isHealthVal := inst.Lookup("isHealth") - if isHealthVal.Exists() { - healthRs := isHealthVal.Eval() - if isHealth, err := healthRs.Bool(); err != nil || !isHealth { - return errors.WithMessage(err, "the workload is unhealthy") - } - } - } - return nil + return checkHealth(templateContext, healthPolicyTemplate) } -// Status get workload status -func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) { - return "", nil +func checkHealth(templateContext map[string]interface{}, healthPolicyTemplate string) (bool, error) { + bt, err := json.Marshal(templateContext) + if err != nil { + return false, errors.WithMessage(err, "json marshal template context") + } + + var buff = "context: " + string(bt) + "\n" + healthPolicyTemplate + var r cue.Runtime + inst, err := r.Compile("-", buff) + if err != nil { + return false, errors.WithMessage(err, "compile health template") + } + healthy, err := inst.Lookup(HealthCheckPolicy).Bool() + if err != nil { + return false, errors.WithMessage(err, "evaluate health status") + } + return healthy, nil +} + +// Status get workload status by customStatusTemplate +func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string) (string, error) { + if customStatusTemplate == "" { + return "", nil + } + templateContext, err := wd.getTemplateContext(ctx, cli, ns) + if err != nil { + return "", errors.WithMessage(err, "get template context") + } + return getStatusMessage(templateContext, customStatusTemplate) +} + +func getStatusMessage(templateContext map[string]interface{}, customStatusTemplate string) (string, error) { + bt, err := json.Marshal(templateContext) + if err != nil { + return "", errors.WithMessage(err, "json marshal template context") + } + var buff = "context: " + string(bt) + "\n" + customStatusTemplate + var r cue.Runtime + inst, err := r.Compile("-", buff) + if err != nil { + return "", err + } + return inst.Lookup(CustomMessage).String() } type traitDef struct { def } -// NewTDTemplater create Trait Definition templater -func NewTDTemplater(name, templ, health string) Template { +// NewTraitAbstractEngine create Trait Definition AbstractEngine +func NewTraitAbstractEngine(name string) AbstractEngine { return &traitDef{ def: def{ - name: name, - templ: templ, - health: health, + name: name, }, } } // Params set definition's params -func (td *traitDef) Params(params interface{}) Template { +func (td *traitDef) Params(params interface{}) AbstractEngine { td.params = params return td } // Complete do trait definition's rendering -func (td *traitDef) Complete(ctx process.Context) error { +func (td *traitDef) Complete(ctx process.Context, abstractTemplate string) error { bi := build.NewContext().NewInstance("", nil) - if err := bi.AddFile("-", td.templ); err != nil { + if err := bi.AddFile("-", abstractTemplate); err != nil { return err } if td.params != nil { @@ -217,7 +260,7 @@ func (td *traitDef) Complete(ctx process.Context) error { } } - if err := bi.AddFile("f", ctx.Compile("context")); err != nil { + if err := bi.AddFile("f", ctx.BaseContextFile()); err != nil { return err } insts := cue.Build([]*build.Instance{bi}) @@ -241,8 +284,7 @@ func (td *traitDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "traitDef %s new Assist", td.name) } - other.SetTag(OutputObjectPath, OutputFieldName) - ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) + ctx.PutAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, IsOutputs: false}) } outputs := inst.Lookup(OutputsFieldName) @@ -257,8 +299,7 @@ func (td *traitDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "traitDef %s new Assists(%s)", td.name, fieldInfo.Name) } - other.SetTag(OutputObjectPath, strings.Join([]string{OutputsFieldName, fieldInfo.Name}, ".")) - ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) + ctx.PutAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, Name: fieldInfo.Name, IsOutputs: true}) } } @@ -277,124 +318,91 @@ func (td *traitDef) Complete(ctx process.Context) error { return nil } -// Status get trait status by handleTempl -func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) { - _, assists := ctx.Output() +func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, ns string) (map[string]interface{}, error) { var root = map[string]interface{}{} + var commonLabels = map[string]string{} + for k, v := range ctx.BaseContextLabels() { + root[k] = v + switch k { + case "appName": + commonLabels[oam.LabelAppName] = v + case "name": + commonLabels[oam.LabelAppComponent] = v + } + } + _, assists := ctx.Output() for _, assist := range assists { if assist.Type != td.name { continue } traitRef, err := assist.Ins.Unstructured() if err != nil { - return "", err + return nil, err } - if err := cli.Get(context.Background(), client.ObjectKey{ - Namespace: ns, - Name: traitRef.GetName(), - }, traitRef); err != nil { - return "", err + object, err := getResourceFromObj(traitRef, cli, ns, util.MergeMapOverrideWithDst(map[string]string{ + oam.TraitTypeLabel: assist.Type, + }, commonLabels), assist.Name) + if err != nil { + return nil, err } - - paths := strings.Split(assist.Ins.GetTag(OutputObjectPath), ".") - - x := traitRef.Object - for i := len(paths) - 1; i >= 0; i-- { - x = map[string]interface{}{paths[i]: x} - } - for k, v := range x { - root[k] = v + if assist.IsOutputs { + root[OutputsFieldName] = map[string]interface{}{ + assist.Name: object, + } + } else { + root[OutputFieldName] = object } } - - bt, _ := json.Marshal(root) - var buff = "context: " + string(bt) - - buff += "\n" + handleTempl - var r cue.Runtime - inst, err := r.Compile("-", buff) - if err != nil { - return "", err - } - return inst.Lookup("output").String() + return root, nil } -// Output fetch the trait cr and set result to context -func (td *traitDef) Output(ctx process.Context, client client.Client, name string) Template { - _, assists := ctx.Output() - for _, assist := range assists { - if assist.Type != td.name { - continue - } - traitRef, err := assist.Ins.Unstructured() - if err != nil { - return td - } - traitCr, err := getObj(client, traitRef, name) - if err != nil { - return td - } - td.output = traitCr - return td +// Status get trait status by customStatusTemplate +func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string) (string, error) { + if customStatusTemplate == "" { + return "", nil } - return td + templateContext, err := td.getTemplateContext(ctx, cli, ns) + if err != nil { + return "", errors.WithMessage(err, "get template context") + } + return getStatusMessage(templateContext, customStatusTemplate) } // HealthCheck address health check for trait -func (td *traitDef) HealthCheck() error { - if td.health == "" { - return nil +func (td *traitDef) HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error) { + if healthPolicyTemplate == "" { + return true, nil } - bi := build.NewContext().NewInstance("", nil) - if err := bi.AddFile("-", td.health); err != nil { - return err + templateContext, err := td.getTemplateContext(ctx, cli, ns) + if err != nil { + return false, errors.WithMessage(err, "get template context") } - if td.output != nil { - bt, _ := json.Marshal(td.output) - if err := bi.AddFile("output", fmt.Sprintf("output: %s", string(bt))); err != nil { - return err - } - } else { - return errors.WithMessagef(errors.New("there is no trait output cr for health check"), "trait %s health check", td.name) - } - insts := cue.Build([]*build.Instance{bi}) - for _, inst := range insts { - if err := inst.Value().Err(); err != nil { - return errors.WithMessagef(err, "trait %s check", td.name) - } - isHealthVal := inst.Lookup("isHealth") - if isHealthVal.Exists() { - if isHealth, err := isHealthVal.Bool(); err != nil || !isHealth { - return errors.WithMessage(err, "the trait is unhealthy") - } - } - } - return nil + return checkHealth(templateContext, healthPolicyTemplate) } -func getObj(cli client.Client, obj runtime.Object, name string) (map[string]interface{}, error) { - var kind, apiVersion string - var err error - kind, err = metadataAccessor.Kind(obj) - if err != nil { - return nil, fmt.Errorf("cannot access object kind") +func getResourceFromObj(obj *unstructured.Unstructured, client client.Reader, namespace string, labels map[string]string, outputsResource string) (map[string]interface{}, error) { + if outputsResource != "" { + labels[oam.TraitResource] = outputsResource } - apiVersion, err = metadataAccessor.APIVersion(obj) - if err != nil { - return nil, fmt.Errorf("cannot access object kind") - } - unList := &unstructured.UnstructuredList{} - unList.SetKind(kind) - unList.SetAPIVersion(apiVersion) - if err := cli.List(context.Background(), unList, client.MatchingLabels{oam.LabelAppName: name}); err != nil { - if kerrors.IsNotFound(err) { - return nil, nil + if obj.GetName() != "" { + u, err := util.GetObjectGivenGVKAndName(context.Background(), client, obj.GroupVersionKind(), namespace, obj.GetName()) + if err != nil { + return nil, err } + return u.Object, nil + } + list, err := util.GetObjectsGivenGVKAndLabels(context.Background(), client, obj.GroupVersionKind(), namespace, labels) + if err != nil { return nil, err } - if len(unList.Items) == 0 { - return nil, nil + if len(list.Items) == 1 { + return list.Items[0].Object, nil } - return unList.Items[0].Object, nil + for _, v := range list.Items { + if v.GetLabels()[oam.TraitResource] == outputsResource { + return v.Object, nil + } + } + return nil, errors.Errorf("no resources found gvk(%v) labels(%v)", obj.GroupVersionKind(), labels) } diff --git a/pkg/dsl/definition/template_test.go b/pkg/dsl/definition/template_test.go index ddaef151b..785f3f8ce 100644 --- a/pkg/dsl/definition/template_test.go +++ b/pkg/dsl/definition/template_test.go @@ -3,7 +3,7 @@ package definition import ( "testing" - "github.com/bmizerany/assert" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -38,9 +38,9 @@ parameter: { } for _, v := range testCases { - ctx := process.NewContext("test") - wt := NewWDTemplater("-", v.templ, "") - if err := wt.Params(v.params).Complete(ctx); err != nil { + ctx := process.NewContext("test", "myapp") + wt := NewWorkloadAbstractEngine("-") + if err := wt.Params(v.params).Complete(ctx, v.templ); err != nil { t.Error(err) return } @@ -73,11 +73,11 @@ parameter: { replicas: *1 | int } ` - ctx := process.NewContext("test") - wt := NewWDTemplater("-", baseTemplate, "") + ctx := process.NewContext("test", "myapp") + wt := NewWorkloadAbstractEngine("-") if err := wt.Params(map[string]interface{}{ "replicas": 2, - }).Complete(ctx); err != nil { + }).Complete(ctx, baseTemplate); err != nil { t.Error(err) return } @@ -107,8 +107,8 @@ parameter: { } for _, v := range tds { - td := NewTDTemplater("-", v.templ, "") - if err := td.Params(v.params).Complete(ctx); err != nil { + td := NewTraitAbstractEngine("-") + if err := td.Params(v.params).Complete(ctx, v.templ); err != nil { t.Error(err) return } @@ -133,3 +133,54 @@ parameter: { }} assert.Equal(t, expect, obj) } + +func TestCheckHealth(t *testing.T) { + cases := map[string]struct { + tpContext map[string]interface{} + healthTemp string + exp bool + }{ + "normal-equal": { + tpContext: map[string]interface{}{ + "output": map[string]interface{}{ + "status": map[string]interface{}{ + "readyReplicas": 4, + "replicas": 4, + }, + }, + }, + healthTemp: "isHealth: context.output.status.readyReplicas == context.output.status.replicas", + exp: true, + }, + "normal-false": { + tpContext: map[string]interface{}{ + "output": map[string]interface{}{ + "status": map[string]interface{}{ + "readyReplicas": 4, + "replicas": 5, + }, + }, + }, + healthTemp: "isHealth: context.output.status.readyReplicas == context.output.status.replicas", + exp: false, + }, + "array-case-equal": { + tpContext: map[string]interface{}{ + "output": map[string]interface{}{ + "status": map[string]interface{}{ + "conditions": []interface{}{ + map[string]interface{}{"status": "True"}, + }, + }, + }, + }, + healthTemp: `isHealth: context.output.status.conditions[0].status == "True"`, + exp: true, + }, + } + for message, ca := range cases { + healthy, err := checkHealth(ca.tpContext, ca.healthTemp) + assert.NoError(t, err, message) + assert.Equal(t, ca.exp, healthy) + } +} diff --git a/pkg/dsl/model/instance.go b/pkg/dsl/model/instance.go index 35a2774bf..467b7221e 100644 --- a/pkg/dsl/model/instance.go +++ b/pkg/dsl/model/instance.go @@ -16,14 +16,11 @@ type Instance interface { IsBase() bool Unify(other Instance) error Compile() ([]byte, error) - SetTag(k, v string) - GetTag(k string) string } type instance struct { v string base bool - tags map[string]string } // String return instance's cue format string @@ -36,16 +33,6 @@ func (inst *instance) IsBase() bool { return inst.base } -// SetTag add or update tag for model -func (inst *instance) SetTag(k, v string) { - inst.tags[k] = v -} - -// GetTag get the tag of model by key -func (inst *instance) GetTag(k string) string { - return inst.tags[k] -} - func (inst *instance) Compile() ([]byte, error) { var r cue.Runtime cueInst, err := r.Compile("-", inst.v) @@ -95,7 +82,6 @@ func NewBase(v cue.Value) (Instance, error) { return &instance{ v: vs, base: true, - tags: map[string]string{}, }, nil } @@ -106,8 +92,7 @@ func NewOther(v cue.Value) (Instance, error) { return nil, err } return &instance{ - v: vs, - tags: map[string]string{}, + v: vs, }, nil } diff --git a/pkg/dsl/process/handle.go b/pkg/dsl/process/handle.go index ef2d91edd..bc17e8f08 100644 --- a/pkg/dsl/process/handle.go +++ b/pkg/dsl/process/handle.go @@ -12,55 +12,72 @@ import ( // Context defines Rendering Context Interface type Context interface { SetBase(base model.Instance) - PutAssistants(insts ...Assistant) + PutAuxiliaries(insts ...Auxiliary) SetConfigs(configs []map[string]string) - Output() (model.Instance, []Assistant) - Compile(label string) string + Output() (model.Instance, []Auxiliary) + BaseContextFile() string + BaseContextLabels() map[string]string } -// Assistant are objects rendered by definition template. -type Assistant struct { +// Auxiliary are objects rendered by definition template. +type Auxiliary struct { Ins model.Instance // Type will be used to mark definition label for OAM runtime to get the CRD // It's now required for trait and main workload object. Extra workload CR object will not have the type. Type string + + // Workload or trait with multiple `outputs` will have a name, if name is empty, than it's the main of this type. + Name string + + // IsOutputs will record the output path format of the Auxiliary + // it can be one of these two cases: + // false: the format is `output`, this means it's the main resource of the trait + // true: the format is `outputs.`, this means it can be auxiliary workload or trait + IsOutputs bool } -type context struct { - name string - configs []map[string]string - base model.Instance - assistants []Assistant +type templateContext struct { + // name is the component name of Application + name string + // appName is the name of Application + appName string + configs []map[string]string + base model.Instance + auxiliaries []Auxiliary + + // TODO(wonderflow): add a revision number here, and it should be a suffix combined with appName to be the name of AppConfig } -// NewContext create render context -func NewContext(name string) Context { - return &context{ - name: name, - configs: []map[string]string{}, - assistants: []Assistant{}, +// NewContext create render templateContext +func NewContext(name, appName string) Context { + return &templateContext{ + name: name, + appName: appName, + configs: []map[string]string{}, + auxiliaries: []Auxiliary{}, } } -// SetBase set context base model -func (ctx *context) SetConfigs(configs []map[string]string) { +// SetBase set templateContext base model +func (ctx *templateContext) SetConfigs(configs []map[string]string) { ctx.configs = configs } -// SetBase set context base model -func (ctx *context) SetBase(base model.Instance) { +// SetBase set templateContext base model +func (ctx *templateContext) SetBase(base model.Instance) { ctx.base = base } -// PutAssistants add Assist model to context -func (ctx *context) PutAssistants(insts ...Assistant) { - ctx.assistants = append(ctx.assistants, insts...) +// PutAuxiliaries add Assist model to templateContext +func (ctx *templateContext) PutAuxiliaries(auxiliaries ...Auxiliary) { + ctx.auxiliaries = append(ctx.auxiliaries, auxiliaries...) } -// Compile return cue format string of context -func (ctx *context) Compile(label string) string { +// BaseContextFile return cue format string of templateContext +func (ctx *templateContext) BaseContextFile() string { var buff string buff += fmt.Sprintf("name: \"%s\"\n", ctx.name) + buff += fmt.Sprintf("appName: \"%s\"\n", ctx.appName) if ctx.base != nil { buff += fmt.Sprintf("input: %s\n", structMarshal(ctx.base.String())) @@ -71,16 +88,22 @@ func (ctx *context) Compile(label string) string { buff += "config: " + string(bt) } - if label != "" { - buff = fmt.Sprintf("%s: %s", label, structMarshal(buff)) - } - - return buff + return fmt.Sprintf("context: %s", structMarshal(buff)) } -// Output return models of context -func (ctx *context) Output() (model.Instance, []Assistant) { - return ctx.base, ctx.assistants +func (ctx *templateContext) BaseContextLabels() map[string]string { + + return map[string]string{ + // appName is oam.LabelAppName + "appName": ctx.appName, + // name is oam.LabelAppComponent + "name": ctx.name, + } +} + +// GetK8sResource return models of templateContext +func (ctx *templateContext) Output() (model.Instance, []Auxiliary) { + return ctx.base, ctx.auxiliaries } func structMarshal(v string) string { diff --git a/pkg/dsl/process/handle_test.go b/pkg/dsl/process/handle_test.go index 2a4b2c3b4..b1aa264c8 100644 --- a/pkg/dsl/process/handle_test.go +++ b/pkg/dsl/process/handle_test.go @@ -26,9 +26,9 @@ image: "myserver" return } - ctx := NewContext("myctx") + ctx := NewContext("mycomp", "myapp") ctx.SetBase(base) - ctxInst, err := r.Compile("-", ctx.Compile("context")) + ctxInst, err := r.Compile("-", ctx.BaseContextFile()) if err != nil { t.Error(err) return @@ -36,7 +36,11 @@ image: "myserver" gName, err := ctxInst.Lookup("context", "name").String() assert.Equal(t, nil, err) - assert.Equal(t, "myctx", gName) + assert.Equal(t, "mycomp", gName) + + myAppName, err := ctxInst.Lookup("context", "appName").String() + assert.Equal(t, nil, err) + assert.Equal(t, "myapp", myAppName) inputJs, err := ctxInst.Lookup("context", "input").MarshalJSON() assert.Equal(t, nil, err) assert.Equal(t, `{"image":"myserver"}`, string(inputJs)) diff --git a/pkg/oam/labels.go b/pkg/oam/labels.go index 844dc0107..929554346 100644 --- a/pkg/oam/labels.go +++ b/pkg/oam/labels.go @@ -32,6 +32,8 @@ const ( WorkloadTypeLabel = "workload.oam.dev/type" // TraitTypeLabel indicates the type of the traitDefinition TraitTypeLabel = "trait.oam.dev/type" + // TraitResource indicates which resource it is when a trait is composed by multiple resources in KubeVela + TraitResource = "trait.oam.dev/resource" ) const ( diff --git a/pkg/oam/util/helper.go b/pkg/oam/util/helper.go index a79d85e26..81387aeb3 100644 --- a/pkg/oam/util/helper.go +++ b/pkg/oam/util/helper.go @@ -11,8 +11,6 @@ import ( "strings" "time" - "k8s.io/apimachinery/pkg/runtime" - cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" "github.com/davecgh/go-spew/spew" "github.com/go-logr/logr" @@ -23,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" 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/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" @@ -355,6 +354,22 @@ func GetGVKFromDefinition(dm discoverymapper.DiscoveryMapper, definitionRef v1al return kinds[0], nil } +// GetObjectsGivenGVKAndLabels fetches the kubernetes object given its gvk and labels by list API +func GetObjectsGivenGVKAndLabels(ctx context.Context, cli client.Reader, + gvk schema.GroupVersionKind, namespace string, labels map[string]string) (*unstructured.UnstructuredList, error) { + unstructuredObjList := &unstructured.UnstructuredList{} + apiVersion := metav1.GroupVersion{ + Group: gvk.Group, + Version: gvk.Version, + }.String() + unstructuredObjList.SetAPIVersion(apiVersion) + unstructuredObjList.SetKind(gvk.Kind) + if err := cli.List(ctx, unstructuredObjList, client.MatchingLabels(labels), client.InNamespace(namespace)); err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to get obj with labels %+v and gvk %+v ", labels, gvk)) + } + return unstructuredObjList, nil +} + // GetObjectGivenGVKAndName fetches the kubernetes object given its gvk and name func GetObjectGivenGVKAndName(ctx context.Context, client client.Reader, gvk schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error) { diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index 441989605..e7ee89e10 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -47,7 +47,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if wd.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTempl(wd.Spec.Extension.Raw) + tmpl, err := getTemplate(wd.Spec.Extension.Raw) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -66,7 +66,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if td.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTempl(td.Spec.Extension.Raw) + tmpl, err := getTemplate(td.Spec.Extension.Raw) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -82,7 +82,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e return nil, fmt.Errorf("kind(%s) of %s not supported", kd, key) } -func getTempl(raw []byte) (*Template, error) { +func getTemplate(raw []byte) (*Template, error) { _tmp := map[string]interface{}{} if err := json.Unmarshal(raw, &_tmp); err != nil { return nil, err @@ -100,5 +100,6 @@ func getTempl(raw []byte) (*Template, error) { return &Template{ TemplateStr: fmt.Sprint(_tmp["template"]), Health: health, - CustomStatus: status}, nil + CustomStatus: status, + }, nil } From 829d230427e3bf097ff853d14540edb7069e033e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Wed, 3 Feb 2021 18:19:51 +0800 Subject: [PATCH 17/38] remove unused client side trait checker --- pkg/serverlib/trait_checker.go | 217 --------------------------------- 1 file changed, 217 deletions(-) delete mode 100644 pkg/serverlib/trait_checker.go diff --git a/pkg/serverlib/trait_checker.go b/pkg/serverlib/trait_checker.go deleted file mode 100644 index 7478c9054..000000000 --- a/pkg/serverlib/trait_checker.go +++ /dev/null @@ -1,217 +0,0 @@ -package serverlib - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/oam-dev/kubevela/pkg/appfile/api" - - "github.com/oam-dev/kubevela/pkg/appfile" - - runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" - v12 "k8s.io/api/autoscaling/v1" - v1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" - "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" - autoscalers "github.com/oam-dev/kubevela/pkg/controller/standard.oam.dev/v1alpha1/autoscaler" - "github.com/oam-dev/kubevela/pkg/oam" -) - -// CheckStatus defines the type of checking status -type CheckStatus string - -const ( - // StatusChecking means in checking loop - StatusChecking = "checking" - // StatusDone means check has done - StatusDone = "done" -) - -// GetChecker will get Trait checker for 'vela status' -func GetChecker(traitType string, c client.Client) Checker { - switch traitType { - case "route": - return &RouteChecker{c: c} - case "metrics": - return &MetricChecker{c: c} - case "autoscale": - return &AutoscalerChecker{c: c} - } - - return &DefaultChecker{c: c} -} - -// Checker defines the interface of checker -type Checker interface { - Check(ctx context.Context, reference runtimev1alpha1.TypedReference, compName string, appConfig *v1alpha2.ApplicationConfiguration, app *api.Application) (CheckStatus, string, error) -} - -// DefaultChecker defines the default checker -type DefaultChecker struct { - c client.Client -} - -// Check default check object if exist and print the configs -func (d *DefaultChecker) Check(ctx context.Context, reference runtimev1alpha1.TypedReference, compName string, appConfig *v1alpha2.ApplicationConfiguration, app *api.Application) (CheckStatus, string, error) { - tr, err := GetUnstructured(ctx, d.c, appConfig.Namespace, reference) - if err != nil { - return StatusChecking, "", err - } - traitType, ok := tr.GetLabels()[oam.TraitTypeLabel] - if !ok { - message, err := GetStatusFromObject(tr) - return StatusDone, message, err - } - traitData, err := appfile.GetTraitsByType(app, compName, traitType) - if err != nil { - return StatusDone, err.Error(), err - } - var message string - for k, v := range traitData { - message += fmt.Sprintf("%v=%v\n\t\t", k, v) - } - return StatusDone, message, err -} - -// MetricChecker check for 'metrics' core trait -type MetricChecker struct { - c client.Client -} - -// Check metrics -func (d *MetricChecker) Check(ctx context.Context, reference runtimev1alpha1.TypedReference, _ string, appConfig *v1alpha2.ApplicationConfiguration, _ *api.Application) (CheckStatus, string, error) { - metric := v1alpha1.MetricsTrait{} - if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: reference.Name}, &metric); err != nil { - return StatusChecking, "", err - } - condition := metric.Status.Conditions - if len(condition) < 1 { - return StatusChecking, "", nil - } - if condition[0].Status != v1.ConditionTrue { - return StatusChecking, condition[0].Message, nil - } - if metric.Spec.ScrapeService.Enabled != nil && !*metric.Spec.ScrapeService.Enabled { - return StatusDone, "Monitoring disabled", nil - } - var message = fmt.Sprintf("Monitoring port: %s, path: %s, format: %s, schema: %s.", - metric.Status.Port.String(), metric.Spec.ScrapeService.Path, - metric.Spec.ScrapeService.Format, metric.Spec.ScrapeService.Scheme) - return StatusDone, message, nil -} - -// RouteChecker check for 'route' core trait -type RouteChecker struct { - c client.Client -} - -// Check understand route status -func (d *RouteChecker) Check(ctx context.Context, reference runtimev1alpha1.TypedReference, _ string, appConfig *v1alpha2.ApplicationConfiguration, _ *api.Application) (CheckStatus, string, error) { - route := v1alpha1.Route{} - if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: reference.Name}, &route); err != nil { - return StatusChecking, "", err - } - condition := route.Status.Conditions - if len(condition) < 1 { - return StatusChecking, "", nil - } - if condition[0].Status != v1.ConditionTrue { - return StatusChecking, condition[0].Message, nil - } - var message string - for _, ingress := range route.Status.Ingresses { - var in v1beta1.Ingress - if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: ingress.Name}, &in); err != nil { - return StatusChecking, "", err - } - value := in.Status.LoadBalancer.Ingress - if len(value) < 1 { - return StatusChecking, "", fmt.Errorf("%s IP not assigned yet", in.Name) - } - var url string - if len(in.Spec.TLS) >= 1 { - url = "https://" + in.Spec.Rules[0].Host - } else { - url = "http://" + in.Spec.Rules[0].Host - } - addr := value[0].IP - if value[0].Hostname != "" { - addr = value[0].Hostname - } - message += fmt.Sprintf("\tVisiting URL: %s\tIP: %s\n", url, addr) - } - if len(route.Status.Ingresses) == 0 { - message += fmt.Sprintf("Visiting by using 'vela port-forward %s --route'\n", appConfig.Name) - } - return StatusDone, message, nil -} - -// AutoscalerChecker checks 'autoscale' trait -type AutoscalerChecker struct { - c client.Client -} - -// Check should understand autoscale trait status -func (d *AutoscalerChecker) Check(ctx context.Context, ref runtimev1alpha1.TypedReference, _ string, appConfig *v1alpha2.ApplicationConfiguration, _ *api.Application) (CheckStatus, string, error) { - traitName := ref.Name - var scaler v1alpha1.Autoscaler - if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: traitName}, &scaler); err != nil { - return StatusChecking, "", err - } - var scalerType string - triggers := scaler.Spec.Triggers - if len(triggers) >= 1 { - scalerType = string(triggers[0].Type) - } - - hpaName := "keda-hpa-" + traitName - var hpa v12.HorizontalPodAutoscaler - if err := d.c.Get(ctx, client.ObjectKey{Namespace: appConfig.Namespace, Name: hpaName}, &hpa); err != nil { - return StatusChecking, "", err - } - message := fmt.Sprintf("type: %-8s", scalerType) - if scalerType == string(autoscalers.CPUType) { - // When attaching trait, and before the scaler trait works, `CurrentCPUUtilizationPercentage` is nil - currentCPUUtilizationPercentage := hpa.Status.CurrentCPUUtilizationPercentage - var zeroPercentage int32 = 0 - if currentCPUUtilizationPercentage == nil { - currentCPUUtilizationPercentage = &zeroPercentage - } - message += fmt.Sprintf("cpu-utilization(target/current): %v%%/%v%%\t", - *hpa.Spec.TargetCPUUtilizationPercentage, *currentCPUUtilizationPercentage) - } - message += fmt.Sprintf("replicas(min/max/current): %v/%v/%v", *hpa.Spec.MinReplicas, hpa.Spec.MaxReplicas, - hpa.Status.CurrentReplicas) - return StatusDone, message, nil -} - -// GetUnstructured get object by GVK. -func GetUnstructured(ctx context.Context, c client.Client, ns string, resourceRef runtimev1alpha1.TypedReference) (*unstructured.Unstructured, error) { - resource := unstructured.Unstructured{} - resource.SetGroupVersionKind(resourceRef.GroupVersionKind()) - if err := c.Get(ctx, client.ObjectKey{Namespace: ns, Name: resourceRef.Name}, &resource); err != nil { - return nil, err - } - return &resource, nil -} - -// GetStatusFromObject get Unstructured object status -func GetStatusFromObject(resource *unstructured.Unstructured) (string, error) { - var message string - statusData, foundStatus, _ := unstructured.NestedMap(resource.Object, "status") - if foundStatus { - statusJSON, err := json.Marshal(statusData) - if err != nil { - return "", err - } - message = string(statusJSON) - } else { - message = "status not found" - } - return fmt.Sprintf("%s status: %s", resource.GetName(), message), nil -} From fb15b4391476663ff033a2f0943cfb2578e745c7 Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Wed, 3 Feb 2021 19:03:08 +0800 Subject: [PATCH 18/38] Fix issue: artifacthub-repo.yml not uploaded to repo (#1003) * Fix issue: artifacthub-repo.yml not uploaded to repo * Update .github/workflows/registry.yml Co-authored-by: Jianbo Sun --- .github/workflows/registry.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/registry.yml b/.github/workflows/registry.yml index 97c0999bd..ebb197627 100644 --- a/.github/workflows/registry.yml +++ b/.github/workflows/registry.yml @@ -111,7 +111,9 @@ jobs: 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 ./oss + ls -lat ./oss/ + cp hack/artifacthub/artifacthub-repo.yml ./oss/ + ls -lat ./oss/ - name: Package helm charts run: | helm package $HELM_CHART --destination .oss/ From 2375e019bde3554b28ca27c60f1e7e92a240fef9 Mon Sep 17 00:00:00 2001 From: roy wang Date: Wed, 3 Feb 2021 17:43:32 +0900 Subject: [PATCH 19/38] fix cannot sync trait without definitionRef Signed-off-by: roy wang --- e2e/capability/capability_test.go | 49 +++++++++++++++++++++++++++---- pkg/commands/capability.go | 1 + pkg/plugins/capcenter.go | 4 +-- pkg/serverlib/capability.go | 18 +++++++----- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/e2e/capability/capability_test.go b/e2e/capability/capability_test.go index 2196c19f7..1fc68a7b3 100644 --- a/e2e/capability/capability_test.go +++ b/e2e/capability/capability_test.go @@ -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) diff --git a/pkg/commands/capability.go b/pkg/commands/capability.go index 50fb21b46..783e9e265 100644 --- a/pkg/commands/capability.go +++ b/pkg/commands/capability.go @@ -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 { diff --git a/pkg/plugins/capcenter.go b/pkg/plugins/capcenter.go index 190f85862..f4b739250 100644 --- a/pkg/plugins/capcenter.go +++ b/pkg/plugins/capcenter.go @@ -247,9 +247,9 @@ func (g *GithubCenter) SyncCapabilityFromCenter() error { continue } //nolint:gosec - err = ioutil.WriteFile(filepath.Join(repoDir, tmp.CrdName+".yaml"), data, 0644) + err = ioutil.WriteFile(filepath.Join(repoDir, tmp.Name+".yaml"), data, 0644) if err != nil { - fmt.Printf("write definition %s to %s err %v\n", tmp.CrdName+".yaml", repoDir, err) + fmt.Printf("write definition %s to %s err %v\n", tmp.Name+".yaml", repoDir, err) continue } success++ diff --git a/pkg/serverlib/capability.go b/pkg/serverlib/capability.go index 69e20f222..476660767 100644 --- a/pkg/serverlib/capability.go +++ b/pkg/serverlib/capability.go @@ -91,9 +91,9 @@ func InstallCapability(client client.Client, mapper discoverymapper.DiscoveryMap switch tp.Type { case types.TypeWorkload: var wd v1alpha2.WorkloadDefinition - workloadData, err := ioutil.ReadFile(filepath.Clean(filepath.Join(repoDir, tp.CrdName+".yaml"))) + workloadData, err := ioutil.ReadFile(filepath.Clean(filepath.Join(repoDir, tp.Name+".yaml"))) if err != nil { - return nil + return err } if err = yaml.Unmarshal(workloadData, &wd); err != nil { return err @@ -119,9 +119,9 @@ func InstallCapability(client client.Client, mapper discoverymapper.DiscoveryMap } case types.TypeTrait: var td v1alpha2.TraitDefinition - traitdata, err := ioutil.ReadFile(filepath.Clean(filepath.Join(repoDir, tp.CrdName+".yaml"))) + traitdata, err := ioutil.ReadFile(filepath.Clean(filepath.Join(repoDir, tp.Name+".yaml"))) if err != nil { - return nil + return err } if err = yaml.Unmarshal(traitdata, &td); err != nil { return err @@ -303,13 +303,17 @@ func uninstallCap(client client.Client, cap types.Capability, ioStreams cmdutil. capdir, _ := system.GetCapabilityDir() switch cap.Type { case types.TypeTrait: - return os.Remove(filepath.Join(capdir, "traits", cap.Name)) + if err := os.Remove(filepath.Join(capdir, "traits", cap.Name)); err != nil { + return err + } case types.TypeWorkload: - return os.Remove(filepath.Join(capdir, "workloads", cap.Name)) + if err := os.Remove(filepath.Join(capdir, "workloads", cap.Name)); err != nil { + return err + } case types.TypeScope: // TODO(wonderflow): add scope remove here. } - ioStreams.Infof("%s removed successfully", cap.Name) + ioStreams.Infof("Successfully uninstalled capability %s", cap.Name) return nil } From 51860cf11ab2fd27e1e0e894600d3405310b3ef4 Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Wed, 3 Feb 2021 19:51:34 +0800 Subject: [PATCH 20/38] Fix wrong target directory name for artifathub.io (#1004) --- .github/workflows/registry.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/registry.yml b/.github/workflows/registry.yml index ebb197627..140080392 100644 --- a/.github/workflows/registry.yml +++ b/.github/workflows/registry.yml @@ -68,6 +68,7 @@ jobs: 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 @@ -105,19 +106,17 @@ jobs: - 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://$BUCKET/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 - ls -lat ./oss/ - cp hack/artifacthub/artifacthub-repo.yml ./oss/ - ls -lat ./oss/ + rsync hack/artifacthub/artifacthub-repo.yml $LOCAL_OSS_DIRECTORY - name: Package helm charts run: | - helm package $HELM_CHART --destination .oss/ - helm package $LEGACY_HELM_CHART --destination .oss/ - helm repo index --url https://$BUCKET.$ENDPOINT/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://$BUCKET/core -f + run: ./ossutil --config-file .ossutilconfig sync $LOCAL_OSS_DIRECTORY oss://$BUCKET/core -f From 5aea4c4baa0a679c2064595657c78638fa0cba54 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Wed, 3 Feb 2021 10:57:56 +0800 Subject: [PATCH 21/38] Fix openAPIV3Schema validatation issue Legacy crd charts/vela-core/crds/standard.oam.dev_routes.yaml could not be applied to old Kubernetes clusters like 1.15.12. Fix #993 --- apis/generate.go | 2 +- .../crds/standard.oam.dev_rollouts.yaml | 349 ----------------- hack/crd/update.go | 84 ++--- .../crds/standard.oam.dev_rollouts.yaml | 350 ------------------ .../crds/standard.oam.dev_routes.yaml | 1 - 5 files changed, 41 insertions(+), 745 deletions(-) delete mode 100644 charts/vela-core/crds/standard.oam.dev_rollouts.yaml delete mode 100644 legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouts.yaml diff --git a/apis/generate.go b/apis/generate.go index 555d78324..bad72e0e7 100644 --- a/apis/generate.go +++ b/apis/generate.go @@ -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 diff --git a/charts/vela-core/crds/standard.oam.dev_rollouts.yaml b/charts/vela-core/crds/standard.oam.dev_rollouts.yaml deleted file mode 100644 index 7fd23f126..000000000 --- a/charts/vela-core/crds/standard.oam.dev_rollouts.yaml +++ /dev/null @@ -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: [] diff --git a/hack/crd/update.go b/hack/crd/update.go index a957525aa..1ce35d813 100644 --- a/hack/crd/update.go +++ b/hack/crd/update.go @@ -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 } diff --git a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouts.yaml b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouts.yaml deleted file mode 100644 index 9ef768ad7..000000000 --- a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouts.yaml +++ /dev/null @@ -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: [] diff --git a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml index bdc833f37..5faf081bb 100644 --- a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml +++ b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_routes.yaml @@ -116,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 From 966a773195f20e20639ea96ce37d2595e2ab01e4 Mon Sep 17 00:00:00 2001 From: Harry Zhang Date: Wed, 3 Feb 2021 09:05:37 -0800 Subject: [PATCH 22/38] Fix doc detail --- docs/en/platform-engineers/overview.md | 29 +++----------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index f8d842a4f..e197e5b18 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -1,4 +1,4 @@ -# KubeVela for Platform Builders +# KubeVela Under The Hood This documentation explains how KubeVela works in perspective of platform team. @@ -51,30 +51,11 @@ spec: bucket: "my-bucket" ``` -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 module template in definitions by any time. +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. #### A Unified Abstraction For All -KubeVela intends to support any possible module type by natural, for example `CUE`, `Terraform`, `Helm`, etc and of course by a plain `Kubernetes CRD`. In order to define modules and parameters orgnized, we also introduced a [`catalog` structure](https://github.com/oam-dev/kubevela/blob/master/design/vela-core/APIServer-Catalog.md#catalog-structure). KubeVela will load such catalog via Git repo URL. - -```console -/catalog/ # a catalog consists of multiple packages -|-- - |-- v1.0 # a package consists of multiple versions - |-- metadata.yaml - |-- definitions/ - |-- xxx-workload.yaml - |-- xxx-trait.yaml - |-- conditions/ - |-- check-crd.yaml - |-- hooks/ - |-- pre-install.yaml - |-- modules.yaml # could be helm, terraform, etc. - |-- v2.0 -|-- -``` - -Hence, it's straightforward that you could use KubeVela to create unified abstraction that can deploy any kind of resource, including cloud services, as long as they could be encapsulated by a module and placed in the catalog above. Actually, in the `application-sample` above it defined a OSS bucket on Alibaba Cloud for the other component to consume, this is powered by Terraform module. +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 @@ -95,7 +76,3 @@ The issue above could be even painful if the workload instance is not `Deploymen 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. - -### Deployment Engine - -The deployment engine is one of the operation controllers provided by KubeVela to handle progressive rollout of the application. More contents about it will come later. From 26aa3eceae1a94acb7361840d4dbb40e398ec0c3 Mon Sep 17 00:00:00 2001 From: Vaibhav Kaushik Date: Thu, 4 Feb 2021 02:13:25 +0530 Subject: [PATCH 23/38] Fix link in APIServer-Catalog Examples --- design/vela-core/APIServer-Catalog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/vela-core/APIServer-Catalog.md b/design/vela-core/APIServer-Catalog.md index d5a033b69..4e788a8c9 100644 --- a/design/vela-core/APIServer-Catalog.md +++ b/design/vela-core/APIServer-Catalog.md @@ -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 From 026d5f644613f80dadcfb890b6f5a6e183b9ae13 Mon Sep 17 00:00:00 2001 From: Vaibhav Kaushik Date: Thu, 4 Feb 2021 02:26:54 +0530 Subject: [PATCH 24/38] Minor fix for APIServe-Catalog Doc --- design/vela-core/APIServer-Catalog.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/design/vela-core/APIServer-Catalog.md b/design/vela-core/APIServer-Catalog.md index 4e788a8c9..a878abc1f 100644 --- a/design/vela-core/APIServer-Catalog.md +++ b/design/vela-core/APIServer-Catalog.md @@ -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/` 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 ![alt](../../docs/resources/catalog-workflow.jpg) -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 @@ -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. From 87e52bb349485b8ac766183fd6b7564602ac5471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Wed, 3 Feb 2021 20:32:29 +0800 Subject: [PATCH 25/38] add demo, test and refine code --- config/samples/app-with-status/app.yaml | 21 ++ config/samples/app-with-status/template.yaml | 122 +++++++ pkg/appfile/parser.go | 2 +- .../application/application_controller.go | 4 +- .../application_controller_test.go | 102 ++++++ .../v1alpha2/application/apply.go | 6 +- pkg/dsl/definition/template.go | 112 ++++--- pkg/dsl/definition/template_test.go | 304 +++++++++++++----- pkg/dsl/process/handle.go | 6 +- 9 files changed, 551 insertions(+), 128 deletions(-) create mode 100644 config/samples/app-with-status/app.yaml create mode 100644 config/samples/app-with-status/template.yaml diff --git a/config/samples/app-with-status/app.yaml b/config/samples/app-with-status/app.yaml new file mode 100644 index 000000000..f2f1917f5 --- /dev/null +++ b/config/samples/app-with-status/app.yaml @@ -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 \ No newline at end of file diff --git a/config/samples/app-with-status/template.yaml b/config/samples/app-with-status/template.yaml new file mode 100644 index 000000000..1252663ee --- /dev/null +++ b/config/samples/app-with-status/template.yaml @@ -0,0 +1,122 @@ +# Code generated by KubeVela templates. DO NOT EDIT. +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: worker + 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 + extension: + 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: + extension: + 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 + } + } + ] + } + }] + } + } \ No newline at end of file diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index 8f9af44a9..a9278b8e0 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -63,7 +63,7 @@ func (wl *Workload) EvalContext(ctx process.Context) error { // EvalStatus eval workload status func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) { - return definition.NewTraitAbstractEngine(wl.Name).Status(ctx, cli, ns, wl.CustomStatusFormat) + return definition.NewWorkloadAbstractEngine(wl.Name).Status(ctx, cli, ns, wl.CustomStatusFormat) } // EvalHealth eval workload health check diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index 067270d24..67cabba81 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -111,7 +111,7 @@ 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 + // 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)) @@ -135,7 +135,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { // 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 diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index 1e8eb11d6..3bcd13d9b 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -822,6 +822,108 @@ 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, owd := &v1alpha2.WorkloadDefinition{}, &v1alpha2.WorkloadDefinition{} + wDDefJson, _ := yaml.YAMLToJSON([]byte(wDDefWithHealthYaml)) + Expect(json.Unmarshal(wDDefJson, nwd)).Should(BeNil()) + Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "worker"}, owd)).Should(BeNil()) + nwd.ResourceVersion = owd.ResourceVersion + Expect(k8sClient.Update(ctx, nwd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + ntd, otd := &v1alpha2.TraitDefinition{}, &v1alpha2.TraitDefinition{} + tDDefJson, _ := yaml.YAMLToJSON([]byte(tDDefWithHealthYaml)) + Expect(json.Unmarshal(tDDefJson, ntd)).Should(BeNil()) + 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) + + By("create the new namespace") + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vela-test-with-health", + }, + } + 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()) + 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", + }) + (expTrait.Object["spec"].(map[string]interface{}))["workloadRef"] = map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": app.Name, + } + Expect(k8sClient.Create(ctx, &expTrait)).Should(BeNil()) + + By("enrich the status of deployment and scaler 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()) + expTrait.Object["status"] = v1alpha1.ConditionedStatus{ + Conditions: []v1alpha1.Condition{{ + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Now(), + }}, + } + Expect(k8sClient.Status().Update(ctx, &expTrait)).Should(BeNil()) + tGot := &unstructured.Unstructured{} + tGot.SetAPIVersion("core.oam.dev/v1alpha2") + tGot.SetKind("ManualScalerTrait") + Expect(k8sClient.Get(ctx, client.ObjectKey{ + Namespace: app.Namespace, + Name: app.Name, + }, tGot)).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") + + 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()) + }) }) func reconcileRetry(r reconcile.Reconciler, req reconcile.Request) { diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go index 1cd7cfd00..f79f81302 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go @@ -79,7 +79,8 @@ func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.App var healthy = true for _, wl := range appfile.Workloads { var status = v1alpha2.ApplicationComponentStatus{ - Name: wl.Name, + Name: wl.Name, + Healthy: true, } pCtx := process.NewContext(wl.Name, appfile.Name) if err := wl.EvalContext(pCtx); err != nil { @@ -107,7 +108,8 @@ func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.App var traitStatusList []v1alpha2.ApplicationTraitStatus for _, trait := range wl.Traits { var traitStatus = v1alpha2.ApplicationTraitStatus{ - Type: trait.Name, + Type: trait.Name, + Healthy: true, } traitHealth, err := trait.EvalHealth(pCtx, ret.c, ret.app.Namespace) if err != nil { diff --git a/pkg/dsl/definition/template.go b/pkg/dsl/definition/template.go index 0ccba95d5..c9c7e3bb4 100644 --- a/pkg/dsl/definition/template.go +++ b/pkg/dsl/definition/template.go @@ -74,45 +74,52 @@ func (wd *workloadDef) Params(params interface{}) AbstractEngine { func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string) error { bi := build.NewContext().NewInstance("", nil) if err := bi.AddFile("-", abstractTemplate); err != nil { - return err + return errors.WithMessagef(err, "invalid cue template of workload %s", wd.name) } if wd.params != nil { - bt, _ := json.Marshal(wd.params) + bt, err := json.Marshal(wd.params) + if err != nil { + return errors.WithMessagef(err, "marshal parameter of workload %s", wd.name) + } if err := bi.AddFile("parameter", fmt.Sprintf("parameter: %s", string(bt))); err != nil { - return err + return errors.WithMessagef(err, "invalid parameter of workload %s", wd.name) } } if err := bi.AddFile("-", ctx.BaseContextFile()); err != nil { return err } - insts := cue.Build([]*build.Instance{bi}) - for _, inst := range insts { + instances := cue.Build([]*build.Instance{bi}) + for _, inst := range instances { if err := inst.Value().Err(); err != nil { - return errors.WithMessagef(err, "workloadDef %s eval", wd.name) + return errors.WithMessagef(err, "invalid cue template of workload %s after merge parameter and context", wd.name) } output := inst.Lookup(OutputFieldName) base, err := model.NewBase(output) if err != nil { - return errors.WithMessagef(err, "workloadDef %s new base", wd.name) + return errors.WithMessagef(err, "invalid output of workload %s", wd.name) } ctx.SetBase(base) // we will support outputs for workload composition, and it will become trait in AppConfig. outputs := inst.Lookup(OutputsFieldName) + if !outputs.Exists() { + continue + } st, err := outputs.Struct() - if err == nil { - for i := 0; i < st.Len(); i++ { - fieldInfo := st.Field(i) - if fieldInfo.IsDefinition || fieldInfo.IsHidden || fieldInfo.IsOptional { - continue - } - other, err := model.NewOther(fieldInfo.Value) - if err != nil { - return errors.WithMessagef(err, "parse WorkloadDefinition %s outputs(%s)", wd.name, fieldInfo.Name) - } - ctx.PutAuxiliaries(process.Auxiliary{Ins: other, Type: AuxiliaryWorkload, Name: fieldInfo.Name, IsOutputs: true}) + if err != nil { + return errors.WithMessagef(err, "invalid outputs of workload %s", wd.name) + } + for i := 0; i < st.Len(); i++ { + fieldInfo := st.Field(i) + if fieldInfo.IsDefinition || fieldInfo.IsHidden || fieldInfo.IsOptional { + continue } + other, err := model.NewOther(fieldInfo.Value) + if err != nil { + return errors.WithMessagef(err, "invalid outputs(%s) of workload %s", fieldInfo.Name, wd.name) + } + ctx.AppendAuxiliaries(process.Auxiliary{Ins: other, Type: AuxiliaryWorkload, Name: fieldInfo.Name, IsOutputs: true}) } } return nil @@ -145,7 +152,7 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader return nil, err } root[OutputFieldName] = object - + outputs := make(map[string]interface{}) for _, assist := range assists { if assist.Type != AuxiliaryWorkload { continue @@ -164,9 +171,10 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader if err != nil { return nil, err } - root[OutputsFieldName] = map[string]interface{}{ - assist.Name: object, - } + outputs[assist.Name] = object + } + if len(outputs) > 0 { + root[OutputsFieldName] = outputs } return root, nil } @@ -223,9 +231,13 @@ func getStatusMessage(templateContext map[string]interface{}, customStatusTempla var r cue.Runtime inst, err := r.Compile("-", buff) if err != nil { - return "", err + return "", errors.WithMessage(err, "compile customStatus template") } - return inst.Lookup(CustomMessage).String() + message, err := inst.Lookup(CustomMessage).String() + if err != nil { + return "", errors.WithMessage(err, "evaluate customStatus.message") + } + return message, nil } type traitDef struct { @@ -251,30 +263,31 @@ func (td *traitDef) Params(params interface{}) AbstractEngine { func (td *traitDef) Complete(ctx process.Context, abstractTemplate string) error { bi := build.NewContext().NewInstance("", nil) if err := bi.AddFile("-", abstractTemplate); err != nil { - return err + return errors.WithMessagef(err, "invalid template of trait %s", td.name) } if td.params != nil { - bt, _ := json.Marshal(td.params) + bt, err := json.Marshal(td.params) + if err != nil { + return errors.WithMessagef(err, "marshal parameter of trait %s", td.name) + } if err := bi.AddFile("parameter", fmt.Sprintf("parameter: %s", string(bt))); err != nil { - return err + return errors.WithMessagef(err, "invalid parameter of trait %s", td.name) } } - if err := bi.AddFile("f", ctx.BaseContextFile()); err != nil { - return err + if err := bi.AddFile("context", ctx.BaseContextFile()); err != nil { + return errors.WithMessagef(err, "invalid context of trait %s", td.name) } - insts := cue.Build([]*build.Instance{bi}) - for _, inst := range insts { - + instances := cue.Build([]*build.Instance{bi}) + for _, inst := range instances { if err := inst.Value().Err(); err != nil { - return errors.WithMessagef(err, "traitDef %s build", td.name) + return errors.WithMessagef(err, "invalid template of trait %s after merge with parameter and context", td.name) } - processing := inst.Lookup("processing") var err error if processing.Exists() { if inst, err = task.Process(inst); err != nil { - return errors.WithMessagef(err, "traitDef %s build", td.name) + return errors.WithMessagef(err, "invalid process of trait %s", td.name) } } @@ -282,14 +295,16 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string) error if output.Exists() { other, err := model.NewOther(output) if err != nil { - return errors.WithMessagef(err, "traitDef %s new Assist", td.name) + return errors.WithMessagef(err, "invalid output of trait %s", td.name) } - ctx.PutAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, IsOutputs: false}) + ctx.AppendAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, IsOutputs: false}) } - outputs := inst.Lookup(OutputsFieldName) - st, err := outputs.Struct() - if err == nil { + if outputs.Exists() { + st, err := outputs.Struct() + if err != nil { + return errors.WithMessagef(err, "invalid outputs of trait %s", td.name) + } for i := 0; i < st.Len(); i++ { fieldInfo := st.Field(i) if fieldInfo.IsDefinition || fieldInfo.IsHidden || fieldInfo.IsOptional { @@ -297,9 +312,9 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string) error } other, err := model.NewOther(fieldInfo.Value) if err != nil { - return errors.WithMessagef(err, "traitDef %s new Assists(%s)", td.name, fieldInfo.Name) + return errors.WithMessagef(err, "invalid outputs(resource=%s) of trait %s", fieldInfo.Name, td.name) } - ctx.PutAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, Name: fieldInfo.Name, IsOutputs: true}) + ctx.AppendAuxiliaries(process.Auxiliary{Ins: other, Type: td.name, Name: fieldInfo.Name, IsOutputs: true}) } } @@ -308,10 +323,10 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string) error base, _ := ctx.Output() p, err := model.NewOther(patcher) if err != nil { - return errors.WithMessagef(err, "traitDef %s patcher NewOther", td.name) + return errors.WithMessagef(err, "invalid patch of trait %s", td.name) } if err := base.Unify(p); err != nil { - return err + return errors.WithMessagef(err, "invalid patch trait %s into workload", td.name) } } } @@ -331,6 +346,7 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, n } } _, assists := ctx.Output() + outputs := make(map[string]interface{}) for _, assist := range assists { if assist.Type != td.name { continue @@ -339,7 +355,6 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, n if err != nil { return nil, err } - object, err := getResourceFromObj(traitRef, cli, ns, util.MergeMapOverrideWithDst(map[string]string{ oam.TraitTypeLabel: assist.Type, }, commonLabels), assist.Name) @@ -347,13 +362,14 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, n return nil, err } if assist.IsOutputs { - root[OutputsFieldName] = map[string]interface{}{ - assist.Name: object, - } + outputs[assist.Name] = object } else { root[OutputFieldName] = object } } + if len(outputs) > 0 { + root[OutputsFieldName] = outputs + } return root, nil } diff --git a/pkg/dsl/definition/template_test.go b/pkg/dsl/definition/template_test.go index 785f3f8ce..08067893c 100644 --- a/pkg/dsl/definition/template_test.go +++ b/pkg/dsl/definition/template_test.go @@ -10,53 +10,210 @@ import ( "github.com/oam-dev/kubevela/pkg/dsl/process" ) -func TestWDTemplate(t *testing.T) { +func TestWorkloadTemplateComplete(t *testing.T) { testCases := []struct { - templ string - params map[string]interface{} - expectObj runtime.Object + workloadTemplate string + params map[string]interface{} + expectObj runtime.Object + expAssObjs map[string]runtime.Object }{ { - templ: ` + workloadTemplate: ` output:{ apiVersion: "apps/v1" kind: "Deployment" metadata: name: context.name spec: replicas: parameter.replicas } - parameter: { replicas: *1 | int + type: string + host: string } `, params: map[string]interface{}{ "replicas": 2, + "type": "ClusterIP", + "host": "example.com", }, expectObj: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"replicas": int64(2)}}}, }, + { + workloadTemplate: ` +output:{ + apiVersion: "apps/v1" + kind: "Deployment" + metadata: name: context.name + spec: replicas: parameter.replicas +} +outputs: service: { + apiVersion: "v1" + kind: "Service" + metadata: name: context.name + spec: type: parameter.type +} +outputs: ingress: { + apiVersion: "extensions/v1beta1" + kind: "Ingress" + metadata: name: context.name + spec: rules: [{host: parameter.host}] +} + +parameter: { + replicas: *1 | int + type: string + host: string +} +`, + params: map[string]interface{}{ + "replicas": 2, + "type": "ClusterIP", + "host": "example.com", + }, + expectObj: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"replicas": int64(2)}}}, + expAssObjs: map[string]runtime.Object{ + "service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"type": "ClusterIP"}}}, + "ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{ + "host": "example.com", + }}}}}, + }, + }, } for _, v := range testCases { ctx := process.NewContext("test", "myapp") - wt := NewWorkloadAbstractEngine("-") - if err := wt.Params(v.params).Complete(ctx, v.templ); err != nil { - t.Error(err) - return - } + wt := NewWorkloadAbstractEngine("testworkload") + assert.NoError(t, wt.Params(v.params).Complete(ctx, v.workloadTemplate)) base, assists := ctx.Output() - assert.Equal(t, 0, len(assists)) - assert.Equal(t, false, base == nil) + assert.Equal(t, len(v.expAssObjs), len(assists)) + assert.NotNil(t, base) baseObj, err := base.Unstructured() assert.Equal(t, nil, err) assert.Equal(t, v.expectObj, baseObj) - + for _, ss := range assists { + assert.Equal(t, AuxiliaryWorkload, ss.Type) + got, err := ss.Ins.Unstructured() + assert.NoError(t, err) + assert.Equal(t, got, v.expAssObjs[ss.Name]) + } } } -func TestTDTemplate(t *testing.T) { - baseTemplate := ` +func TestTraitTemplateComplete(t *testing.T) { + + tds := map[string]struct { + traitName string + traitTemplate string + params map[string]interface{} + expWorkload *unstructured.Unstructured + expAssObjs map[string]runtime.Object + }{ + "patch trait": { + traitTemplate: ` +patch: { + // +patchKey=name + spec: template: spec: containers: [parameter] +} + +parameter: { + name: string + image: string + command?: [...string] +}`, + params: map[string]interface{}{ + "name": "sidecar", + "image": "metrics-agent:0.2", + }, + expWorkload: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{"name": "test"}, + "spec": map[string]interface{}{ + "replicas": int64(2), + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{map[string]interface{}{"image": "website:0.1", "name": "main"}, + map[string]interface{}{"image": "metrics-agent:0.2", "name": "sidecar"}}}}}, + }}, + }, + "output trait": { + traitTemplate: ` +output: { + apiVersion: "v1" + kind: "Service" + metadata: name: context.name + spec: type: parameter.type +} +parameter: { + type: string +}`, + params: map[string]interface{}{ + "type": "ClusterIP", + }, + expWorkload: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{"name": "test"}, + "spec": map[string]interface{}{ + "replicas": int64(2), + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{map[string]interface{}{"image": "website:0.1", "name": "main"}}}}}, + }}, + traitName: "t1", + expAssObjs: map[string]runtime.Object{ + "t1": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"type": "ClusterIP"}}}, + }, + }, + "outputs trait": { + traitTemplate: ` +output: { + apiVersion: "v1" + kind: "Service" + metadata: name: context.name + spec: type: parameter.type +} +outputs: ingress: { + apiVersion: "extensions/v1beta1" + kind: "Ingress" + metadata: name: context.name + spec: rules: [{host: parameter.host}] +} +parameter: { + type: string + host: string +}`, + params: map[string]interface{}{ + "type": "ClusterIP", + "host": "example.com", + }, + expWorkload: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{"name": "test"}, + "spec": map[string]interface{}{ + "replicas": int64(2), + "template": map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []interface{}{map[string]interface{}{"image": "website:0.1", "name": "main"}}}}}, + }}, + traitName: "t2", + expAssObjs: map[string]runtime.Object{ + "t2": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"type": "ClusterIP"}}}, + "t2ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{ + "host": "example.com", + }}}}}, + }, + }, + } + + for cassinfo, v := range tds { + baseTemplate := ` output:{ apiVersion: "apps/v1" kind: "Deployment" @@ -73,65 +230,28 @@ parameter: { replicas: *1 | int } ` - ctx := process.NewContext("test", "myapp") - wt := NewWorkloadAbstractEngine("-") - if err := wt.Params(map[string]interface{}{ - "replicas": 2, - }).Complete(ctx, baseTemplate); err != nil { - t.Error(err) - return - } - - tds := []struct { - templ string - params map[string]interface{} - }{ - { - templ: ` -patch: { - // +patchKey=name - spec: template: spec: containers: [parameter] -} - -parameter: { - name: string - image: string - command?: [...string] -} -`, - params: map[string]interface{}{ - "name": "sidecar", - "image": "metrics-agent:0.2", - }, - }, - } - - for _, v := range tds { - td := NewTraitAbstractEngine("-") - if err := td.Params(v.params).Complete(ctx, v.templ); err != nil { + ctx := process.NewContext("test", "myapp") + wt := NewWorkloadAbstractEngine("-") + if err := wt.Params(map[string]interface{}{ + "replicas": 2, + }).Complete(ctx, baseTemplate); err != nil { t.Error(err) return } + td := NewTraitAbstractEngine(v.traitName) + assert.NoError(t, td.Params(v.params).Complete(ctx, v.traitTemplate)) + base, assists := ctx.Output() + assert.Equal(t, len(v.expAssObjs), len(assists), cassinfo) + assert.NotNil(t, base) + obj, err := base.Unstructured() + assert.NoError(t, err) + assert.Equal(t, v.expWorkload, obj, cassinfo) + for _, ss := range assists { + got, err := ss.Ins.Unstructured() + assert.NoError(t, err, cassinfo) + assert.Equal(t, got, v.expAssObjs[ss.Type+ss.Name], cassinfo, ss.Type+ss.Name) + } } - - base, assists := ctx.Output() - assert.Equal(t, 0, len(assists)) - assert.Equal(t, false, base == nil) - obj, err := base.Unstructured() - assert.Equal(t, nil, err) - expect := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": map[string]interface{}{"name": "test"}, - "spec": map[string]interface{}{ - "replicas": int64(2), - "template": map[string]interface{}{ - "spec": map[string]interface{}{ - "containers": []interface{}{map[string]interface{}{"image": "website:0.1", "name": "main"}, - map[string]interface{}{"image": "metrics-agent:0.2", "name": "sidecar"}}}}}, - }} - assert.Equal(t, expect, obj) } func TestCheckHealth(t *testing.T) { @@ -181,6 +301,46 @@ func TestCheckHealth(t *testing.T) { for message, ca := range cases { healthy, err := checkHealth(ca.tpContext, ca.healthTemp) assert.NoError(t, err, message) - assert.Equal(t, ca.exp, healthy) + assert.Equal(t, ca.exp, healthy, message) + } +} + +func TestGetStatus(t *testing.T) { + cases := map[string]struct { + tpContext map[string]interface{} + statusTemp string + expMessage string + }{ + "field-with-array-and-outputs": { + tpContext: map[string]interface{}{ + "outputs": map[string]interface{}{ + "service": map[string]interface{}{ + "spec": map[string]interface{}{ + "type": "NodePort", + "clusterIP": "10.0.0.1", + "ports": []interface{}{ + map[string]interface{}{ + "port": 80, + }, + }, + }, + }, + "ingress": map[string]interface{}{ + "rules": []interface{}{ + map[string]interface{}{ + "host": "example.com", + }, + }, + }, + }, + }, + statusTemp: `message: "type: " + context.outputs.service.spec.type + " clusterIP:" + context.outputs.service.spec.clusterIP + " ports:" + "\(context.outputs.service.spec.ports[0].port)" + " domain:" + context.outputs.ingress.rules[0].host`, + expMessage: "type: NodePort clusterIP:10.0.0.1 ports:80 domain:example.com", + }, + } + for message, ca := range cases { + gotMessage, err := getStatusMessage(ca.tpContext, ca.statusTemp) + assert.NoError(t, err, message) + assert.Equal(t, ca.expMessage, gotMessage, message) } } diff --git a/pkg/dsl/process/handle.go b/pkg/dsl/process/handle.go index bc17e8f08..581c362a1 100644 --- a/pkg/dsl/process/handle.go +++ b/pkg/dsl/process/handle.go @@ -12,7 +12,7 @@ import ( // Context defines Rendering Context Interface type Context interface { SetBase(base model.Instance) - PutAuxiliaries(insts ...Auxiliary) + AppendAuxiliaries(auxiliaries ...Auxiliary) SetConfigs(configs []map[string]string) Output() (model.Instance, []Auxiliary) BaseContextFile() string @@ -68,8 +68,8 @@ func (ctx *templateContext) SetBase(base model.Instance) { ctx.base = base } -// PutAuxiliaries add Assist model to templateContext -func (ctx *templateContext) PutAuxiliaries(auxiliaries ...Auxiliary) { +// AppendAuxiliaries add Assist model to templateContext +func (ctx *templateContext) AppendAuxiliaries(auxiliaries ...Auxiliary) { ctx.auxiliaries = append(ctx.auxiliaries, auxiliaries...) } From ea306dac5e8fdea8361d933f3eb702dc63dd4f15 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 4 Feb 2021 16:15:43 +0800 Subject: [PATCH 26/38] remove redundant image push operation --- pkg/builtin/build/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/builtin/build/build.go b/pkg/builtin/build/build.go index f6f7b308a..27d4d209f 100644 --- a/pkg/builtin/build/build.go +++ b/pkg/builtin/build/build.go @@ -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 { From 868e0925d49fdb4847da38176eda43fa257a6ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Thu, 4 Feb 2021 16:36:54 +0800 Subject: [PATCH 27/38] move health check and status out of extention --- apis/core.oam.dev/v1alpha2/core_types.go | 18 ++ .../v1alpha2/zz_generated.deepcopy.go | 25 ++ .../crds/core.oam.dev_traitdefinitions.yaml | 10 + .../core.oam.dev_workloaddefinitions.yaml | 10 + .../templates/defwithtemplate/ingress.yaml | 10 + config/samples/app-with-status/template.yaml | 7 +- docs/en/quick-start.md | 3 +- hack/vela-templates/definitions/ingress.yaml | 10 + .../crds/core.oam.dev_traitdefinitions.yaml | 10 + .../core.oam.dev_workloaddefinitions.yaml | 10 + pkg/commands/status.go | 5 +- .../application/application_controller.go | 2 +- .../application_controller_test.go | 265 +++++++++++++++--- pkg/dsl/definition/template_test.go | 31 ++ pkg/oam/util/template.go | 31 +- 15 files changed, 375 insertions(+), 72 deletions(-) diff --git a/apis/core.oam.dev/v1alpha2/core_types.go b/apis/core.oam.dev/v1alpha2/core_types.go index c02d880ea..49118cf63 100644 --- a/apis/core.oam.dev/v1alpha2/core_types.go +++ b/apis/core.oam.dev/v1alpha2/core_types.go @@ -64,12 +64,26 @@ 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"` + // 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 +140,10 @@ type TraitDefinitionSpec struct { // +optional ConflictsWith []string `json:"conflictsWith,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 diff --git a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go index 734506cca..629ce6a12 100644 --- a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go @@ -1649,6 +1649,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 @@ -1736,6 +1751,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) @@ -1884,6 +1904,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) diff --git a/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml b/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml index 9f73a7a7f..8a60ff7ed 100644 --- a/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml @@ -68,6 +68,16 @@ 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 workloadRefPath: description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object type: string diff --git a/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml b/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml index 404a5a2eb..4fefb019e 100644 --- a/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml @@ -82,6 +82,16 @@ 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 required: - definitionRef type: object diff --git a/charts/vela-core/templates/defwithtemplate/ingress.yaml b/charts/vela-core/templates/defwithtemplate/ingress.yaml index 51bc22e87..89cfa60f9 100644 --- a/charts/vela-core/templates/defwithtemplate/ingress.yaml +++ b/charts/vela-core/templates/defwithtemplate/ingress.yaml @@ -7,6 +7,16 @@ 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 diff --git a/config/samples/app-with-status/template.yaml b/config/samples/app-with-status/template.yaml index 1252663ee..621ad3f3d 100644 --- a/config/samples/app-with-status/template.yaml +++ b/config/samples/app-with-status/template.yaml @@ -1,4 +1,3 @@ -# Code generated by KubeVela templates. DO NOT EDIT. apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: @@ -8,11 +7,12 @@ metadata: spec: definitionRef: name: deployments.apps - extension: + 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 + extension: template: | output: { apiVersion: "apps/v1" @@ -73,11 +73,12 @@ kind: TraitDefinition metadata: name: ingress spec: - extension: + 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 + extension: template: | parameter: { domain: string diff --git a/docs/en/quick-start.md b/docs/en/quick-start.md index b1b27acfa..658e709d3 100644 --- a/docs/en/quick-start.md +++ b/docs/en/quick-start.md @@ -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: ``` **In [kind cluster setup](./install.md#kind)**, you can visit the service via localhost. In other setups, replace localhost with ingress address accordingly. diff --git a/hack/vela-templates/definitions/ingress.yaml b/hack/vela-templates/definitions/ingress.yaml index 6a460e661..a458aa169 100644 --- a/hack/vela-templates/definitions/ingress.yaml +++ b/hack/vela-templates/definitions/ingress.yaml @@ -6,6 +6,16 @@ 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 diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml index eb78f7d86..5581c7ac7 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml @@ -67,6 +67,16 @@ 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 workloadRefPath: description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object type: string diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml index 4a2e46a6f..27e0866a0 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml @@ -81,6 +81,16 @@ 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 required: - definitionRef type: object diff --git a/pkg/commands/status.go b/pkg/commands/status.go index ee6ec26e0..3adede6df 100644 --- a/pkg/commands/status.go +++ b/pkg/commands/status.go @@ -7,8 +7,6 @@ import ( "strings" "time" - "github.com/oam-dev/kubevela/pkg/oam/util" - "github.com/fatih/color" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -19,6 +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/util" ) // HealthStatus represents health status strings. @@ -274,7 +273,7 @@ func TrackDeployStatus(ctx context.Context, c client.Client, compName, appName s // 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, err := loadRemoteApplication(c, appName, env.Namespace) + app, err := loadRemoteApplication(c, env.Namespace, appName) if err != nil { return compStatusUnknown, HealthStatusNotDiagnosed, "", err } diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index 67cabba81..e088d8636 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -110,7 +110,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } app.Status.SetConditions(readyCondition("Built")) - applog.Info("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]") diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index 3bcd13d9b..abe022e3c 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -825,54 +825,110 @@ var _ = Describe("Test Application Controller", func() { It("app with health policy and custom status for workload", func() { By("change workload and trait definition with health policy") - nwd, owd := &v1alpha2.WorkloadDefinition{}, &v1alpha2.WorkloadDefinition{} - wDDefJson, _ := yaml.YAMLToJSON([]byte(wDDefWithHealthYaml)) + nwd := &v1alpha2.WorkloadDefinition{} + wDDefJson, _ := yaml.YAMLToJSON([]byte(wdDefWithHealthStatusYaml)) Expect(json.Unmarshal(wDDefJson, nwd)).Should(BeNil()) - Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "worker"}, owd)).Should(BeNil()) - nwd.ResourceVersion = owd.ResourceVersion - Expect(k8sClient.Update(ctx, nwd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) - ntd, otd := &v1alpha2.TraitDefinition{}, &v1alpha2.TraitDefinition{} - tDDefJson, _ := yaml.YAMLToJSON([]byte(tDDefWithHealthYaml)) + 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.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) + 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", + Name: "vela-test-with-health-status", }, } - appWithTrait.SetNamespace(ns.Name) + appWithTraitHealthStatus.SetNamespace(ns.Name) Expect(k8sClient.Create(ctx, ns)).Should(BeNil()) - app := appWithTrait.DeepCopy() + 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()) - expTrait := expectScalerTrait(compName, app.Name) - expTrait.SetName(app.Name) + + 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) - expTrait.SetLabels(map[string]string{ - oam.LabelAppName: app.Name, - "trait.oam.dev/type": "scaler", - "app.oam.dev/component": "myweb-health", - }) - (expTrait.Object["spec"].(map[string]interface{}))["workloadRef"] = map[string]interface{}{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "name": app.Name, - } Expect(k8sClient.Create(ctx, &expTrait)).Should(BeNil()) - By("enrich the status of deployment and scaler trait") + 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()) @@ -881,20 +937,6 @@ var _ = Describe("Test Application Controller", func() { Namespace: app.Namespace, Name: app.Name, }, got)).Should(BeNil()) - expTrait.Object["status"] = v1alpha1.ConditionedStatus{ - Conditions: []v1alpha1.Condition{{ - Status: corev1.ConditionTrue, - LastTransitionTime: metav1.Now(), - }}, - } - Expect(k8sClient.Status().Update(ctx, &expTrait)).Should(BeNil()) - tGot := &unstructured.Unstructured{} - tGot.SetAPIVersion("core.oam.dev/v1alpha2") - tGot.SetKind("ManualScalerTrait") - Expect(k8sClient.Get(ctx, client.ObjectKey{ - Namespace: app.Namespace, - Name: app.Name, - }, tGot)).Should(BeNil()) By("apply appfile") Expect(k8sClient.Create(ctx, app)).Should(BeNil()) @@ -905,13 +947,12 @@ var _ = Describe("Test Application Controller", func() { 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() } - checkApp := &v1alpha2.Application{} err = k8sClient.Get(ctx, appKey, checkApp) if err != nil { return err.Error() @@ -921,7 +962,20 @@ var _ = Describe("Test Application Controller", func() { } 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()) }) }) @@ -1158,6 +1212,73 @@ 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 + 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 + 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 @@ -1262,6 +1383,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 + extension: + 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 { diff --git a/pkg/dsl/definition/template_test.go b/pkg/dsl/definition/template_test.go index 08067893c..5a252a19a 100644 --- a/pkg/dsl/definition/template_test.go +++ b/pkg/dsl/definition/template_test.go @@ -337,6 +337,37 @@ func TestGetStatus(t *testing.T) { statusTemp: `message: "type: " + context.outputs.service.spec.type + " clusterIP:" + context.outputs.service.spec.clusterIP + " ports:" + "\(context.outputs.service.spec.ports[0].port)" + " domain:" + context.outputs.ingress.rules[0].host`, expMessage: "type: NodePort clusterIP:10.0.0.1 ports:80 domain:example.com", }, + "complex status": { + tpContext: map[string]interface{}{ + "outputs": map[string]interface{}{ + "ingress": map[string]interface{}{ + "spec": map[string]interface{}{ + "rules": []interface{}{ + map[string]interface{}{ + "host": "example.com", + }, + }, + }, + "status": map[string]interface{}{ + "loadBalancer": map[string]interface{}{ + "ingress": []interface{}{ + map[string]interface{}{ + "ip": "10.0.0.1", + }, + }, + }, + }, + }, + }, + }, + statusTemp: `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" +}`, + expMessage: "Visiting URL: example.com, IP: 10.0.0.1", + }, } for message, ca := range cases { gotMessage, err := getStatusMessage(ca.tpContext, ca.statusTemp) diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index e7ee89e10..05a080e23 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" @@ -47,7 +48,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if wd.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTemplate(wd.Spec.Extension.Raw) + tmpl, err := NewTemplate(wd.Spec.Extension, wd.Spec.Status) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -66,7 +67,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if td.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTemplate(td.Spec.Extension.Raw) + tmpl, err := NewTemplate(td.Spec.Extension, td.Spec.Status) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -82,24 +83,18 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e return nil, fmt.Errorf("kind(%s) of %s not supported", kd, key) } -func getTemplate(raw []byte) (*Template, error) { - _tmp := map[string]interface{}{} - if err := json.Unmarshal(raw, &_tmp); err != nil { +// NewTemplate will create CUE template for inner AbstractEngine using. +func NewTemplate(raw *runtime.RawExtension, status *v1alpha2.Status) (*Template, error) { + extension := map[string]interface{}{} + if err := json.Unmarshal(raw.Raw, &extension); err != nil { return nil, err } - var ( - health string - status string - ) - if _, ok := _tmp["healthPolicy"]; ok { - health = fmt.Sprint(_tmp["healthPolicy"]) + tmp := &Template{ + TemplateStr: fmt.Sprint(extension["template"]), } - if _, ok := _tmp["customStatus"]; ok { - status = fmt.Sprint(_tmp["customStatus"]) + if status != nil { + tmp.CustomStatus = status.CustomStatus + tmp.Health = status.HealthPolicy } - return &Template{ - TemplateStr: fmt.Sprint(_tmp["template"]), - Health: health, - CustomStatus: status, - }, nil + return tmp, nil } From cd971063f8d1d17aa3bdba27c6c0f5d7e3b5bc17 Mon Sep 17 00:00:00 2001 From: Harry Zhang Date: Thu, 4 Feb 2021 14:22:45 -0800 Subject: [PATCH 28/38] Update the details of the doc --- docs/en/_sidebar.md | 3 +- docs/en/platform-engineers/overview.md | 38 +++++++++++++------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/docs/en/_sidebar.md b/docs/en/_sidebar.md index f3d88b8a5..5c95e3656 100644 --- a/docs/en/_sidebar.md +++ b/docs/en/_sidebar.md @@ -4,8 +4,7 @@ - [Concepts and Glossaries](/en/concepts.md) - Platform Team Guide - - [Overview](/en/platform-engineers/overview.md) - + - [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) diff --git a/docs/en/platform-engineers/overview.md b/docs/en/platform-engineers/overview.md index e197e5b18..6fe4e4681 100644 --- a/docs/en/platform-engineers/overview.md +++ b/docs/en/platform-engineers/overview.md @@ -1,28 +1,20 @@ -# KubeVela Under The Hood +# What is KubeVela? -This documentation explains how KubeVela works in perspective of platform team. +This documentation explains "what KubeVela can do for you" in perspective of platform team. -## KubeVela Runtime +## Overview -The KubeVela runtime is the core component of KubeVela, it is a Kubernetes addon composed by several parts. - -The first part of this runtime is "encapsulation engine". This component supports various of encapsulation modules to create a single user facing abstraction named `Application` that allows end user to fill in parameters to instantiate the module. At the meantime, it also provides a set of interfaces for platform team to define and customize the module (i.e. CUE, Helm, or Terraform modules, etc). The implementation of abstraction engine is powered by Open Application Model. - -The second part is "deployment engine", it is responsible for progressive rollout of the application following given rollout strategy (e.g. canary, blue-green, etc) claimed in `AppDeployment`. +KubeVela provides several independent building blocks to help you create application platforms easily. ![alt](../../resources/kubevela-runtime.png) -### Encapsulation Engine +### 1. Application Encapsulation -As a platform builder, the encapsulation engine is essential to create any end user facing platform with Kubernetes, i.e. we want to lower the bar for end users by creating higher level abstractions. +The encapsulation engine enables you to define an `Application` abstraction that encapsulates all the needed resources composed your app. -One typical example is we will want to encapsulate a Kubernetes `Deployment` and `Service` into a module probably named *Web Service*, and let end users to instantiate this module by simply filling in the needed 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 for creating such abstraction. +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. -Hence, the encapsulation engine of KubeVela is designed to help to make building abstractions easy, in a highly extensible approach. - -#### Build Extensible Abstraction - -First of all, with KubeVela, you will never create monolithic abstraction which is restricted and can't be extended. In detail, the encapsulation engine introduced an extensible app-centric model behind the abstraction, this makes the abstraction is essentially assembled by components (workload modules) and traits (operational modules), an example is like below: +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 @@ -51,9 +43,11 @@ spec: bucket: "my-bucket" ``` -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. +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. -#### A Unified Abstraction For All +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. @@ -71,8 +65,14 @@ A typical use case is, as the platform team, we want to leverage `Istio` as the 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 +#### 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. From 15020660c9d0a001af4ef4533d374129189d9ab2 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 4 Feb 2021 01:38:45 -0800 Subject: [PATCH 29/38] cloneset controller draft --- apis/core.oam.dev/v1alpha2/appdeploy_types.go | 19 +- .../v1alpha2/zz_generated.deepcopy.go | 16 ++ .../v1alpha1/rollout_plan_types.go | 30 +- .../v1alpha1/rollout_state.go | 260 ++++++++++++++++++ .../core.oam.dev_applicationdeployments.yaml | 22 +- .../crds/standard.oam.dev_rollouttraits.yaml | 14 +- cmd/core/main.go | 2 + go.mod | 3 +- go.sum | 185 ++++++++++++- .../core.oam.dev_applicationdeployments.yaml | 22 +- .../crds/standard.oam.dev_rollouttraits.yaml | 14 +- pkg/commands/refresh.go | 2 +- .../common/rollout/rollout_plan_controller.go | 216 ++++++++++++++- .../common/rollout/rollout_state.go | 210 -------------- .../rollout/rollout_webhook.go} | 0 .../rollout/workloads/cloneset_controller.go | 197 ++++++++++++- .../common/rollout/workloads/controller.go | 43 +-- .../common/rollout/workloads/factory.go | 47 ---- .../applicationdeployment_controller.go | 45 +-- pkg/utils/apply/apply.go | 10 +- pkg/webhook/common/rollout/rollout_plan.go | 6 +- 21 files changed, 976 insertions(+), 387 deletions(-) create mode 100644 apis/standard.oam.dev/v1alpha1/rollout_state.go delete mode 100644 pkg/controller/common/rollout/rollout_state.go rename pkg/controller/{standard.oam.dev/v1alpha1/rollout/webhook.go => common/rollout/rollout_webhook.go} (100%) delete mode 100644 pkg/controller/common/rollout/workloads/factory.go diff --git a/apis/core.oam.dev/v1alpha2/appdeploy_types.go b/apis/core.oam.dev/v1alpha2/appdeploy_types.go index 280d02a52..d7671e108 100644 --- a/apis/core.oam.dev/v1alpha2/appdeploy_types.go +++ b/apis/core.oam.dev/v1alpha2/appdeploy_types.go @@ -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 diff --git a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go index 629ce6a12..2e198892b 100644 --- a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go @@ -371,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 diff --git a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go index 46bae00c0..bd212c52d 100644 --- a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go +++ b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go @@ -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 @@ -41,8 +41,6 @@ const ( InitializingState RollingState = "initializing" // RollingInBatchesState rolling out RollingInBatchesState RollingState = "rollingInBatches" - // PausedState rollout is stopped, the batch rolling is not completed - PausedState RollingState = "paused" // 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 @@ -61,15 +59,15 @@ const ( 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. This happens when it's either manual or - // automatic with analysis + // BatchVerifyingState verifying if the application is ready to roll. + // This happens when it's either manual or automatic with analysis BatchVerifyingState BatchRollingState = "batchVerifying" - // BatchVerifyFailedState indicates that the batch didn't get the manual or automatic approval - BatchVerifyFailedState BatchRollingState = "batchVerifyFailed" + // BatchRolloutFailedState indicates that the batch didn't get the manual or automatic approval + BatchRolloutFailedState BatchRollingState = "batchVerifyFailed" // BatchReadyState indicates that all the pods in the are upgraded and its state is ready BatchReadyState BatchRollingState = "batchReady" - // BatchAvailableState indicates that all the pods in the are available, we can move on to the next batch - BatchAvailableState BatchRollingState = "batchAvailable" + // BatchFinalizeState indicates that all the pods in the are available, we can move on to the next batch + BatchFinalizeState BatchRollingState = "batchFinalize" ) // RolloutPlan fines the details of the rollout plan @@ -208,16 +206,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"` @@ -227,6 +228,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 diff --git a/apis/standard.oam.dev/v1alpha1/rollout_state.go b/apis/standard.oam.dev/v1alpha1/rollout_state.go new file mode 100644 index 000000000..197916e59 --- /dev/null +++ b/apis/standard.oam.dev/v1alpha1/rollout_state.go @@ -0,0 +1,260 @@ +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 are waiting for the approval of the + 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 pod. +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" +) + +// 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: + 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 + return + } + panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) + + case InitializingState: + if event == RollingInitializedEvent { + r.RollingState = RollingInBatchesState + return + } + panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) + + case RollingInBatchesState: + r.batchStateTransition(event) + return + + case FinalisingState: + if event == RollingFinalizedEvent { + r.RollingState = RolloutSucceedState + return + } + panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) + + case RolloutSucceedState: + if event == WorkloadModifiedEvent { + r.RollingState = VerifyingState + return + } + if event == RollingFinalizedEvent { + // no op + return + } + panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) + + case RolloutFailedState: + if event == WorkloadModifiedEvent { + r.RollingState = VerifyingState + 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 + 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 + return + } + panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event)) + + case BatchVerifyingState: + if event == OneBatchAvailableEvent { + r.BatchRollingState = BatchReadyState + return + } + if event == BatchRolloutVerifyingEvent { + // no op + return + } + panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event)) + + case BatchReadyState: + if event == BatchRolloutApprovedEvent { + r.BatchRollingState = BatchFinalizeState + return + } + panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event)) + + case BatchFinalizeState: + if event == FinishedOneBatchEvent { + r.BatchRollingState = BatchInitializingState + return + } + if event == AllBatchFinishedEvent { + // transition out of the batch loop + r.RollingState = FinalisingState + return + } + panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event)) + + default: + panic(fmt.Errorf("invalid batch rolling state %s", batchRollingState)) + } +} diff --git a/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml b/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml index 625f6b90e..436d9dcf3 100644 --- a/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml +++ b/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml @@ -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 diff --git a/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml b/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml index 00d1ca396..b768135ef 100644 --- a/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml +++ b/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml @@ -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 diff --git a/cmd/core/main.go b/cmd/core/main.go index 78efb6054..6de1e4e8a 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -18,6 +18,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 +63,7 @@ func init() { _ = injectorv1alpha1.AddToScheme(scheme) _ = certmanager.AddToScheme(scheme) _ = kedav1alpha1.AddToScheme(scheme) + _ = kruise.AddToScheme(scheme) // +kubebuilder:scaffold:scheme } diff --git a/go.mod b/go.mod index 0fc91b7e0..3fe639794 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -71,6 +72,7 @@ require ( k8s.io/klog/v2 v2.0.0 k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 k8s.io/kubectl v0.18.6 + k8s.io/kubernetes v1.14.7 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 sigs.k8s.io/controller-runtime v0.6.2 sigs.k8s.io/controller-tools v0.2.4 @@ -79,7 +81,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 diff --git a/go.sum b/go.sum index ddcb41f69..8cd1f874a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM= cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -95,6 +96,7 @@ github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/azure-sdk-for-go v28.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v34.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v36.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -169,6 +171,7 @@ github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced3 github.com/GoogleCloudPlatform/testgrid v0.0.1-alpha.3/go.mod h1:f96W2HYy3tiBNV5zbbRc+NczwYHgG1PHXMQfoEWv680= github.com/GoogleCloudPlatform/testgrid v0.0.7/go.mod h1:lmtHGBL0M/MLbu1tR9BWV7FGZ1FEFIdPqmJiHNCL7y8= github.com/GoogleCloudPlatform/testgrid v0.0.13/go.mod h1:UlC/MvnkKjiVGijIKOHxnVyhDiTDCydw9H1XzmclQGU= +github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -187,9 +190,11 @@ github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZ github.com/Masterminds/squirrel v1.2.0 h1:K1NhbTO21BWG47IVR0OnIZuE0LZcXAYqywrC3Ko53KI= github.com/Masterminds/squirrel v1.2.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -198,6 +203,7 @@ github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nB github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -206,6 +212,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -241,6 +248,7 @@ github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= +github.com/appscode/jsonpatch v1.0.1/go.mod h1:4AJxUpXUhv4N+ziTvIcWWXgeorXpxPZOfk9HdEVr96M= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -254,6 +262,7 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/aws/aws-k8s-tester v0.0.0-20190114231546-b411acf57dfe/go.mod h1:1ADF5tAtU1/mVtfMcHAYSm2fPw71DA7fFk0yed64/0I= github.com/aws/aws-k8s-tester v0.9.3/go.mod h1:nsh1f7joi8ZI1lvR+Ron6kJM2QdCYPU/vFePghSSuTc= github.com/aws/aws-k8s-tester v1.0.0/go.mod h1:NUNd9k43+h9O5tvwL+4N1Ctb//SapmeeFX1G0/2/0Qc= @@ -273,6 +282,7 @@ github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.28.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.32/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.29.34/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.30.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= @@ -287,7 +297,11 @@ github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZw github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/bazelbuild/bazel-gazelle v0.18.2/go.mod h1:D0ehMSbS+vesFsLGiD6JXu3mVEzOlfUl8wNnq+x/9p0= +github.com/bazelbuild/bazel-gazelle v0.19.1-0.20191105222053-70208cbdc798/go.mod h1:rPwzNHUqEzngx1iVBfO/2X2npKaT3tqPqqHW6rVsn/A= +github.com/bazelbuild/buildtools v0.0.0-20190731111112-f720930ceb60/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= github.com/bazelbuild/buildtools v0.0.0-20190917191645-69366ca98f89/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= +github.com/bazelbuild/rules_go v0.0.0-20190719190356-6dae44dc5cab/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= @@ -297,6 +311,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -314,6 +329,7 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/brancz/kube-rbac-proxy v0.5.0/go.mod h1:cL2VjiIFGS90Cjh5ZZ8+It6tMcBt8rwvuw2J6Mamnl0= github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0= @@ -330,15 +346,18 @@ github.com/bwmarrin/snowflake v0.0.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/ github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -347,6 +366,8 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= +github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -360,6 +381,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3/go.mod h1:j1nZWMLGg3om8SswStBoY6/SHvcLM19MuZqwDtMtmzs= github.com/cloudevents/sdk-go v1.0.0/go.mod h1:3TkmM0cFqkhCHOq5JzzRU/RxRkwzoS8TZ+G448qVTog= github.com/cloudevents/sdk-go/v2 v2.0.0/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU= +github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= +github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -369,9 +392,13 @@ github.com/cockroachdb/apd/v2 v2.0.1 h1:y1Rh3tEU89D+7Tgbw+lp52T6p/GJLpDmNvr10UWq github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= @@ -385,12 +412,14 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 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= @@ -400,6 +429,7 @@ github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -408,6 +438,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/prometheus-operator v0.41.1 h1:MEhY9syliPlQg+VlFRUfNodUEVXRXJ2n1pFG0aBp+mI= github.com/coreos/prometheus-operator v0.41.1/go.mod h1:LhLfEBydppl7nvfEA1jIqlF3xJ9myHCnzrU+HHDxRd4= +github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -461,6 +492,7 @@ github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA= @@ -468,6 +500,7 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= @@ -508,8 +541,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.6/go.mod h1:GFqM7v0B62MraO4PWRedIbhThr/Rf7ev6aHOOPXeaDA= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -519,6 +554,7 @@ github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v0.0.0-20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= @@ -562,12 +598,16 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= +github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I= +github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-critic/go-critic v0.5.0/go.mod h1:4jeRh3ZAVnRYhuWdOEvwzVqLUpxMSoAT0xZ74JsTPlo= @@ -685,6 +725,7 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= @@ -760,6 +801,7 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -791,6 +833,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -826,14 +869,19 @@ github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8ju github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.18.0/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg= github.com/golangci/golangci-lint v1.23.7/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng= github.com/golangci/golangci-lint v1.30.0/go.mod h1:5t0i3wHlqQc9deBBvZsP+a/4xz7cfjV+zhp5U0Mzp14= +github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= @@ -846,6 +894,7 @@ github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAO github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v1.7.0/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/diff v0.0.0-20181124234638-500114f11e71/go.mod h1:22dM4PLscQl+Nzf64qNBurVJvfyvZELT0iRW2l/NN70= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= @@ -859,6 +908,8 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Z github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= @@ -959,6 +1010,7 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= @@ -1051,6 +1103,7 @@ github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -1058,6 +1111,7 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8 github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -1076,6 +1130,8 @@ github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0 github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/heketi/heketi v9.0.1-0.20190917153846-c2e2a4ab7ab9+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= +github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -1113,11 +1169,13 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jenkins-x/go-scm v1.5.65/go.mod h1:MgGRkJScE/rJ30J/bXYqduN5sDPZqZFITJopsnZmTOw= github.com/jenkins-x/go-scm v1.5.79/go.mod h1:PCT338UhP/pQ0IeEeMEf/hoLTYKcH7qjGEKd7jPkeYg= github.com/jenkins-x/go-scm v1.5.117/go.mod h1:PCT338UhP/pQ0IeEeMEf/hoLTYKcH7qjGEKd7jPkeYg= github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jinzhu/gorm v0.0.0-20170316141641-572d0a0ab1eb/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= @@ -1168,6 +1226,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -1177,6 +1236,7 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -1210,6 +1270,7 @@ github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= @@ -1239,6 +1300,7 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -1248,7 +1310,13 @@ github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lovoo/gcloud-opentracing v0.3.0/go.mod h1:ZFqk2y38kMDDikZPAK7ynTTGuyt17nSPdS3K5e+ZTBY= +github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= +github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= +github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk= +github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao= +github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1268,6 +1336,7 @@ github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzfe github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= @@ -1304,6 +1373,7 @@ github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1N github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v0.0.0-20160514122348-38ee283dabf1/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1321,18 +1391,24 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= +github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= github.com/minio/minio-go/v6 v6.0.49/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -1340,6 +1416,7 @@ github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:Sfy github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -1359,6 +1436,7 @@ github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWe github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -1375,23 +1453,30 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= +github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= github.com/nats-io/go-nats v1.7.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= @@ -1405,6 +1490,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -1470,9 +1557,17 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2Pupw60ON8TYEIGGTAI77yZsWYkiOeHFZWkwlCk= +github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 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-spec v1.0.0/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/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= +github.com/openkruise/kruise v0.7.0 h1:Uap2OiKojQovxeSvxk5CGvUnZ6SIKGrIH872JPF+Z88= +github.com/openkruise/kruise v0.7.0/go.mod h1:/8LlpF0EUkDAJCi/5Zo1MxOLLN0VF4qzyjZ1TXmwzN4= +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= @@ -1496,6 +1591,7 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= @@ -1530,6 +1626,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus-community/prom-label-proxy v0.1.1-0.20200616110844-0fbfa11fa8f3/go.mod h1:XdjyZg7LCbCC5FADHtpgNp6kQ0W9beXVGfmcvndMj5Y= github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE= github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg= @@ -1606,10 +1703,13 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190706150252-9beb055b7962/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -1629,6 +1729,7 @@ github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 h1:xkBtI5JktwbW/vf4vopBbhYsRFTGfQWHYXzC0/qYwxI= github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= +github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -1638,6 +1739,7 @@ github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUcc github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -1653,6 +1755,7 @@ github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrY github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= @@ -1663,6 +1766,7 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/servicemeshinterface/smi-sdk-go v0.4.1/go.mod h1:9rsLPBNcqfDNmEgyYwpopn93aE9yz46d2EHFBNOYj/w= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/githubv4 v0.0.0-20180925043049-51d7b505e2e9/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= @@ -1680,6 +1784,7 @@ github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJV github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1704,15 +1809,18 @@ github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34c github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.0-20180629152535-a114f312e075/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -1721,6 +1829,7 @@ github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1730,6 +1839,7 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= @@ -1737,6 +1847,7 @@ github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfD github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/ssgreg/nlreturn/v2 v2.0.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1766,7 +1877,9 @@ github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0 github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= +github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tektoncd/pipeline v0.8.0/go.mod h1:IZzJdiX9EqEMuUcgdnElozdYYRh0/ZRC+NKMLj1K3Yw= @@ -1781,7 +1894,9 @@ github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQx github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc= +github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -1824,6 +1939,7 @@ github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1831,11 +1947,13 @@ github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= +github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/quicktemplate v1.5.1/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= @@ -1843,6 +1961,9 @@ github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44 github.com/vdemeester/k8s-pkg-credentialprovider v1.13.12-1/go.mod h1:Fko0rTxEtDW2kju5Ky7yFJNS3IcNvW8IPsp4/e9oev0= github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wonderflow/cert-manager-api v1.0.3 h1:xQQMkJNQ12oYyy00jOQUlSKgdraApaURxv3PHFdVTfA= @@ -1871,6 +1992,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xyproto/pinterface v0.0.0-20200201214933-70763765f31f/go.mod h1:X5B5pKE49ak7SpyDh5QvJvLH9cC9XuZNDcl5hEyYc34= +github.com/xyproto/simpleredis v0.0.0-20200201215242-1ff0da2967b4/go.mod h1:U/ZOQqa0ggBGPs+d0y7r50BY6FyFTh5WhWf7F8f1MBM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1944,22 +2067,29 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= +golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -2043,6 +2173,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2050,6 +2181,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2059,8 +2191,10 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -2120,6 +2254,7 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2141,6 +2276,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2161,6 +2297,7 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omN golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2171,6 +2308,8 @@ golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190122202912-9c309ee22fab/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2205,6 +2344,7 @@ golang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190918214516-5a1a30219888/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2495,6 +2635,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -2534,8 +2675,11 @@ gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8 h1:jL/vaozO53FMfZLySWM+4nulF gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= helm.sh/helm/v3 v3.1.1/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g= helm.sh/helm/v3 v3.2.0/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0= helm.sh/helm/v3 v3.2.4 h1:lz/0ZRkSgyIF+pCo6pjFzap1udCARB1IN6CRfqkpcOg= @@ -2561,6 +2705,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 +2717,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 +2727,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 +2740,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 +2755,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,22 +2768,28 @@ 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= +k8s.io/apiserver v0.18.8 h1:Au4kMn8sb1zFdyKqc8iMHLsYLxRI6Y+iAhRNKKQtlBY= +k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA= k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= k8s.io/cli-runtime v0.18.6 h1:I8BkH5NyqMQ4zqUBmpXJ1LxIqpCH88H/1edPkPVWzjQ= k8s.io/cli-runtime v0.18.6/go.mod h1:+G/WTNqHgUv636e5y7rhOQ7epUbRXnwmPnhOhD6t9uM= +k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k= +k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M= k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= +k8s.io/cloud-provider v0.18.8/go.mod h1:cn9AlzMPVIXA4HHLVbgGUigaQlZyHSZ7WAwDEFNrQSs= +k8s.io/cluster-bootstrap v0.18.8/go.mod h1:guq0Uc+QwazHgpS1yAw5Z7yUlBCtGppbgWQkbN3lxIY= 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= @@ -2664,8 +2813,12 @@ k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmD k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= k8s.io/component-base v0.18.6 h1:Wd6cHGwJN2qpufnirVOB3oMhyhbioGsKEi5HeDBsV+s= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= +k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8= +k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= +k8s.io/cri-api v0.18.8/go.mod h1:OJtpjDvfsKoLGhvcc0qfygved0S0dGX56IJzPbqTG1s= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +k8s.io/csi-translation-lib v0.18.8/go.mod h1:6cA6Btlzxy9s3QrS4BCZzQqclIWnTLr6Jx3H2ctAzY4= k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190306031000-7a1b7fb0289f/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -2673,6 +2826,7 @@ k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20191108084044-e500ee069b5c/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200205140755-e0e292d8aa12/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -2682,6 +2836,8 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/kube-aggregator v0.18.8/go.mod h1:CyLoGZB+io8eEwnn+6RbV7QWJQhj8a3TBH8ZM8sLbhI= +k8s.io/kube-controller-manager v0.18.8/go.mod h1:IYZteddXJFD1TVgAw8eRP3c9OOA2WtHdXdE8aH6gXnc= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= @@ -2693,19 +2849,31 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOEC k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= +k8s.io/kube-proxy v0.18.8/go.mod h1:u4E8OsUpUzfZ9CEFf9rdLsbYiusZr8utbtF4WQrX+qs= +k8s.io/kube-scheduler v0.18.8/go.mod h1:OeliYiILv1XkSq0nmQjRewgt5NimKsTidZFEhfL5fqA= k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk= k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= k8s.io/kubectl v0.18.6 h1:IFPNuLPkZ59vSGQzynXY8XGz9yuOSRpkJupnobdYvO4= k8s.io/kubectl v0.18.6/go.mod h1:3TLzFOrF9h4mlRPAvdNkDbs5NWspN4e0EnPnEB41CGo= +k8s.io/kubectl v0.18.8 h1:qTkHCz21YmK0+S0oE6TtjtxmjeDP42gJcZJyRKsIenA= +k8s.io/kubectl v0.18.8/go.mod h1:PlEgIAjOMua4hDFTEkVf+W5M0asHUKfE4y7VDZkpLHM= +k8s.io/kubelet v0.18.8/go.mod h1:6z1jHCk0NPE6WshFStfqcgQ1bnD3tetcPmhC2915aio= 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/kubernetes v1.16.6 h1:ZWSNwxZ1w/IPV7pYH9gohR7AhKmn1VoJ9fEKxmkkeh8= +k8s.io/kubernetes v1.16.6/go.mod h1:rO6tSgbJjbo6lLkrq4jryUaXqZ2PdDJjzWXKZQmLfnQ= 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= +k8s.io/legacy-cloud-providers v0.18.8/go.mod h1:tgp4xYf6lvjrWnjQwTOPvWQE9IVqSBGPF4on0IyICQE= k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw= k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= k8s.io/metrics v0.18.6/go.mod h1:iAwGeabusQNO3duHDM7BBExTUB8L+iq8PM7N9EtQw6g= +k8s.io/metrics v0.18.8/go.mod h1:j7JzZdiyhLP2BsJm/Fzjs+j5Lb1Y7TySjhPWqBPwRXA= +k8s.io/repo-infra v0.0.1-alpha.1/go.mod h1:wO1t9WaB99V80ljbeENTnayuEEwNZt7gECYh/CEyOJ8= +k8s.io/sample-apiserver v0.18.8/go.mod h1:qXPfVwaZwM2owoSMNRRm9vw+HNJGLNsBpGckv1uxWy4= k8s.io/test-infra v0.0.0-20181019233642-2e10a0bbe9b3/go.mod h1:2NzXB13Ji0nqpyublHeiPC4FZwU0TknfvyaaNfl/BTA= k8s.io/test-infra v0.0.0-20191212060232-70b0b49fe247/go.mod h1:d8SKryJBXAwfCFVL4wieRez47J2NOOAb9d029sWLseQ= k8s.io/test-infra v0.0.0-20200407001919-bc7f71ef65b8/go.mod h1:/WpJWcaDvuykB322WXP4kJbX8IpalOzuPxA62GpwkJk= @@ -2727,6 +2895,8 @@ k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 h1:Ly1Oxdu5p5ZFmiVT71LFgeZETvMfZ k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19 h1:7Nu2dTj82c6IaWvL7hImJzcXoTPz1MsSCH7r+0m6rfo= +k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= knative.dev/caching v0.0.0-20190719140829-2032732871ff/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/caching v0.0.0-20200116200605-67bca2c83dfa/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= @@ -2755,6 +2925,7 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= mvdan.cc/xurls/v2 v2.0.0/go.mod h1:2/webFPYOXN9jp/lzuj0zuAVlF+9g4KPFJANH1oJhRU= @@ -2776,6 +2947,7 @@ sigs.k8s.io/controller-runtime v0.3.0/go.mod h1:Cw6PkEg0Sa7dAYovGT4R0tRkGhHXpYij sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= sigs.k8s.io/controller-runtime v0.5.4/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= +sigs.k8s.io/controller-runtime v0.5.7/go.mod h1:KjjGQrdWFaSTHwB5A5VDmX9sMLlvkXjVazxVbfOI3a8= sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM= sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= sigs.k8s.io/controller-runtime v0.6.1 h1:LcK2+nk0kmaOnKGN+vBcWHqY5WDJNJNB/c5pW+sU8fc= @@ -2787,10 +2959,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= diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml index 631cd76b4..dc7edb12c 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml @@ -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 diff --git a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml index 9b5cfec33..92da354d9 100644 --- a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml +++ b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml @@ -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 diff --git a/pkg/commands/refresh.go b/pkg/commands/refresh.go index f63254e17..799217b95 100644 --- a/pkg/commands/refresh.go +++ b/pkg/commands/refresh.go @@ -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" diff --git a/pkg/controller/common/rollout/rollout_plan_controller.go b/pkg/controller/common/rollout/rollout_plan_controller.go index 63632766a..125047695 100644 --- a/pkg/controller/common/rollout/rollout_plan_controller.go +++ b/pkg/controller/common/rollout/rollout_plan_controller.go @@ -2,27 +2,217 @@ 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/rollout/workloads" + "github.com/oam-dev/kubevela/pkg/oam" ) -// ReconcileRolloutPlan generates the rollout plan and reconcile it -func ReconcileRolloutPlan(ctx context.Context, client client.Client, rolloutSpec *v1alpha1.RolloutPlan, - targetWorkload, sourceWorkload *unstructured.Unstructured, rolloutStatus *v1alpha1.RolloutStatus) (v1alpha1.RolloutStatus, error) { - klog.InfoS("generate the rollout plan", "rollout Spec", rolloutSpec, - "target workload", klog.KObj(targetWorkload)) - if sourceWorkload != nil { - klog.InfoS("we will do rolling upgrades", "source workload", klog.KObj(sourceWorkload)) - } - klog.Info("check the rollout status ", "rollout state", rolloutStatus.RollingState, "batch rolling state", - rolloutStatus.BatchRollingState) +// the default time to check back if we still have work to do +const rolloutReconcileRequeueTime = 5 * time.Second - wf := workloads.NewWorkloadControllerFactory(ctx, client, rolloutSpec, targetWorkload, sourceWorkload) - wf.GetController(targetWorkload.GroupVersionKind()) - return *rolloutStatus, nil +// 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, + } + } + }() + + wc, 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 = *wc.Verify(ctx) + + case v1alpha1.InitializingState: + // TODO: call the pre-rollout webhooks + status = *wc.Initialize(ctx) + + case v1alpha1.RollingInBatchesState: + status = r.reconcileBatchInRolling(ctx, wc) + + case v1alpha1.FinalisingState: + // TODO: call the post-rollout webhooks + status = *wc.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, wc 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 := wc.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 = *wc.RolloutOneBatchPods(ctx) + + case v1alpha1.BatchVerifyingState: + // verifying if the application is ready to roll. + // This happens when it's either manual or automatic with analysis + // TODO: call the post-batch webhooks if there are any + + case v1alpha1.BatchReadyState: + // all the pods in the are upgraded and its state is ready + // need to check if they meet the availability requirements in the rollout spec + status = *wc.CheckOneBatchPods(ctx) + + case v1alpha1.BatchFinalizeState: + // indicates that all the pods in the are available, we can move on to the next batch + r.rolloutStatus.CurrentBatch++ + + default: + panic(fmt.Sprintf("illegal status %+v", r.rolloutStatus)) + } + + return status +} + +// 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 + 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) + } } diff --git a/pkg/controller/common/rollout/rollout_state.go b/pkg/controller/common/rollout/rollout_state.go deleted file mode 100644 index 2b438744d..000000000 --- a/pkg/controller/common/rollout/rollout_state.go +++ /dev/null @@ -1,210 +0,0 @@ -package rollout - -import ( - "fmt" - - "k8s.io/klog/v2" - - "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" -) - -type rolloutEvent string - -const ( - // 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" - - // rollingFailedEvent indicates that the rolling is paused - rollingPausedEvent rolloutEvent = "rollingFailedEvent" - - // rollingResumedEvent indicates that the rolling is resumed - rollingResumedEvent rolloutEvent = "rollingResumedEvent" - - // rollingFinalizedEvent indicates that we have finalized the rollout which includes but not - // limited to the resource garbage collection - rollingFinalizedEvent rolloutEvent = "allBatchFinishedEvent" - - // rollingFailedEvent indicates that we encountered an unexpected error during upgrading - rollingFailedEvent rolloutEvent = "rollingFailedEvent" - - // 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" - - // 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" - - // batchRolloutContinueEvent indicates that we need to continue to upgrade the pods in the batch - batchRolloutContinueEvent rolloutEvent = "batchRolloutContinueEvent" - - // batchRolloutWaitingEvent indicates that we are waiting for the approval of resume one batch - batchRolloutWaitingEvent rolloutEvent = "batchWaitRolloutEvent" - - // batchRolloutApprovedEvent indicates that we are waiting for the approval of the - batchRolloutApprovedEvent rolloutEvent = "batchWaitRolloutEvent" - - // batchRolloutFailedEvent indicates that we are waiting for the approval of the - batchRolloutFailedEvent rolloutEvent = "batchRolloutFailedEvent" - - // workloadModifiedEvent indicates that the res - workloadModifiedEvent rolloutEvent = "workloadModifiedEvent" -) - -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" - -// StateMachineTransition 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 StateMachineTransition(rolloutStatus *v1alpha1.RolloutStatus, event rolloutEvent) error { - rollingState := rolloutStatus.RollingState - batchRollingState := rolloutStatus.BatchRollingState - defer klog.InfoS("try to execute a rollout state transition", - "pre rolling state", rollingState, - "pre batch rolling state", batchRollingState, - "post rolling state", rolloutStatus.RollingState, - "post batch rolling state", rolloutStatus.BatchRollingState) - - // we first process the global event - if event == rollingFailedEvent { - rolloutStatus.RollingState = v1alpha1.RolloutFailedState - return nil - } - if event == rollingPausedEvent { - rolloutStatus.RollingState = v1alpha1.PausedState - return nil - } - - switch rollingState { - case v1alpha1.VerifyingState: - if event == rollingSpecVerifiedEvent { - rolloutStatus.RollingState = v1alpha1.InitializingState - return nil - } - return fmt.Errorf(invalidRollingStateTransition, rollingState, event) - - case v1alpha1.InitializingState: - if event == rollingInitializedEvent { - rolloutStatus.RollingState = v1alpha1.RollingInBatchesState - return nil - } - return fmt.Errorf(invalidRollingStateTransition, rollingState, event) - - case v1alpha1.PausedState: - if event == rollingResumedEvent { - // we don't know where it was last time, need to start from beginning - // since we don't change the batch rolling state when we pause - // we should be able to resume if it was rolling before paused - rolloutStatus.RollingState = v1alpha1.VerifyingState - return nil - } - return fmt.Errorf(invalidBatchRollingStateTransition, rollingState, event) - - case v1alpha1.RollingInBatchesState: - return batchStateTransition(rolloutStatus, batchRollingState, event) - - case v1alpha1.FinalisingState: - if event == rollingFinalizedEvent { - rolloutStatus.RollingState = v1alpha1.RolloutSucceedState - return nil - } - return fmt.Errorf(invalidRollingStateTransition, rollingState, event) - - case v1alpha1.RolloutSucceedState: - if event == workloadModifiedEvent { - rolloutStatus.RollingState = v1alpha1.VerifyingState - return nil - } - if event == rollingFinalizedEvent { - // no op - return nil - } - return fmt.Errorf(invalidRollingStateTransition, rollingState, event) - - case v1alpha1.RolloutFailedState: - if event == workloadModifiedEvent { - rolloutStatus.RollingState = v1alpha1.VerifyingState - return nil - } - if event == rollingFailedEvent { - // no op - return nil - } - return fmt.Errorf(invalidRollingStateTransition, rollingState, event) - - default: - return fmt.Errorf("invalid rolling state %s", rollingState) - } -} - -// batchStateTransition handles the state transition when the rollout is in action -func batchStateTransition(rolloutStatus *v1alpha1.RolloutStatus, - batchRollingState v1alpha1.BatchRollingState, event rolloutEvent) error { - switch batchRollingState { - case v1alpha1.BatchInitializingState: - if event == initializedOneBatchEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchInRollingState - return nil - } - return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) - - case v1alpha1.BatchInRollingState: - if event == batchRolloutWaitingEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchVerifyingState - return nil - } - if event == batchRolloutContinueEvent { - // no op - return nil - } - if event == batchRolloutApprovedEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchReadyState - return nil - } - return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) - - case v1alpha1.BatchVerifyingState: - if event == batchRolloutApprovedEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchReadyState - return nil - } - if event == batchRolloutFailedEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchVerifyFailedState - rolloutStatus.RollingState = v1alpha1.RolloutFailedState - return nil - } - return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) - - case v1alpha1.BatchReadyState: - if event == oneBatchAvailableEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchAvailableState - return nil - } - return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) - - case v1alpha1.BatchAvailableState: - if event == finishedOneBatchEvent { - rolloutStatus.BatchRollingState = v1alpha1.BatchInitializingState - return nil - } - if event == allBatchFinishedEvent { - // transition out of the batch loop - rolloutStatus.RollingState = v1alpha1.FinalisingState - return nil - } - return fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event) - - default: - return fmt.Errorf("invalid batch rolling state %s", batchRollingState) - } -} diff --git a/pkg/controller/standard.oam.dev/v1alpha1/rollout/webhook.go b/pkg/controller/common/rollout/rollout_webhook.go similarity index 100% rename from pkg/controller/standard.oam.dev/v1alpha1/rollout/webhook.go rename to pkg/controller/common/rollout/rollout_webhook.go diff --git a/pkg/controller/common/rollout/workloads/cloneset_controller.go b/pkg/controller/common/rollout/workloads/cloneset_controller.go index e1e07e773..831763430 100644 --- a/pkg/controller/common/rollout/workloads/cloneset_controller.go +++ b/pkg/controller/common/rollout/workloads/cloneset_controller.go @@ -1,32 +1,205 @@ package workloads import ( - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "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/oam" ) // CloneSetController is responsible for handle Cloneset type of workloads type CloneSetController struct { - client client.Client - rolloutSpec *v1alpha1.RolloutPlan - targetWorkload *unstructured.Unstructured + client client.Client + recorder event.Recorder + parentController oam.Object + + rolloutSpec *v1alpha1.RolloutPlan + rolloutStatus *v1alpha1.RolloutStatus + workloadNamespacedName types.NamespacedName + cloneSet *kruise.CloneSet } -// Initialize first verify that the cloneset status is compatible with the rollout spec -// it then set the cloneset partition the same as the replicas (no new pod) and add an annotation -func (c *CloneSetController) Initialize() (int32, error) { - return 0, nil +// 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, + } } -// RolloutPods calculates the number of pods we can upgrade once according to the rollout spec +// 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 { + if c.fetchCloneSet(ctx) != nil { + return c.rolloutStatus + } + + // make sure that there are changes in the pod template + targetHash := c.cloneSet.Status.UpdateRevision + if targetHash == c.rolloutStatus.LastAppliedPodTemplateIdentifier { + err := fmt.Errorf("there is no difference between the source and target, hash = %s", targetHash) + klog.Error(err) + c.rolloutStatus.RolloutFailed(err.Error()) + c.recorder.Event(c.parentController, event.Warning("VerifyFailed", err)) + return c.rolloutStatus + } + // record the new pod template hash + c.rolloutStatus.NewPodTemplateIdentifier = targetHash + + // check if the rollout spec is compatible with the current state + // 1. the rollout batch is either automatic or zero + if c.rolloutSpec.BatchPartition != nil && *c.rolloutSpec.BatchPartition != 0 { + err := fmt.Errorf("the rollout plan has to start from zero, partition= %d", *c.rolloutSpec.BatchPartition) + klog.Error(err) + c.rolloutStatus.RolloutFailed(err.Error()) + c.recorder.Event(c.parentController, event.Warning("VerifyFailed", err)) + return c.rolloutStatus + } + // 2. the number of old version in the Cloneset equals to the total number + totalReplicas, _ := c.Size(ctx) + oldVersionPod, _ := intstr.GetValueFromIntOrPercent(c.cloneSet.Spec.UpdateStrategy.Partition, int(totalReplicas), + true) + if oldVersionPod != int(totalReplicas) { + err := fmt.Errorf("the cloneset was still in the middle of updating, number of old pods= %d", oldVersionPod) + klog.Error(err) + c.rolloutStatus.RolloutFailed(err.Error()) + c.recorder.Event(c.parentController, event.Warning("VerifyFailed", err)) + 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) RolloutPods() (int32, error) { - return 0, nil +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 update the Cloneset", err)) + c.rolloutStatus.RolloutRetry(err.Error()) + c.rolloutStatus.StateTransition(v1alpha1.BatchRolloutContinueEvent) + return c.rolloutStatus + } + // record the upgrade + 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.InfoS("checking the rolling out progress", "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 + c.rolloutStatus.StateTransition(v1alpha1.OneBatchAvailableEvent) + } else { + // continue to verify + c.rolloutStatus.StateTransition(v1alpha1.BatchRolloutVerifyingEvent) + } + return c.rolloutStatus } // Finalize makes sure the Cloneset is all upgraded and -func (c *CloneSetController) Finalize() error { +func (c *CloneSetController) Finalize(ctx context.Context) *v1alpha1.RolloutStatus { + if c.fetchCloneSet(ctx) != nil { + return c.rolloutStatus + } + // mark the rollout finalized + c.recorder.Event(c.parentController, event.Normal("Finalized", "Rollout resource are finalized")) + c.rolloutStatus.StateTransition(v1alpha1.RollingFinalizedEvent) + return c.rolloutStatus +} + +// The functions below are helper functions +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 { + klog.CalculateMaxSize() + 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 := c.rolloutStatus.CurrentBatch + newPodTarget := 0 + for i, r := range c.rolloutSpec.RolloutBatches { + batchSize, _ := intstr.GetValueFromIntOrPercent(&r.Replicas, cloneSetSize, true) + if i <= int(currentBatch) { + newPodTarget += batchSize + } else { + break + } + } + klog.InfoS("Calculated the number of new version pod", "new version pod target", newPodTarget) + return newPodTarget +} diff --git a/pkg/controller/common/rollout/workloads/controller.go b/pkg/controller/common/rollout/workloads/controller.go index 5d4f1d782..e49558f8d 100644 --- a/pkg/controller/common/rollout/workloads/controller.go +++ b/pkg/controller/common/rollout/workloads/controller.go @@ -1,28 +1,37 @@ package workloads -// WorkloadController is the interface that all type of workload controller implements -type WorkloadController interface { - // Initialize makes sure that the resources can be upgraded according to the rollout plan - // it returns the number of available pods that are upgrade (with the new spec) - Initialize() (int32, error) +import ( + "context" - // RolloutPods tries to upgrade pods in the resources following the rollout plan + "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 - RolloutPods() (int32, error) + RolloutOneBatchPods(ctx context.Context) *v1alpha1.RolloutStatus - /* - GetMetadata() (string, map[string]int32, error) + // 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 - SyncStatus() error - - SetStatusFailedChecks() error - - ScaleToZero() error - */ // 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 - // or we may add an annotation to indicate the upgrade finished time - Finalize() error + // and we will call the finalize rollout web hooks + Finalize(ctx context.Context) *v1alpha1.RolloutStatus } diff --git a/pkg/controller/common/rollout/workloads/factory.go b/pkg/controller/common/rollout/workloads/factory.go deleted file mode 100644 index 2dbb2a9e5..000000000 --- a/pkg/controller/common/rollout/workloads/factory.go +++ /dev/null @@ -1,47 +0,0 @@ -package workloads - -import ( - "context" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" -) - -// WorkloadControllerFactory is the factory that creates controllers for different types of workload -type WorkloadControllerFactory struct { - client client.Client - rolloutSpec *v1alpha1.RolloutPlan - targetWorkload *unstructured.Unstructured - sourceWorkload *unstructured.Unstructured -} - -// NewWorkloadControllerFactory creates a WorkloadControllerFactory -func NewWorkloadControllerFactory(ctx context.Context, client client.Client, rolloutSpec *v1alpha1.RolloutPlan, - targetWorkload, sourceWorkload *unstructured.Unstructured) *WorkloadControllerFactory { - return &WorkloadControllerFactory{ - client: client, - rolloutSpec: rolloutSpec, - targetWorkload: targetWorkload, - sourceWorkload: sourceWorkload, - } -} - -// GetController generates the controller depends on the workload type -func (f *WorkloadControllerFactory) GetController(kind schema.GroupVersionKind) WorkloadController { - cloneSetCtrl := &CloneSetController{ - client: f.client, - rolloutSpec: f.rolloutSpec, - targetWorkload: f.targetWorkload, - } - - switch kind.Kind { - case "CloneSet": - return cloneSetCtrl - - default: - return cloneSetCtrl - } -} diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go b/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go index 457f86ab9..68b4efd9d 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationdeployment/applicationdeployment_controller.go @@ -14,9 +14,9 @@ import ( "k8s.io/kubectl/pkg/util/slice" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" corev1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" - "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" "github.com/oam-dev/kubevela/pkg/controller/common/rollout" controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application" @@ -25,7 +25,7 @@ import ( ) const appDeployFinalizer = "finalizers.applicationdeployment.oam.dev" -const reconcileTimeOut = 10 * time.Second +const reconcileTimeOut = 30 * time.Second // Reconciler reconciles an ApplicationDeployment object type Reconciler struct { @@ -41,11 +41,25 @@ type Reconciler struct { // +kubebuilder:rbac:groups=core.oam.dev,resources=applications/status,verbs=get;update;patch // Reconcile is the main logic of applicationdeployment controller -func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { +func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr error) { var appDeploy corev1alpha2.ApplicationDeployment - requeueAfterTime := 5 * time.Second ctx, cancel := context.WithTimeout(context.TODO(), reconcileTimeOut) defer cancel() + + startTime := time.Now() + defer func() { + if retErr == nil { + if res.Requeue || res.RequeueAfter > 0 { + klog.InfoS("Finished reconciling appDeployment", "deployment", req, "time spent", + time.Since(startTime), "result", res) + } else { + klog.InfoS("Finished reconcile appDeployment", "deployment", req, "time spent", time.Since(startTime)) + } + } else { + klog.Errorf("Failed to reconcile appDeployment %s: %v", req, retErr) + } + }() + if err := r.Get(ctx, req.NamespacedName, &appDeploy); err != nil { if apierrors.IsNotFound(err) { klog.InfoS("application deployment does not exist", "appDeploy", klog.KRef(req.Namespace, req.Name)) @@ -91,7 +105,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { klog.ErrorS(err, "cannot fetch the workloads to upgrade", "workload Type", workloadType, "workload GVK", *workloadGVK, "target application", klog.KRef(req.Namespace, targetAppName), "source application", klog.KRef(req.Namespace, sourceAppName)) - return ctrl.Result{RequeueAfter: requeueAfterTime}, client.IgnoreNotFound(err) + return ctrl.Result{RequeueAfter: 5 * time.Second}, client.IgnoreNotFound(err) } klog.InfoS("get the target workload we need to work on", "targetWorkload", klog.KObj(targetWorkload)) @@ -111,20 +125,13 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } // reconcile the rollout part of the spec given the target and source workload - rolloutStatus, err := rollout.ReconcileRolloutPlan(ctx, r, &appDeploy.Spec.RolloutPlan, targetWorkload, - sourceWorkload, &appDeploy.Status) - if err != nil { - klog.ErrorS(err, "cannot reconcile the rollout plan", "rollout spec", appDeploy.Spec.RolloutPlan) - return ctrl.Result{}, err - } - - appDeploy.Status = rolloutStatus - if rolloutStatus.RollingState == v1alpha1.RolloutFailedState || - rolloutStatus.RollingState == v1alpha1.RolloutSucceedState { - // we don't need to keep checking the rollout too frequently if the rollout is at a terminal state - requeueAfterTime = 30 * time.Second - } - return ctrl.Result{RequeueAfter: requeueAfterTime}, r.Update(ctx, &appDeploy) + rolloutPlanController := rollout.NewRolloutPlanController(r, &appDeploy, r.record, + &appDeploy.Spec.RolloutPlan, appDeploy.Status.RolloutStatus, targetWorkload, sourceWorkload) + result, rolloutStatus := rolloutPlanController.Reconcile(ctx) + // make sure that the new status is copied back + appDeploy.Status.RolloutStatus = rolloutStatus + // update the appDeploy status + return result, r.Update(ctx, &appDeploy) } func (r *Reconciler) handleFinalizer(appDeploy *corev1alpha2.ApplicationDeployment) { diff --git a/pkg/utils/apply/apply.go b/pkg/utils/apply/apply.go index abb7d548b..2d68e32f4 100644 --- a/pkg/utils/apply/apply.go +++ b/pkg/utils/apply/apply.go @@ -11,13 +11,9 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" -) -// An Object is a Kubernetes object. -type Object interface { - metav1.Object - runtime.Object -} + "github.com/oam-dev/kubevela/pkg/oam" +) // Applicator applies new state to an object or create it if not exist. // It employes the same mechanism as `kubectl apply`, that is, for each resource being applied, @@ -93,7 +89,7 @@ func (a *APIApplicator) Apply(ctx context.Context, desired runtime.Object, ao .. // createOrGetExisting will create the object if it does not exist // or get and return the existing object func createOrGetExisting(ctx context.Context, c client.Client, desired runtime.Object, ao ...ApplyOption) (runtime.Object, error) { - m, ok := desired.(Object) + m, ok := desired.(oam.Object) if !ok { return nil, errors.New("cannot access object metadata") } diff --git a/pkg/webhook/common/rollout/rollout_plan.go b/pkg/webhook/common/rollout/rollout_plan.go index ccae056ce..bda2d23aa 100644 --- a/pkg/webhook/common/rollout/rollout_plan.go +++ b/pkg/webhook/common/rollout/rollout_plan.go @@ -13,9 +13,11 @@ func DefaultRolloutPlan(rollout *v1alpha1.RolloutPlan) { // ValidateCreate validate the rollout plan func ValidateCreate(rollout *v1alpha1.RolloutPlan) field.ErrorList { - // 1. The total number of replicas in the batches match the current target resource pod size + var allErrs field.ErrorList + // 1. The total number of num in the batches match the current target resource pod size // 2. The TargetSize and NumBatches are mutually exclusive to RolloutBatches - return nil + + return allErrs } // ValidateUpdate validate if one can change the rollout plan from the previous psec From 10bf5739ca9435925e3a994432a241ca4390089f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 4 Feb 2021 17:11:04 -0800 Subject: [PATCH 30/38] fix lint --- go.mod | 1 - go.sum | 171 --------------------------------------------------------- 2 files changed, 172 deletions(-) diff --git a/go.mod b/go.mod index 3fe639794..8d355d5f8 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,6 @@ require ( k8s.io/klog/v2 v2.0.0 k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 k8s.io/kubectl v0.18.6 - k8s.io/kubernetes v1.14.7 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 sigs.k8s.io/controller-runtime v0.6.2 sigs.k8s.io/controller-tools v0.2.4 diff --git a/go.sum b/go.sum index 8cd1f874a..149af76b0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM= cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -96,7 +95,6 @@ github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/azure-sdk-for-go v28.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v34.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v36.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -171,7 +169,6 @@ github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced3 github.com/GoogleCloudPlatform/testgrid v0.0.1-alpha.3/go.mod h1:f96W2HYy3tiBNV5zbbRc+NczwYHgG1PHXMQfoEWv680= github.com/GoogleCloudPlatform/testgrid v0.0.7/go.mod h1:lmtHGBL0M/MLbu1tR9BWV7FGZ1FEFIdPqmJiHNCL7y8= github.com/GoogleCloudPlatform/testgrid v0.0.13/go.mod h1:UlC/MvnkKjiVGijIKOHxnVyhDiTDCydw9H1XzmclQGU= -github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -190,11 +187,9 @@ github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZ github.com/Masterminds/squirrel v1.2.0 h1:K1NhbTO21BWG47IVR0OnIZuE0LZcXAYqywrC3Ko53KI= github.com/Masterminds/squirrel v1.2.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -203,7 +198,6 @@ github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nB github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -212,7 +206,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -248,7 +241,6 @@ github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= -github.com/appscode/jsonpatch v1.0.1/go.mod h1:4AJxUpXUhv4N+ziTvIcWWXgeorXpxPZOfk9HdEVr96M= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -262,7 +254,6 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/aws/aws-k8s-tester v0.0.0-20190114231546-b411acf57dfe/go.mod h1:1ADF5tAtU1/mVtfMcHAYSm2fPw71DA7fFk0yed64/0I= github.com/aws/aws-k8s-tester v0.9.3/go.mod h1:nsh1f7joi8ZI1lvR+Ron6kJM2QdCYPU/vFePghSSuTc= github.com/aws/aws-k8s-tester v1.0.0/go.mod h1:NUNd9k43+h9O5tvwL+4N1Ctb//SapmeeFX1G0/2/0Qc= @@ -282,7 +273,6 @@ github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.28.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.32/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.29.34/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.30.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= @@ -297,11 +287,7 @@ github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZw github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= -github.com/bazelbuild/bazel-gazelle v0.18.2/go.mod h1:D0ehMSbS+vesFsLGiD6JXu3mVEzOlfUl8wNnq+x/9p0= -github.com/bazelbuild/bazel-gazelle v0.19.1-0.20191105222053-70208cbdc798/go.mod h1:rPwzNHUqEzngx1iVBfO/2X2npKaT3tqPqqHW6rVsn/A= -github.com/bazelbuild/buildtools v0.0.0-20190731111112-f720930ceb60/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= github.com/bazelbuild/buildtools v0.0.0-20190917191645-69366ca98f89/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= -github.com/bazelbuild/rules_go v0.0.0-20190719190356-6dae44dc5cab/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= @@ -311,7 +297,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -329,7 +314,6 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/brancz/kube-rbac-proxy v0.5.0/go.mod h1:cL2VjiIFGS90Cjh5ZZ8+It6tMcBt8rwvuw2J6Mamnl0= github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0= @@ -346,18 +330,15 @@ github.com/bwmarrin/snowflake v0.0.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/ github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= -github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -366,8 +347,6 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= -github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -381,8 +360,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3/go.mod h1:j1nZWMLGg3om8SswStBoY6/SHvcLM19MuZqwDtMtmzs= github.com/cloudevents/sdk-go v1.0.0/go.mod h1:3TkmM0cFqkhCHOq5JzzRU/RxRkwzoS8TZ+G448qVTog= github.com/cloudevents/sdk-go/v2 v2.0.0/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU= -github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= -github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -392,13 +369,9 @@ github.com/cockroachdb/apd/v2 v2.0.1 h1:y1Rh3tEU89D+7Tgbw+lp52T6p/GJLpDmNvr10UWq github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= -github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= @@ -412,9 +385,6 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -429,7 +399,6 @@ github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -438,7 +407,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/prometheus-operator v0.41.1 h1:MEhY9syliPlQg+VlFRUfNodUEVXRXJ2n1pFG0aBp+mI= github.com/coreos/prometheus-operator v0.41.1/go.mod h1:LhLfEBydppl7nvfEA1jIqlF3xJ9myHCnzrU+HHDxRd4= -github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -492,7 +460,6 @@ github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA= @@ -500,7 +467,6 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= @@ -541,10 +507,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.6/go.mod h1:GFqM7v0B62MraO4PWRedIbhThr/Rf7ev6aHOOPXeaDA= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= -github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -554,7 +518,6 @@ github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v0.0.0-20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= @@ -598,16 +561,12 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= -github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I= -github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-critic/go-critic v0.5.0/go.mod h1:4jeRh3ZAVnRYhuWdOEvwzVqLUpxMSoAT0xZ74JsTPlo= @@ -725,7 +684,6 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= @@ -801,7 +759,6 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -833,7 +790,6 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -869,19 +825,14 @@ github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8ju github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= -github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= -github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.18.0/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg= github.com/golangci/golangci-lint v1.23.7/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng= github.com/golangci/golangci-lint v1.30.0/go.mod h1:5t0i3wHlqQc9deBBvZsP+a/4xz7cfjV+zhp5U0Mzp14= -github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= -github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= @@ -894,7 +845,6 @@ github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAO github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v1.7.0/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/diff v0.0.0-20181124234638-500114f11e71/go.mod h1:22dM4PLscQl+Nzf64qNBurVJvfyvZELT0iRW2l/NN70= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= @@ -908,8 +858,6 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Z github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= @@ -1010,7 +958,6 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= @@ -1103,7 +1050,6 @@ github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -1111,7 +1057,6 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8 github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -1130,8 +1075,6 @@ github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0 github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/heketi/heketi v9.0.1-0.20190917153846-c2e2a4ab7ab9+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= -github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -1169,13 +1112,11 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jenkins-x/go-scm v1.5.65/go.mod h1:MgGRkJScE/rJ30J/bXYqduN5sDPZqZFITJopsnZmTOw= github.com/jenkins-x/go-scm v1.5.79/go.mod h1:PCT338UhP/pQ0IeEeMEf/hoLTYKcH7qjGEKd7jPkeYg= github.com/jenkins-x/go-scm v1.5.117/go.mod h1:PCT338UhP/pQ0IeEeMEf/hoLTYKcH7qjGEKd7jPkeYg= github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jinzhu/gorm v0.0.0-20170316141641-572d0a0ab1eb/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= @@ -1226,7 +1167,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -1236,7 +1176,6 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -1270,7 +1209,6 @@ github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= @@ -1300,7 +1238,6 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -1310,13 +1247,7 @@ github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lovoo/gcloud-opentracing v0.3.0/go.mod h1:ZFqk2y38kMDDikZPAK7ynTTGuyt17nSPdS3K5e+ZTBY= -github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= -github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= -github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk= -github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao= -github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1336,7 +1267,6 @@ github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzfe github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= @@ -1373,7 +1303,6 @@ github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1N github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v0.0.0-20160514122348-38ee283dabf1/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1391,24 +1320,18 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= -github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= -github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= github.com/minio/minio-go/v6 v6.0.49/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -1416,7 +1339,6 @@ github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:Sfy github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -1436,7 +1358,6 @@ github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWe github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -1453,30 +1374,23 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= -github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= github.com/nats-io/go-nats v1.7.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= @@ -1490,8 +1404,6 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= -github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -1557,15 +1469,9 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2Pupw60ON8TYEIGGTAI77yZsWYkiOeHFZWkwlCk= -github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 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-spec v1.0.0/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/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= -github.com/openkruise/kruise v0.7.0 h1:Uap2OiKojQovxeSvxk5CGvUnZ6SIKGrIH872JPF+Z88= -github.com/openkruise/kruise v0.7.0/go.mod h1:/8LlpF0EUkDAJCi/5Zo1MxOLLN0VF4qzyjZ1TXmwzN4= 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= @@ -1591,7 +1497,6 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= -github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= @@ -1626,7 +1531,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus-community/prom-label-proxy v0.1.1-0.20200616110844-0fbfa11fa8f3/go.mod h1:XdjyZg7LCbCC5FADHtpgNp6kQ0W9beXVGfmcvndMj5Y= github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE= github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg= @@ -1703,13 +1607,10 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190706150252-9beb055b7962/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -1729,7 +1630,6 @@ github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 h1:xkBtI5JktwbW/vf4vopBbhYsRFTGfQWHYXzC0/qYwxI= github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= -github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -1739,7 +1639,6 @@ github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUcc github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -1755,7 +1654,6 @@ github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrY github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= @@ -1766,7 +1664,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/servicemeshinterface/smi-sdk-go v0.4.1/go.mod h1:9rsLPBNcqfDNmEgyYwpopn93aE9yz46d2EHFBNOYj/w= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/githubv4 v0.0.0-20180925043049-51d7b505e2e9/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= @@ -1784,7 +1681,6 @@ github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJV github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1809,18 +1705,15 @@ github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34c github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.0-20180629152535-a114f312e075/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -1829,7 +1722,6 @@ github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1839,7 +1731,6 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= @@ -1847,7 +1738,6 @@ github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfD github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/ssgreg/nlreturn/v2 v2.0.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1877,9 +1767,7 @@ github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0 github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= -github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tektoncd/pipeline v0.8.0/go.mod h1:IZzJdiX9EqEMuUcgdnElozdYYRh0/ZRC+NKMLj1K3Yw= @@ -1894,9 +1782,7 @@ github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQx github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc= -github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -1939,7 +1825,6 @@ github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1947,13 +1832,11 @@ github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= -github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/quicktemplate v1.5.1/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= @@ -1961,9 +1844,6 @@ github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44 github.com/vdemeester/k8s-pkg-credentialprovider v1.13.12-1/go.mod h1:Fko0rTxEtDW2kju5Ky7yFJNS3IcNvW8IPsp4/e9oev0= github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wonderflow/cert-manager-api v1.0.3 h1:xQQMkJNQ12oYyy00jOQUlSKgdraApaURxv3PHFdVTfA= @@ -1992,8 +1872,6 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/xyproto/pinterface v0.0.0-20200201214933-70763765f31f/go.mod h1:X5B5pKE49ak7SpyDh5QvJvLH9cC9XuZNDcl5hEyYc34= -github.com/xyproto/simpleredis v0.0.0-20200201215242-1ff0da2967b4/go.mod h1:U/ZOQqa0ggBGPs+d0y7r50BY6FyFTh5WhWf7F8f1MBM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -2067,29 +1945,22 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= -golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -2173,7 +2044,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2181,7 +2051,6 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2191,10 +2060,8 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -2254,7 +2121,6 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2276,7 +2142,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2297,7 +2162,6 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omN golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2308,8 +2172,6 @@ golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190122202912-9c309ee22fab/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2344,7 +2206,6 @@ golang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190918214516-5a1a30219888/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2635,7 +2496,6 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -2675,11 +2535,8 @@ gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8 h1:jL/vaozO53FMfZLySWM+4nulF gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= helm.sh/helm/v3 v3.1.1/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g= helm.sh/helm/v3 v3.2.0/go.mod h1:ZaXz/vzktgwjyGGFbUWtIQkscfE7WYoRGP2szqAFHR0= helm.sh/helm/v3 v3.2.4 h1:lz/0ZRkSgyIF+pCo6pjFzap1udCARB1IN6CRfqkpcOg= @@ -2771,21 +2628,15 @@ k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= 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= -k8s.io/apiserver v0.18.8 h1:Au4kMn8sb1zFdyKqc8iMHLsYLxRI6Y+iAhRNKKQtlBY= -k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM= k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= k8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA= k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= k8s.io/cli-runtime v0.18.6 h1:I8BkH5NyqMQ4zqUBmpXJ1LxIqpCH88H/1edPkPVWzjQ= k8s.io/cli-runtime v0.18.6/go.mod h1:+G/WTNqHgUv636e5y7rhOQ7epUbRXnwmPnhOhD6t9uM= -k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k= -k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M= k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM= k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= -k8s.io/cloud-provider v0.18.8/go.mod h1:cn9AlzMPVIXA4HHLVbgGUigaQlZyHSZ7WAwDEFNrQSs= -k8s.io/cluster-bootstrap v0.18.8/go.mod h1:guq0Uc+QwazHgpS1yAw5Z7yUlBCtGppbgWQkbN3lxIY= 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= @@ -2813,12 +2664,8 @@ k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmD k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= k8s.io/component-base v0.18.6 h1:Wd6cHGwJN2qpufnirVOB3oMhyhbioGsKEi5HeDBsV+s= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= -k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8= -k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU= -k8s.io/cri-api v0.18.8/go.mod h1:OJtpjDvfsKoLGhvcc0qfygved0S0dGX56IJzPbqTG1s= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= -k8s.io/csi-translation-lib v0.18.8/go.mod h1:6cA6Btlzxy9s3QrS4BCZzQqclIWnTLr6Jx3H2ctAzY4= k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190306031000-7a1b7fb0289f/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -2826,7 +2673,6 @@ k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20191108084044-e500ee069b5c/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200205140755-e0e292d8aa12/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -2836,8 +2682,6 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/kube-aggregator v0.18.8/go.mod h1:CyLoGZB+io8eEwnn+6RbV7QWJQhj8a3TBH8ZM8sLbhI= -k8s.io/kube-controller-manager v0.18.8/go.mod h1:IYZteddXJFD1TVgAw8eRP3c9OOA2WtHdXdE8aH6gXnc= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= @@ -2849,31 +2693,20 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOEC k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= -k8s.io/kube-proxy v0.18.8/go.mod h1:u4E8OsUpUzfZ9CEFf9rdLsbYiusZr8utbtF4WQrX+qs= -k8s.io/kube-scheduler v0.18.8/go.mod h1:OeliYiILv1XkSq0nmQjRewgt5NimKsTidZFEhfL5fqA= k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk= k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= k8s.io/kubectl v0.18.6 h1:IFPNuLPkZ59vSGQzynXY8XGz9yuOSRpkJupnobdYvO4= k8s.io/kubectl v0.18.6/go.mod h1:3TLzFOrF9h4mlRPAvdNkDbs5NWspN4e0EnPnEB41CGo= -k8s.io/kubectl v0.18.8 h1:qTkHCz21YmK0+S0oE6TtjtxmjeDP42gJcZJyRKsIenA= -k8s.io/kubectl v0.18.8/go.mod h1:PlEgIAjOMua4hDFTEkVf+W5M0asHUKfE4y7VDZkpLHM= -k8s.io/kubelet v0.18.8/go.mod h1:6z1jHCk0NPE6WshFStfqcgQ1bnD3tetcPmhC2915aio= 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/kubernetes v1.16.6 h1:ZWSNwxZ1w/IPV7pYH9gohR7AhKmn1VoJ9fEKxmkkeh8= -k8s.io/kubernetes v1.16.6/go.mod h1:rO6tSgbJjbo6lLkrq4jryUaXqZ2PdDJjzWXKZQmLfnQ= 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= -k8s.io/legacy-cloud-providers v0.18.8/go.mod h1:tgp4xYf6lvjrWnjQwTOPvWQE9IVqSBGPF4on0IyICQE= k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw= k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= k8s.io/metrics v0.18.6/go.mod h1:iAwGeabusQNO3duHDM7BBExTUB8L+iq8PM7N9EtQw6g= -k8s.io/metrics v0.18.8/go.mod h1:j7JzZdiyhLP2BsJm/Fzjs+j5Lb1Y7TySjhPWqBPwRXA= -k8s.io/repo-infra v0.0.1-alpha.1/go.mod h1:wO1t9WaB99V80ljbeENTnayuEEwNZt7gECYh/CEyOJ8= -k8s.io/sample-apiserver v0.18.8/go.mod h1:qXPfVwaZwM2owoSMNRRm9vw+HNJGLNsBpGckv1uxWy4= k8s.io/test-infra v0.0.0-20181019233642-2e10a0bbe9b3/go.mod h1:2NzXB13Ji0nqpyublHeiPC4FZwU0TknfvyaaNfl/BTA= k8s.io/test-infra v0.0.0-20191212060232-70b0b49fe247/go.mod h1:d8SKryJBXAwfCFVL4wieRez47J2NOOAb9d029sWLseQ= k8s.io/test-infra v0.0.0-20200407001919-bc7f71ef65b8/go.mod h1:/WpJWcaDvuykB322WXP4kJbX8IpalOzuPxA62GpwkJk= @@ -2895,8 +2728,6 @@ k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 h1:Ly1Oxdu5p5ZFmiVT71LFgeZETvMfZ k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19 h1:7Nu2dTj82c6IaWvL7hImJzcXoTPz1MsSCH7r+0m6rfo= -k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= knative.dev/caching v0.0.0-20190719140829-2032732871ff/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/caching v0.0.0-20200116200605-67bca2c83dfa/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= @@ -2925,7 +2756,6 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= mvdan.cc/xurls/v2 v2.0.0/go.mod h1:2/webFPYOXN9jp/lzuj0zuAVlF+9g4KPFJANH1oJhRU= @@ -2947,7 +2777,6 @@ sigs.k8s.io/controller-runtime v0.3.0/go.mod h1:Cw6PkEg0Sa7dAYovGT4R0tRkGhHXpYij sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= sigs.k8s.io/controller-runtime v0.5.4/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= -sigs.k8s.io/controller-runtime v0.5.7/go.mod h1:KjjGQrdWFaSTHwB5A5VDmX9sMLlvkXjVazxVbfOI3a8= sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM= sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= sigs.k8s.io/controller-runtime v0.6.1 h1:LcK2+nk0kmaOnKGN+vBcWHqY5WDJNJNB/c5pW+sU8fc= From 9230e5c1817f2602a11e7a9da65bc23f929c2d06 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Fri, 5 Feb 2021 11:31:37 +0800 Subject: [PATCH 31/38] Revert package management and build tool to npm As yarn failed to build and start the dashboard most of time, revert the tool to npm --- Makefile | 2 +- dashboard/README.md | 86 ++++++++++++++++++++------------------------- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/Makefile b/Makefile index 2089e4681..8c2cb9422 100644 --- a/Makefile +++ b/Makefile @@ -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/ diff --git a/dashboard/README.md b/dashboard/README.md index b47c064f1..d5f31fb8f 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -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 ``` From 96a589b0984e2d4e209b949363f45741a707c9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Thu, 4 Feb 2021 22:09:09 +0800 Subject: [PATCH 32/38] move template out from extension --- apis/core.oam.dev/v1alpha2/core_types.go | 20 ++ apis/types/capability.go | 16 -- .../crds/core.oam.dev_traitdefinitions.yaml | 6 + .../core.oam.dev_workloaddefinitions.yaml | 6 + .../templates/defwithtemplate/ingress.yaml | 95 +++++---- .../defwithtemplate/manualscale.yaml | 29 ++- .../templates/defwithtemplate/task.yaml | 73 ++++--- .../templates/defwithtemplate/webservice.yaml | 161 ++++++++------- .../templates/defwithtemplate/worker.yaml | 73 ++++--- config/samples/app-with-status/template.yaml | 184 +++++++++--------- hack/vela-templates/definitions/ingress.yaml | 3 +- .../definitions/manualscale.yaml | 3 +- hack/vela-templates/definitions/task.yaml | 3 +- .../definitions/webservice.yaml | 3 +- hack/vela-templates/definitions/worker.yaml | 3 +- hack/vela-templates/gen_definitions.sh | 2 +- .../crds/core.oam.dev_traitdefinitions.yaml | 6 + .../core.oam.dev_workloaddefinitions.yaml | 6 + pkg/appfile/parser.go | 10 +- .../application_controller_test.go | 181 +++++++++-------- pkg/oam/util/template.go | 43 +++- pkg/oam/util/template_test.go | 174 ++++++++++++++++- pkg/plugins/capcenter.go | 4 +- pkg/plugins/cluster.go | 36 ++-- .../traitdefinition/validating_handler.go | 15 +- 25 files changed, 681 insertions(+), 474 deletions(-) diff --git a/apis/core.oam.dev/v1alpha2/core_types.go b/apis/core.oam.dev/v1alpha2/core_types.go index 49118cf63..48433a600 100644 --- a/apis/core.oam.dev/v1alpha2/core_types.go +++ b/apis/core.oam.dev/v1alpha2/core_types.go @@ -68,6 +68,16 @@ type WorkloadDefinitionSpec struct { // +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 @@ -140,6 +150,16 @@ 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"` diff --git a/apis/types/capability.go b/apis/types/capability.go index fa89e52a2..3056535c4 100644 --- a/apis/types/capability.go +++ b/apis/types/capability.go @@ -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 diff --git a/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml b/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml index 8a60ff7ed..ac0ff3ac2 100644 --- a/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml @@ -78,6 +78,12 @@ spec: 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 diff --git a/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml b/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml index 4fefb019e..15afb139b 100644 --- a/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml @@ -92,6 +92,12 @@ spec: 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 diff --git a/charts/vela-core/templates/defwithtemplate/ingress.yaml b/charts/vela-core/templates/defwithtemplate/ingress.yaml index 89cfa60f9..540b88f2d 100644 --- a/charts/vela-core/templates/defwithtemplate/ingress.yaml +++ b/charts/vela-core/templates/defwithtemplate/ingress.yaml @@ -20,51 +20,50 @@ spec: 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 + } + }, + ] + } + }] + } + } + diff --git a/charts/vela-core/templates/defwithtemplate/manualscale.yaml b/charts/vela-core/templates/defwithtemplate/manualscale.yaml index 2d8c16a44..3fe214c37 100644 --- a/charts/vela-core/templates/defwithtemplate/manualscale.yaml +++ b/charts/vela-core/templates/defwithtemplate/manualscale.yaml @@ -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 + } + diff --git a/charts/vela-core/templates/defwithtemplate/task.yaml b/charts/vela-core/templates/defwithtemplate/task.yaml index 02b32fd36..98f937559 100644 --- a/charts/vela-core/templates/defwithtemplate/task.yaml +++ b/charts/vela-core/templates/defwithtemplate/task.yaml @@ -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] + } + diff --git a/charts/vela-core/templates/defwithtemplate/webservice.yaml b/charts/vela-core/templates/defwithtemplate/webservice.yaml index 6830fb7cd..f808db89a 100644 --- a/charts/vela-core/templates/defwithtemplate/webservice.yaml +++ b/charts/vela-core/templates/defwithtemplate/webservice.yaml @@ -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 + } + diff --git a/charts/vela-core/templates/defwithtemplate/worker.yaml b/charts/vela-core/templates/defwithtemplate/worker.yaml index 77e3f6e03..9ddeecb62 100644 --- a/charts/vela-core/templates/defwithtemplate/worker.yaml +++ b/charts/vela-core/templates/defwithtemplate/worker.yaml @@ -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] + } + diff --git a/config/samples/app-with-status/template.yaml b/config/samples/app-with-status/template.yaml index 621ad3f3d..c32773dcb 100644 --- a/config/samples/app-with-status/template.yaml +++ b/config/samples/app-with-status/template.yaml @@ -1,7 +1,7 @@ apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: - name: worker + 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: @@ -12,60 +12,57 @@ spec: 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 - extension: - template: | - output: { - apiVersion: "apps/v1" - kind: "Deployment" - spec: { - selector: matchLabels: { - "app.oam.dev/component": context.name - } + 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 - } + 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 - } + 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 @@ -78,46 +75,45 @@ spec: 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 - extension: - 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 - } - } - ] - } - }] - } - } \ No newline at end of file + 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 + } + }, + ] + } + }] + } + } diff --git a/hack/vela-templates/definitions/ingress.yaml b/hack/vela-templates/definitions/ingress.yaml index a458aa169..fd0bb9959 100644 --- a/hack/vela-templates/definitions/ingress.yaml +++ b/hack/vela-templates/definitions/ingress.yaml @@ -19,5 +19,4 @@ spec: appliesToWorkloads: - webservice - worker - extension: - template: | + template: | diff --git a/hack/vela-templates/definitions/manualscale.yaml b/hack/vela-templates/definitions/manualscale.yaml index ce5b7cc38..246291d44 100644 --- a/hack/vela-templates/definitions/manualscale.yaml +++ b/hack/vela-templates/definitions/manualscale.yaml @@ -11,5 +11,4 @@ spec: definitionRef: name: manualscalertraits.core.oam.dev workloadRefPath: spec.workloadRef - extension: - template: |- + template: | diff --git a/hack/vela-templates/definitions/task.yaml b/hack/vela-templates/definitions/task.yaml index 0c45ef657..525794a31 100644 --- a/hack/vela-templates/definitions/task.yaml +++ b/hack/vela-templates/definitions/task.yaml @@ -7,5 +7,4 @@ metadata: spec: definitionRef: name: jobs.batch - extension: - template: | + template: | diff --git a/hack/vela-templates/definitions/webservice.yaml b/hack/vela-templates/definitions/webservice.yaml index 6bc007ebc..ca03df0dc 100644 --- a/hack/vela-templates/definitions/webservice.yaml +++ b/hack/vela-templates/definitions/webservice.yaml @@ -8,5 +8,4 @@ metadata: spec: definitionRef: name: deployments.apps - extension: - template: | + template: | diff --git a/hack/vela-templates/definitions/worker.yaml b/hack/vela-templates/definitions/worker.yaml index 18e818b79..9aea430c5 100644 --- a/hack/vela-templates/definitions/worker.yaml +++ b/hack/vela-templates/definitions/worker.yaml @@ -7,5 +7,4 @@ metadata: spec: definitionRef: name: deployments.apps - extension: - template: | + template: | diff --git a/hack/vela-templates/gen_definitions.sh b/hack/vela-templates/gen_definitions.sh index 2e5c55d23..1ee5ece1e 100755 --- a/hack/vela-templates/gen_definitions.sh +++ b/hack/vela-templates/gen_definitions.sh @@ -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%.*}" diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml index 5581c7ac7..a3093d35b 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml @@ -77,6 +77,12 @@ spec: 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 diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml index 27e0866a0..28ecc1a4e 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml @@ -91,6 +91,12 @@ spec: 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 diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index a9278b8e0..c872dc97f 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -230,7 +230,7 @@ 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, app.Name, wl.Name) @@ -266,7 +266,7 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compNa 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) } labels := map[string]string{ @@ -284,7 +284,7 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compNa 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) } labels := map[string]string{ oam.TraitTypeLabel: assist.Type, @@ -314,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 } diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index abe022e3c..02a9fad42 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -1227,58 +1227,57 @@ spec: 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 - extension: - template: | - output: { - apiVersion: "apps/v1" - kind: "Deployment" - spec: { - selector: matchLabels: { - "app.oam.dev/component": context.name - } + 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 - } + 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 - } - }] - } - } - } - } + 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 - } - } + 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 - } + 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 @@ -1394,49 +1393,49 @@ spec: 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 - extension: - 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 - } - } - ] - } - }] - } - }` + 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 { diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index 05a080e23..6d16e7e00 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -48,7 +48,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if wd.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := NewTemplate(wd.Spec.Extension, wd.Spec.Status) + tmpl, err := NewTemplate(wd.Spec.Template, wd.Spec.Status, wd.Spec.Extension) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -67,7 +67,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if td.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := NewTemplate(td.Spec.Extension, td.Spec.Status) + tmpl, err := NewTemplate(td.Spec.Template, td.Spec.Status, td.Spec.Extension) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -79,18 +79,24 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e case types.TypeScope: // TODO: add scope template support } - return nil, fmt.Errorf("kind(%s) of %s not supported", kd, key) } // NewTemplate will create CUE template for inner AbstractEngine using. -func NewTemplate(raw *runtime.RawExtension, status *v1alpha2.Status) (*Template, error) { +func NewTemplate(template string, status *v1alpha2.Status, raw *runtime.RawExtension) (*Template, error) { extension := map[string]interface{}{} - if err := json.Unmarshal(raw.Raw, &extension); err != nil { - return nil, err - } tmp := &Template{ - TemplateStr: fmt.Sprint(extension["template"]), + TemplateStr: template, + } + if tmp.TemplateStr == "" && raw != nil { + if err := json.Unmarshal(raw.Raw, &extension); err != nil { + return nil, err + } + if extTemplate, ok := extension["template"]; ok { + if tmpStr, ok := extTemplate.(string); ok { + tmp.TemplateStr = tmpStr + } + } } if status != nil { tmp.CustomStatus = status.CustomStatus @@ -98,3 +104,24 @@ func NewTemplate(raw *runtime.RawExtension, status *v1alpha2.Status) (*Template, } return tmp, nil } + +// ConvertTemplateJSON2Object convert spec.extension to object +func ConvertTemplateJSON2Object(in *runtime.RawExtension, specTemplate string) (types.Capability, error) { + var t types.Capability + capTemplate, err := NewTemplate(specTemplate, nil, in) + if err != nil { + return t, errors.Wrapf(err, "parse cue template") + } + var extension types.Capability + if in != nil && in.Raw != nil { + err := json.Unmarshal(in.Raw, &extension) + if err != nil { + return t, errors.Wrapf(err, "parse extension fail") + } + t = extension + } + if capTemplate.TemplateStr != "" { + t.CueTemplate = capTemplate.TemplateStr + } + return t, err +} diff --git a/pkg/oam/util/template_test.go b/pkg/oam/util/template_test.go index 63ee0a818..a667f2313 100644 --- a/pkg/oam/util/template_test.go +++ b/pkg/oam/util/template_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "cuelang.org/go/cue" "github.com/crossplane/crossplane-runtime/pkg/test" "k8s.io/apimachinery/pkg/runtime" @@ -13,7 +15,7 @@ import ( "github.com/oam-dev/kubevela/apis/types" ) -func TestTemplate(t *testing.T) { +func TestLoadWorkloadTemplate(t *testing.T) { cueTemplate := ` context: { name: "test" @@ -110,3 +112,173 @@ spec: t.Errorf("parsered template is not correct") } } + +func TestLoadTraitTemplate(t *testing.T) { + cueTemplate := ` + parameter: { + domain: string + http: [string]: int + } + context: { + name: "test" + } + // 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 + } + }, + ] + } + }] + } + } + ` + + var traitDefintion = ` +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Configures K8s ingress and service to enable web traffic for your service. + 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 + template: | +` + cueTemplate + + // Create mock client + tclient := test.MockClient{ + MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj runtime.Object) error { + switch o := obj.(type) { + case *v1alpha2.TraitDefinition: + wd, err := UnMarshalStringToTraitDefinition(traitDefintion) + if err != nil { + return err + } + *o = *wd + } + return nil + }, + } + + temp, err := LoadTemplate(&tclient, "ingress", types.TypeTrait) + + if err != nil { + t.Error(err) + return + } + var r cue.Runtime + inst, err := r.Compile("-", temp.TemplateStr) + if err != nil { + t.Error(err) + return + } + instDest, err := r.Compile("-", cueTemplate) + if err != nil { + t.Error(err) + return + } + s1, _ := inst.Value().String() + s2, _ := instDest.Value().String() + if s1 != s2 { + t.Errorf("parsered template is not correct") + } +} + +func TestNewTemplate(t *testing.T) { + testCases := map[string]struct { + tmp string + status *v1alpha2.Status + ext *runtime.RawExtension + exp *Template + }{ + "only tmp": { + tmp: "t1", + exp: &Template{ + TemplateStr: "t1", + }, + }, + "no tmp,but has extension": { + ext: &runtime.RawExtension{Raw: []byte(`{"template":"t1"}`)}, + exp: &Template{ + TemplateStr: "t1", + }, + }, + "no tmp,but has extension without temp": { + ext: &runtime.RawExtension{Raw: []byte(`{"template":{"t1":"t2"}}`)}, + exp: &Template{ + TemplateStr: "", + }, + }, + "tmp with status": { + tmp: "t1", + status: &v1alpha2.Status{ + CustomStatus: "s1", + HealthPolicy: "h1", + }, + exp: &Template{ + TemplateStr: "t1", + CustomStatus: "s1", + Health: "h1", + }, + }, + "no tmp only status": { + status: &v1alpha2.Status{ + CustomStatus: "s1", + HealthPolicy: "h1", + }, + exp: &Template{ + CustomStatus: "s1", + Health: "h1", + }, + }, + } + for reason, casei := range testCases { + gtmp, err := NewTemplate(casei.tmp, casei.status, casei.ext) + assert.NoError(t, err, reason) + assert.Equal(t, gtmp, casei.exp, reason) + } +} diff --git a/pkg/plugins/capcenter.go b/pkg/plugins/capcenter.go index f4b739250..96b5f67bd 100644 --- a/pkg/plugins/capcenter.go +++ b/pkg/plugins/capcenter.go @@ -175,14 +175,14 @@ func ParseAndSyncCapability(data []byte, syncDir string) (types.Capability, erro if err != nil { return types.Capability{}, err } - return HandleDefinition(rd.Name, syncDir, rd.Spec.Reference.Name, rd.Annotations, rd.Spec.Extension, types.TypeWorkload, nil) + return HandleDefinition(rd.Name, syncDir, rd.Spec.Reference.Name, rd.Annotations, rd.Spec.Extension, types.TypeWorkload, nil, rd.Spec.Template) case "TraitDefinition": var td v1alpha2.TraitDefinition err = yaml.Unmarshal(data, &td) if err != nil { return types.Capability{}, err } - return HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads) + return HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Template) case "ScopeDefinition": // TODO(wonderflow): support scope definition here. } diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index 7bed52719..52e09e5ee 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -61,7 +61,7 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args var templateErrors []error for _, wd := range workloadDefs.Items { - tmp, err := HandleDefinition(wd.Name, syncDir, wd.Spec.Reference.Name, wd.Annotations, wd.Spec.Extension, types.TypeWorkload, nil) + tmp, err := HandleDefinition(wd.Name, syncDir, wd.Spec.Reference.Name, wd.Annotations, wd.Spec.Extension, types.TypeWorkload, nil, wd.Spec.Template) if err != nil { templateErrors = append(templateErrors, errors.Wrapf(err, "handle workload template `%s` failed", wd.Name)) continue @@ -93,7 +93,7 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c types.Args, s var templateErrors []error for _, td := range traitDefs.Items { - tmp, err := HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads) + tmp, err := HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Template) if err != nil { templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name)) continue @@ -134,9 +134,9 @@ func validateCapabilities(tmp types.Capability, dm discoverymapper.DiscoveryMapp } // HandleDefinition will handle definition to capability -func HandleDefinition(name, syncDir, crdName string, annotation map[string]string, extension *runtime.RawExtension, tp types.CapType, applyTo []string) (types.Capability, error) { +func HandleDefinition(name, syncDir, crdName string, annotation map[string]string, extension *runtime.RawExtension, tp types.CapType, applyTo []string, template string) (types.Capability, error) { var tmp types.Capability - tmp, err := HandleTemplate(extension, name, syncDir) + tmp, err := HandleTemplate(extension, template, name, syncDir) if err != nil { return types.Capability{}, err } @@ -162,31 +162,31 @@ func GetDescription(annotation map[string]string) string { } // HandleTemplate will handle definition template to capability -func HandleTemplate(in *runtime.RawExtension, name, syncDir string) (types.Capability, error) { - tmp, err := types.ConvertTemplateJSON2Object(in) +func HandleTemplate(in *runtime.RawExtension, specTemplate, name, syncDir string) (types.Capability, error) { + tmp, err := util.ConvertTemplateJSON2Object(in, specTemplate) if err != nil { return types.Capability{}, err } tmp.Name = name - - var cueTemplate string + // if spec.template is not empty it should has the highest priority + if specTemplate != "" { + tmp.CueTemplate = specTemplate + tmp.CueTemplateURI = "" + } if tmp.CueTemplateURI != "" { b, err := common.HTTPGet(context.Background(), tmp.CueTemplateURI) if err != nil { return types.Capability{}, err } - cueTemplate = string(b) - tmp.CueTemplate = cueTemplate - } else { - if tmp.CueTemplate == "" { - return types.Capability{}, errors.New("template not exist in definition") - } - cueTemplate = tmp.CueTemplate + tmp.CueTemplate = string(b) + } + if tmp.CueTemplate == "" { + return types.Capability{}, errors.New("template not exist in definition") } _, _ = system.CreateIfNotExist(syncDir) filePath := filepath.Join(syncDir, name+".cue") //nolint:gosec - err = ioutil.WriteFile(filePath, []byte(cueTemplate), 0644) + err = ioutil.WriteFile(filePath, []byte(tmp.CueTemplate), 0644) if err != nil { return types.Capability{}, err } @@ -245,7 +245,7 @@ func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir } if foundCapability { template, err := HandleDefinition(capabilityName, localDefinitionDir, workloadDef.Spec.Reference.Name, - workloadDef.Annotations, workloadDef.Spec.Extension, types.TypeWorkload, nil) + workloadDef.Annotations, workloadDef.Spec.Extension, types.TypeWorkload, nil, workloadDef.Spec.Template) if err == nil { return &template, nil } @@ -259,7 +259,7 @@ func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir } if foundCapability { template, err := HandleDefinition(capabilityName, localDefinitionDir, traitDef.Spec.Reference.Name, - traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil) + traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, workloadDef.Spec.Template) if err == nil { return &template, nil } diff --git a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go index eb5df8312..8469d1cce 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go @@ -2,10 +2,11 @@ package traitdefinition import ( "context" - "encoding/json" "fmt" "net/http" + "github.com/oam-dev/kubevela/pkg/oam/util" + admissionv1beta1 "k8s.io/api/admission/v1beta1" "k8s.io/klog" "sigs.k8s.io/controller-runtime/pkg/client" @@ -123,17 +124,11 @@ func ValidateDefinitionReference(_ context.Context, td v1alpha2.TraitDefinition) if len(td.Spec.Reference.Name) > 0 { return nil } - - if td.Spec.Extension == nil || len(td.Spec.Extension.Raw) < 1 { - return errors.New(failInfoDefRefOmitted) - } - - tmp := map[string]interface{}{} - if err := json.Unmarshal(td.Spec.Extension.Raw, &tmp); err != nil { + tmp, err := util.NewTemplate(td.Spec.Template, td.Spec.Status, td.Spec.Extension) + if err != nil { return errors.Wrap(err, errValidateDefRef) } - template, ok := tmp["template"] - if !ok || len(fmt.Sprint(template)) < 1 { + if len(tmp.TemplateStr) == 0 { return errors.New(failInfoDefRefOmitted) } return nil From 8af08075df2730c8a820d63d6c4f7e8a5b79237e Mon Sep 17 00:00:00 2001 From: zzxwill Date: Fri, 5 Feb 2021 11:17:48 +0800 Subject: [PATCH 33/38] Implement application creation page To fix #680 --- dashboard/config/routes.ts | 6 + dashboard/package.json | 5 +- dashboard/src/models/useTraitsModel.ts | 2 +- dashboard/src/models/useWorkloadsModel.ts | 2 +- dashboard/src/pages/Application/index.tsx | 5 +- .../src/pages/CreateApplication/index.tsx | 192 ++++++++++++++++++ dashboard/src/services/capability.ts | 23 +++ dashboard/src/services/traits.ts | 10 - dashboard/src/services/workloads.ts | 10 - 9 files changed, 231 insertions(+), 24 deletions(-) create mode 100644 dashboard/src/pages/CreateApplication/index.tsx create mode 100644 dashboard/src/services/capability.ts delete mode 100644 dashboard/src/services/traits.ts delete mode 100644 dashboard/src/services/workloads.ts diff --git a/dashboard/config/routes.ts b/dashboard/config/routes.ts index 3501e39b5..2770ca914 100644 --- a/dashboard/config/routes.ts +++ b/dashboard/config/routes.ts @@ -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', diff --git a/dashboard/package.json b/dashboard/package.json index d478d2d76..36236f18b 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -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" diff --git a/dashboard/src/models/useTraitsModel.ts b/dashboard/src/models/useTraitsModel.ts index c326aefe1..739729ba4 100644 --- a/dashboard/src/models/useTraitsModel.ts +++ b/dashboard/src/models/useTraitsModel.ts @@ -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; diff --git a/dashboard/src/models/useWorkloadsModel.ts b/dashboard/src/models/useWorkloadsModel.ts index b2b6bf03f..aafa55bea 100644 --- a/dashboard/src/models/useWorkloadsModel.ts +++ b/dashboard/src/models/useWorkloadsModel.ts @@ -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; diff --git a/dashboard/src/pages/Application/index.tsx b/dashboard/src/pages/Application/index.tsx index 9c1652d0e..43bc85383 100644 --- a/dashboard/src/pages/Application/index.tsx +++ b/dashboard/src/pages/Application/index.tsx @@ -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,8 @@ export default () => {

diff --git a/dashboard/src/pages/CreateApplication/index.tsx b/dashboard/src/pages/CreateApplication/index.tsx new file mode 100644 index 000000000..892c7ea73 --- /dev/null +++ b/dashboard/src/pages/CreateApplication/index.tsx @@ -0,0 +1,192 @@ +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) => + } onClick={() => handleMenuClick("workload_type", i.name)}> + {i.name} + + ) + + const traitMenuList = traitsList?.map((i) => + } onClick={() => handleMenuClick("trait", i.name)}> + {i.name} + + ) + + const workloadsMenu = ( + + {workloadMenuList} + + ); + + const traitsMenu = ( + + {traitMenuList} + + ); + + + // 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 ( +
+ + + Application + + + + + + + Name: + + + + + + + + + + + + + + + + Services + + + + + + + Name: + + + + + + + + + + Type: + + + + + Select + + + + + + + + Settings: + + + + + + + + + + + + + + + Traits + + + + + + + Type: + + + + e.preventDefault()}> + Select + + + + + + + + Properties: + + + + + + + + + + + + + +
+ ); +}; diff --git a/dashboard/src/services/capability.ts b/dashboard/src/services/capability.ts new file mode 100644 index 000000000..bdb9efb3e --- /dev/null +++ b/dashboard/src/services/capability.ts @@ -0,0 +1,23 @@ +import { request } from 'umi'; + + +/* + * workload type list: get /api/workloads/ + */ +export async function getWorkloads(): Promise> { + return request( '/api/workloads'); +} + + +/* + * trait list: get /api/traits/ + */ +export async function getTraits(): Promise> { + return request('/api/traits'); +} + +export async function getCapabilityOpenAPISchema( + capabilityName: string, +): Promise> { + return request(`/api/definitions/${capabilityName}`, { method: 'get' }); +} diff --git a/dashboard/src/services/traits.ts b/dashboard/src/services/traits.ts deleted file mode 100644 index 5eb008b1a..000000000 --- a/dashboard/src/services/traits.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { request } from 'umi'; - -const BASE_PATH = '/api/traits'; - -/* - * trait 列表: get /api/traits/ - */ -export async function getTraits(): Promise> { - return request(BASE_PATH); -} diff --git a/dashboard/src/services/workloads.ts b/dashboard/src/services/workloads.ts deleted file mode 100644 index 2fe0533a2..000000000 --- a/dashboard/src/services/workloads.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { request } from 'umi'; - -const BASE_PATH = '/api/workloads'; - -/* - * workload 列表: get /api/workloads/ - */ -export async function getWorkloads(): Promise> { - return request(BASE_PATH); -} From 1119d1b529ebeb44ff6786d49cfd476783827e49 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Fri, 5 Feb 2021 17:55:16 +0800 Subject: [PATCH 34/38] fix lint issue --- dashboard/src/pages/Application/index.tsx | 3 +- .../src/pages/CreateApplication/index.tsx | 134 ++++++++---------- dashboard/src/services/capability.ts | 4 +- 3 files changed, 60 insertions(+), 81 deletions(-) diff --git a/dashboard/src/pages/Application/index.tsx b/dashboard/src/pages/Application/index.tsx index 43bc85383..a2b7dda81 100644 --- a/dashboard/src/pages/Application/index.tsx +++ b/dashboard/src/pages/Application/index.tsx @@ -7,7 +7,7 @@ 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"; +import { Link as ReactLink } from 'react-router-dom'; export default () => { const { currentEnvironment } = useModel('useEnvironmentModel'); @@ -45,7 +45,6 @@ export default () => { diff --git a/dashboard/src/pages/CreateApplication/index.tsx b/dashboard/src/pages/CreateApplication/index.tsx index 892c7ea73..75aed7f99 100644 --- a/dashboard/src/pages/CreateApplication/index.tsx +++ b/dashboard/src/pages/CreateApplication/index.tsx @@ -1,63 +1,61 @@ -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 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"; +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'); + const { workloadsLoading, workloadList } = useModel('useWorkloadsModel'); // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unused-vars - const {traitsLoading, traitsList} = useModel('useTraitsModel'); + const { traitsLoading, traitsList } = useModel('useTraitsModel'); - const workloadMenuList = workloadList?.map((i) => - } onClick={() => handleMenuClick("workload_type", i.name)}> + const workloadMenuList = workloadList?.map((i) => ( + } + onClick={() => handleMenuClick('workload_type', i.name)} + > {i.name} - ) + )); - const traitMenuList = traitsList?.map((i) => - } onClick={() => handleMenuClick("trait", i.name)}> + const traitMenuList = traitsList?.map((i) => ( + } + onClick={() => handleMenuClick('trait', i.name)} + > {i.name} - ) + )); - const workloadsMenu = ( - - {workloadMenuList} - - ); - - const traitsMenu = ( - - {traitMenuList} - - ); + const workloadsMenu = {workloadMenuList}; + const traitsMenu = {traitMenuList}; // Capability parameters form render const [formData, setData] = useState({}); // schema is OpenAPI Schema JSON data - const [workloadSchema, setWorkloadSchema] = useState({}) - const [traitSchema, setTraitSchema] = useState({}) + 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 data = JSON.parse(result.data); + if (capabilityType === 'workload_type') { + setWorkloadSchema(data); + } else if (capabilityType === 'trait') { + setTraitSchema(data); } - }) + }); } const onSubmit = () => { @@ -70,64 +68,52 @@ export default (): React.ReactNode => { }; return ( -
+
- - Application - - + Application + - - Name: - + Name: - + - + - + - - Services - - + Services + - - Name: - + Name: - + - + - - Type: - + Type: - Select + Select - - Settings: - + Settings: { - + - - Traits - - + Traits + - - Type: - + Type: - e.preventDefault()}> - Select + e.preventDefault()}> + Select - - Properties: - + Properties: { - + - + - +
); diff --git a/dashboard/src/services/capability.ts b/dashboard/src/services/capability.ts index bdb9efb3e..f8d4a1ee4 100644 --- a/dashboard/src/services/capability.ts +++ b/dashboard/src/services/capability.ts @@ -1,14 +1,12 @@ import { request } from 'umi'; - /* * workload type list: get /api/workloads/ */ export async function getWorkloads(): Promise> { - return request( '/api/workloads'); + return request('/api/workloads'); } - /* * trait list: get /api/traits/ */ From b564e0ef263c1b3c8b46dfb7022ec055bf22ac75 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 5 Feb 2021 12:08:19 -0800 Subject: [PATCH 35/38] add more code to complete the rollout plan (#1024) --- .../v1alpha1/rollout_plan_types.go | 13 +- .../v1alpha1/rollout_state.go | 64 ++++++++-- .../core.oam.dev_applicationdeployments.yaml | 6 +- .../crds/standard.oam.dev_rollouttraits.yaml | 6 +- .../core.oam.dev_applicationdeployments.yaml | 6 +- .../crds/standard.oam.dev_rollouttraits.yaml | 6 +- .../common/rollout/rollout_plan_controller.go | 79 ++++++++---- .../rollout/workloads/cloneset_controller.go | 116 +++++++++++++----- .../common/rollout/workloads/common.go | 39 ++++++ .../common/rollout/workloads/controller.go | 4 + pkg/webhook/common/rollout/rollout_plan.go | 72 +++++++++-- .../applicationdeployment/validation.go | 5 +- 12 files changed, 322 insertions(+), 94 deletions(-) create mode 100644 pkg/controller/common/rollout/workloads/common.go diff --git a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go index bd212c52d..d30afd82f 100644 --- a/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go +++ b/apis/standard.oam.dev/v1alpha1/rollout_plan_types.go @@ -60,14 +60,13 @@ const ( // BatchInRollingState still rolling the batch, the batch rolling is not completed yet BatchInRollingState BatchRollingState = "batchInRolling" // BatchVerifyingState verifying if the application is ready to roll. - // This happens when it's either manual or automatic with analysis 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" - // BatchFinalizeState indicates that all the pods in the are available, we can move on to the next batch - BatchFinalizeState BatchRollingState = "batchFinalize" ) // RolloutPlan fines the details of the rollout plan @@ -88,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"` @@ -103,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"` @@ -117,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"` diff --git a/apis/standard.oam.dev/v1alpha1/rollout_state.go b/apis/standard.oam.dev/v1alpha1/rollout_state.go index 197916e59..9f8f5ad87 100644 --- a/apis/standard.oam.dev/v1alpha1/rollout_state.go +++ b/apis/standard.oam.dev/v1alpha1/rollout_state.go @@ -49,7 +49,7 @@ const ( // this events comes after we have examine the pod readiness check and traffic shifting if needed OneBatchAvailableEvent RolloutEvent = "OneBatchAvailable" - // BatchRolloutApprovedEvent indicates that we are waiting for the approval of the + // BatchRolloutApprovedEvent indicates that we got the approval manually BatchRolloutApprovedEvent RolloutEvent = "BatchRolloutApprovedEvent" // BatchRolloutFailedEvent indicates that we are waiting for the approval of the @@ -59,7 +59,7 @@ const ( WorkloadModifiedEvent RolloutEvent = "WorkloadModifiedEvent" ) -// These are valid conditions of pod. +// 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" @@ -69,6 +69,18 @@ const ( 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 @@ -104,7 +116,22 @@ func (r *RolloutStatus) getRolloutConditionType() runtimev1alpha1.ConditionType return RolloutInitialized case RollingInBatchesState: - return RolloutInProgress + 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 @@ -148,6 +175,7 @@ func (r *RolloutStatus) StateTransition(event RolloutEvent) { case VerifyingState: if event == RollingSpecVerifiedEvent { r.RollingState = InitializingState + r.SetConditions(NewPositiveCondition(r.getRolloutConditionType())) return } panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) @@ -155,6 +183,7 @@ func (r *RolloutStatus) StateTransition(event RolloutEvent) { case InitializingState: if event == RollingInitializedEvent { r.RollingState = RollingInBatchesState + r.SetConditions(NewPositiveCondition(r.getRolloutConditionType())) return } panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) @@ -166,6 +195,7 @@ func (r *RolloutStatus) StateTransition(event RolloutEvent) { case FinalisingState: if event == RollingFinalizedEvent { r.RollingState = RolloutSucceedState + r.SetConditions(NewPositiveCondition(r.getRolloutConditionType())) return } panic(fmt.Errorf(invalidRollingStateTransition, rollingState, event)) @@ -173,6 +203,7 @@ func (r *RolloutStatus) StateTransition(event RolloutEvent) { case RolloutSucceedState: if event == WorkloadModifiedEvent { r.RollingState = VerifyingState + r.SetConditions(NewPositiveCondition(r.getRolloutConditionType())) return } if event == RollingFinalizedEvent { @@ -184,6 +215,7 @@ func (r *RolloutStatus) StateTransition(event RolloutEvent) { case RolloutFailedState: if event == WorkloadModifiedEvent { r.RollingState = VerifyingState + r.SetConditions(NewPositiveCondition(r.getRolloutConditionType())) return } if event == RollingFailedEvent { @@ -203,6 +235,7 @@ func (r *RolloutStatus) batchStateTransition(event RolloutEvent) { if event == BatchRolloutFailedEvent { r.BatchRollingState = BatchRolloutFailedState r.RollingState = RolloutFailedState + r.SetConditions(NewNegativeCondition(r.getRolloutConditionType(), "failed")) return } switch batchRollingState { @@ -220,13 +253,15 @@ func (r *RolloutStatus) batchStateTransition(event RolloutEvent) { } 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 = BatchReadyState + r.BatchRollingState = BatchFinalizingState + r.SetConditions(NewPositiveCondition(r.getRolloutConditionType())) return } if event == BatchRolloutVerifyingEvent { @@ -235,21 +270,24 @@ func (r *RolloutStatus) batchStateTransition(event RolloutEvent) { } panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event)) - case BatchReadyState: - if event == BatchRolloutApprovedEvent { - r.BatchRollingState = BatchFinalizeState - return - } - panic(fmt.Errorf(invalidBatchRollingStateTransition, batchRollingState, event)) - - case BatchFinalizeState: + case BatchFinalizingState: if event == FinishedOneBatchEvent { - r.BatchRollingState = BatchInitializingState + 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)) diff --git a/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml b/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml index 436d9dcf3..600221c5c 100644 --- a/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml +++ b/charts/vela-core/crds/core.oam.dev_applicationdeployments.yaml @@ -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: diff --git a/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml b/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml index b768135ef..96b9e8f03 100644 --- a/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml +++ b/charts/vela-core/crds/standard.oam.dev_rollouttraits.yaml @@ -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: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml index dc7edb12c..8e77ed672 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationdeployments.yaml @@ -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: diff --git a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml index 92da354d9..64148c813 100644 --- a/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml +++ b/legacy/charts/vela-core-legacy/crds/standard.oam.dev_rollouttraits.yaml @@ -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: diff --git a/pkg/controller/common/rollout/rollout_plan_controller.go b/pkg/controller/common/rollout/rollout_plan_controller.go index 125047695..3df2613c8 100644 --- a/pkg/controller/common/rollout/rollout_plan_controller.go +++ b/pkg/controller/common/rollout/rollout_plan_controller.go @@ -14,6 +14,7 @@ import ( "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" ) @@ -79,7 +80,7 @@ func (r *Controller) Reconcile(ctx context.Context) (res reconcile.Result, statu } }() - wc, err := r.GetWorkloadController() + workloadController, err := r.GetWorkloadController() if err != nil { r.rolloutStatus.RolloutFailed(err.Error()) r.recorder.Event(r.parentController, event.Warning("Unsupported workload", err)) @@ -88,18 +89,18 @@ func (r *Controller) Reconcile(ctx context.Context) (res reconcile.Result, statu switch r.rolloutStatus.RollingState { case v1alpha1.VerifyingState: - status = *wc.Verify(ctx) + status = *workloadController.Verify(ctx) case v1alpha1.InitializingState: // TODO: call the pre-rollout webhooks - status = *wc.Initialize(ctx) + status = *workloadController.Initialize(ctx) case v1alpha1.RollingInBatchesState: - status = r.reconcileBatchInRolling(ctx, wc) + status = r.reconcileBatchInRolling(ctx, workloadController) case v1alpha1.FinalisingState: // TODO: call the post-rollout webhooks - status = *wc.Finalize(ctx) + status = *workloadController.Finalize(ctx) case v1alpha1.RolloutSucceedState: // Nothing to do @@ -115,7 +116,7 @@ func (r *Controller) Reconcile(ctx context.Context) (res reconcile.Result, statu } // reconcile logic when we are in the middle of rollout -func (r *Controller) reconcileBatchInRolling(ctx context.Context, wc workloads.WorkloadController) ( +func (r *Controller) reconcileBatchInRolling(ctx context.Context, workloadController workloads.WorkloadController) ( status v1alpha1.RolloutStatus) { if r.rolloutSpec.Paused { @@ -125,7 +126,7 @@ func (r *Controller) reconcileBatchInRolling(ctx context.Context, wc workloads.W } // makes sure that the current batch and replica count in the status are validate - replicas, err := wc.Size(ctx) + replicas, err := workloadController.Size(ctx) if err != nil { r.rolloutStatus.RolloutRetry(err.Error()) return r.rolloutStatus @@ -138,21 +139,22 @@ func (r *Controller) reconcileBatchInRolling(ctx context.Context, wc workloads.W case v1alpha1.BatchInRollingState: // still rolling the batch, the batch rolling is not completed yet - status = *wc.RolloutOneBatchPods(ctx) + status = *workloadController.RolloutOneBatchPods(ctx) case v1alpha1.BatchVerifyingState: - // verifying if the application is ready to roll. - // This happens when it's either manual or automatic with analysis - // TODO: call the post-batch webhooks if there are any + // 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 its state is ready - // need to check if they meet the availability requirements in the rollout spec - status = *wc.CheckOneBatchPods(ctx) - - case v1alpha1.BatchFinalizeState: - // indicates that all the pods in the are available, we can move on to the next batch - r.rolloutStatus.CurrentBatch++ + // 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)) @@ -161,6 +163,36 @@ func (r *Controller) reconcileBatchInRolling(ctx context.Context, wc workloads.W 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 @@ -187,9 +219,14 @@ func (r *Controller) validateRollingBatchStatus(totalSize int) bool { return false } // calculate the upper bound with the current batch - batchSize, _ := intstr.GetValueFromIntOrPercent(&spec.RolloutBatches[currentBatch].Replicas, - totalSize, true) - podCount += batchSize + 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", diff --git a/pkg/controller/common/rollout/workloads/cloneset_controller.go b/pkg/controller/common/rollout/workloads/cloneset_controller.go index 831763430..213870deb 100644 --- a/pkg/controller/common/rollout/workloads/cloneset_controller.go +++ b/pkg/controller/common/rollout/workloads/cloneset_controller.go @@ -13,6 +13,7 @@ import ( "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" ) @@ -58,40 +59,50 @@ func (c *CloneSetController) Size(ctx context.Context) (int32, error) { // Verify verifies that the target rollout resource is consistent with the rollout spec func (c *CloneSetController) Verify(ctx context.Context) *v1alpha1.RolloutStatus { - if c.fetchCloneSet(ctx) != nil { + 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 { - err := fmt.Errorf("there is no difference between the source and target, hash = %s", targetHash) - klog.Error(err) - c.rolloutStatus.RolloutFailed(err.Error()) - c.recorder.Event(c.parentController, event.Warning("VerifyFailed", err)) + 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 - // 1. the rollout batch is either automatic or zero - if c.rolloutSpec.BatchPartition != nil && *c.rolloutSpec.BatchPartition != 0 { - err := fmt.Errorf("the rollout plan has to start from zero, partition= %d", *c.rolloutSpec.BatchPartition) - klog.Error(err) - c.rolloutStatus.RolloutFailed(err.Error()) - c.recorder.Event(c.parentController, event.Warning("VerifyFailed", err)) + 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 } - // 2. the number of old version in the Cloneset equals to the total number - totalReplicas, _ := c.Size(ctx) + + // 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) { - err := fmt.Errorf("the cloneset was still in the middle of updating, number of old pods= %d", oldVersionPod) - klog.Error(err) - c.rolloutStatus.RolloutFailed(err.Error()) - c.recorder.Event(c.parentController, event.Warning("VerifyFailed", err)) + 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 } @@ -126,12 +137,14 @@ func (c *CloneSetController) RolloutOneBatchPods(ctx context.Context) *v1alpha1. 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 update the Cloneset", err)) + c.recorder.Event(c.parentController, event.Warning("Failed to patch update the Cloneset", err)) c.rolloutStatus.RolloutRetry(err.Error()) - c.rolloutStatus.StateTransition(v1alpha1.BatchRolloutContinueEvent) 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 @@ -148,37 +161,64 @@ func (c *CloneSetController) CheckOneBatchPods(ctx context.Context) *v1alpha1.Ro if currentBatch.MaxUnavailable != nil { unavail, _ = intstr.GetValueFromIntOrPercent(currentBatch.MaxUnavailable, int(cloneSetSize), true) } - klog.InfoS("checking the rolling out progress", "new pod count target", newPodTarget, - "new ready pod count", readyPodCount, "max unavailable pod allowed", unavail) + 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 } -// Finalize makes sure the Cloneset is all upgraded and +// 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 } - // mark the rollout finalized - c.recorder.Event(c.parentController, event.Normal("Finalized", "Rollout resource are finalized")) + c.rolloutStatus.StateTransition(v1alpha1.RollingFinalizedEvent) + return c.rolloutStatus } -// The functions below are helper functions +/* -------------------- +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 { - klog.CalculateMaxSize() if !apierrors.IsNotFound(err) { c.recorder.Event(c.parentController, event.Warning("Failed to get the Cloneset", err)) } @@ -190,16 +230,24 @@ func (c *CloneSetController) fetchCloneSet(ctx context.Context) error { } func (c *CloneSetController) calculateNewPodTarget(cloneSetSize int) int { - currentBatch := c.rolloutStatus.CurrentBatch + currentBatch := int(c.rolloutStatus.CurrentBatch) newPodTarget := 0 - for i, r := range c.rolloutSpec.RolloutBatches { - batchSize, _ := intstr.GetValueFromIntOrPercent(&r.Replicas, cloneSetSize, true) - if i <= int(currentBatch) { - newPodTarget += batchSize - } else { - break + 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) } - klog.InfoS("Calculated the number of new version pod", "new version pod target", newPodTarget) return newPodTarget } diff --git a/pkg/controller/common/rollout/workloads/common.go b/pkg/controller/common/rollout/workloads/common.go new file mode 100644 index 000000000..0d66b4c84 --- /dev/null +++ b/pkg/controller/common/rollout/workloads/common.go @@ -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 +} diff --git a/pkg/controller/common/rollout/workloads/controller.go b/pkg/controller/common/rollout/workloads/controller.go index e49558f8d..99f9da3fb 100644 --- a/pkg/controller/common/rollout/workloads/controller.go +++ b/pkg/controller/common/rollout/workloads/controller.go @@ -30,6 +30,10 @@ type WorkloadController interface { // 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 diff --git a/pkg/webhook/common/rollout/rollout_plan.go b/pkg/webhook/common/rollout/rollout_plan.go index bda2d23aa..8fe3620f3 100644 --- a/pkg/webhook/common/rollout/rollout_plan.go +++ b/pkg/webhook/common/rollout/rollout_plan.go @@ -1,27 +1,85 @@ package rollout import ( + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation/field" "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" ) // DefaultRolloutPlan set the default values for a rollout plan +// This is called by the mutation webhooks and before the validators func DefaultRolloutPlan(rollout *v1alpha1.RolloutPlan) { - + if rollout.TargetSize != nil && rollout.NumBatches != nil && rollout.RolloutBatches == nil { + // create the rollout batch based on the total size and num batches if it's not set + // leave it for the validator to reject if they are both set + numBatches := int(*rollout.NumBatches) + totalSize := int(*rollout.TargetSize) + // create the batch array + rollout.RolloutBatches = make([]v1alpha1.RolloutBatch, int(*rollout.NumBatches)) + avg := intstr.FromInt(totalSize / numBatches) + total := 0 + for i := 0; i < numBatches-1; i++ { + rollout.RolloutBatches[i].Replicas = avg + total += avg.IntValue() + } + // fill out the last batch + rollout.RolloutBatches[numBatches-1].Replicas = intstr.FromInt(totalSize - total) + } } // ValidateCreate validate the rollout plan -func ValidateCreate(rollout *v1alpha1.RolloutPlan) field.ErrorList { +func ValidateCreate(rollout *v1alpha1.RolloutPlan, rootPath *field.Path) field.ErrorList { var allErrs field.ErrorList - // 1. The total number of num in the batches match the current target resource pod size - // 2. The TargetSize and NumBatches are mutually exclusive to RolloutBatches + // The total number of num in the batches match the current target resource pod size + + // The TargetSize and NumBatches are mutually exclusive to RolloutBatches + if rollout.NumBatches != nil && rollout.RolloutBatches != nil { + allErrs = append(allErrs, field.Duplicate(rootPath.Child("numBatches"), rollout.NumBatches)) + } + + // validate the webhooks + allErrs = append(allErrs, validateWebhook(rollout, rootPath)...) return allErrs } +func validateWebhook(rollout *v1alpha1.RolloutPlan, rootPath *field.Path) (allErrs field.ErrorList) { + // The webhooks in the rollout plan can only be initialize or finalize webhooks + if rollout.RolloutWebhooks != nil { + webhookPath := rootPath.Child("rolloutWebhooks") + for i, rw := range rollout.RolloutWebhooks { + if rw.Type != v1alpha1.InitializeRolloutHook && rw.Type != v1alpha1.FinalizeRolloutHook { + allErrs = append(allErrs, field.Invalid(webhookPath.Index(i), + rw.Type, "the rollout webhook type can only be initialize or finalize webhook")) + } + // TODO: check the URL/name uniqueness? + } + } + + // The webhooks in the rollout batch can only be pre or post batch types + if rollout.RolloutBatches != nil { + batchesPath := rootPath.Child("rolloutBatches") + for i, rb := range rollout.RolloutBatches { + rolloutBatchPath := batchesPath.Index(i) + for j, brw := range rb.BatchRolloutWebhooks { + if brw.Type != v1alpha1.PostBatchRolloutHook && brw.Type != v1alpha1.PreBatchRolloutHook { + allErrs = append(allErrs, field.Invalid(rolloutBatchPath.Child("batchRolloutWebhooks").Index(j), + brw.Type, "the batch webhook type can only be pre or post batch webhook")) + } + // TODO: check the URL/name uniqueness? + } + } + } + return allErrs +} + // ValidateUpdate validate if one can change the rollout plan from the previous psec -func ValidateUpdate(new *v1alpha1.RolloutPlan, prev *v1alpha1.RolloutPlan) field.ErrorList { - // Only a few fields can change after a rollout plan is set - return nil +func ValidateUpdate(new *v1alpha1.RolloutPlan, prev *v1alpha1.RolloutPlan, rootPath *field.Path) field.ErrorList { + // makes sure the new rollout alone is valid + allErrs := ValidateCreate(new, rootPath) + + // TODO: Enforce that only a few fields can change after a rollout plan is set + + return allErrs } diff --git a/pkg/webhook/core.oam.dev/v1alpha2/applicationdeployment/validation.go b/pkg/webhook/core.oam.dev/v1alpha2/applicationdeployment/validation.go index 14079673d..2ca9f7ff1 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/applicationdeployment/validation.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/applicationdeployment/validation.go @@ -53,7 +53,7 @@ func (h *ValidatingHandler) ValidateCreate(appDeploy *v1alpha2.ApplicationDeploy fldPath.Child("componentList"))...) // validate the rollout plan spec - allErrs = append(allErrs, rollout.ValidateCreate(&appDeploy.Spec.RolloutPlan)...) + allErrs = append(allErrs, rollout.ValidateCreate(&appDeploy.Spec.RolloutPlan, fldPath.Child("rolloutPlan"))...) return allErrs } @@ -113,5 +113,6 @@ func (h *ValidatingHandler) ValidateUpdate(new, old *v1alpha2.ApplicationDeploym if len(errList) > 0 { return errList } - return rollout.ValidateUpdate(&new.Spec.RolloutPlan, &old.Spec.RolloutPlan) + fldPath := field.NewPath("spec").Child("rolloutPlan") + return rollout.ValidateUpdate(&new.Spec.RolloutPlan, &old.Spec.RolloutPlan, fldPath) } From 47ab481eee01b0081f82e9203e3ffd13960d1d10 Mon Sep 17 00:00:00 2001 From: roy wang Date: Thu, 4 Feb 2021 20:20:06 +0900 Subject: [PATCH 36/38] implement ApplyOnceOnlyForce add unit test Signed-off-by: roy wang --- apis/core.oam.dev/v1alpha2/core_types.go | 7 + ...ore.oam.dev_applicationconfigurations.yaml | 4 + .../templates/kubevela-controller.yaml | 1 + charts/vela-core/values.yaml | 2 + cmd/core/main.go | 26 +- design/vela-core/apply-once-only.md | 25 ++ ...ore.oam.dev_applicationconfigurations.yaml | 4 + .../core.oam.dev/oamruntime_controller.go | 22 +- .../applicationconfiguration.go | 146 ++++++--- .../apply_once_only_test.go | 281 +++++++++++++++--- 10 files changed, 426 insertions(+), 92 deletions(-) diff --git a/apis/core.oam.dev/v1alpha2/core_types.go b/apis/core.oam.dev/v1alpha2/core_types.go index 48433a600..ef560c535 100644 --- a/apis/core.oam.dev/v1alpha2/core_types.go +++ b/apis/core.oam.dev/v1alpha2/core_types.go @@ -439,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"` diff --git a/charts/vela-core/crds/core.oam.dev_applicationconfigurations.yaml b/charts/vela-core/crds/core.oam.dev_applicationconfigurations.yaml index 980e3a7cd..ca5d31a9e 100644 --- a/charts/vela-core/crds/core.oam.dev_applicationconfigurations.yaml +++ b/charts/vela-core/crds/core.oam.dev_applicationconfigurations.yaml @@ -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: diff --git a/charts/vela-core/templates/kubevela-controller.yaml b/charts/vela-core/templates/kubevela-controller.yaml index ef0f34296..cf5378c7d 100644 --- a/charts/vela-core/templates/kubevela-controller.yaml +++ b/charts/vela-core/templates/kubevela-controller.yaml @@ -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 }} diff --git a/charts/vela-core/values.yaml b/charts/vela-core/values.yaml index b5ebdb3be..a055f1a0f 100644 --- a/charts/vela-core/values.yaml +++ b/charts/vela-core/values.yaml @@ -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: "" diff --git a/cmd/core/main.go b/cmd/core/main.go index 6de1e4e8a..8df32937a 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -9,6 +9,7 @@ import ( "os/signal" "path/filepath" "strconv" + "strings" "syscall" "time" @@ -79,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") @@ -95,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.") @@ -164,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) @@ -203,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) diff --git a/design/vela-core/apply-once-only.md b/design/vela-core/apply-once-only.md index 02ba38b87..36f3d55b6 100644 --- a/design/vela-core/apply-once-only.md +++ b/design/vela-core/apply-once-only.md @@ -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 +``` + + diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationconfigurations.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationconfigurations.yaml index 147b82953..5fe8abbde 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationconfigurations.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationconfigurations.yaml @@ -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: diff --git a/pkg/controller/core.oam.dev/oamruntime_controller.go b/pkg/controller/core.oam.dev/oamruntime_controller.go index b5b81f275..7fd960ab3 100644 --- a/pkg/controller/core.oam.dev/oamruntime_controller.go +++ b/pkg/controller/core.oam.dev/oamruntime_controller.go @@ -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 diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go index 14a9e9e2f..8b9c63d28 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go @@ -102,22 +102,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 +174,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 +200,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 { @@ -296,10 +297,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco 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)) @@ -349,6 +347,7 @@ func (r *OAMApplicationReconciler) updateStatus(ctx context.Context, ac, acPatch 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 +575,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{} } } diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go index e1ce7cbea..05d05147f 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply_once_only_test.go @@ -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()) + }) + }) }) From abe01d2addd83cf2d77fd4ee3308dece91f391cd Mon Sep 17 00:00:00 2001 From: Dylan Date: Sun, 7 Feb 2021 15:08:22 +0800 Subject: [PATCH 37/38] fix: update CR's status with retry.RetryOnConflict (#1015) --- .../application/application_controller.go | 20 +++++++++++-- .../v1alpha2/application/apply.go | 18 ++++++------ .../applicationconfiguration.go | 28 ++++++++++++++----- .../applicationconfiguration/component.go | 15 +++++++++- .../healthscope/healthscope_controller.go | 16 ++++++++++- .../containerizedworkload_controller.go | 16 ++++++++++- .../metrics/metricstrait_controller.go | 16 ++++++++++- .../podspecworkload_controller.go | 16 ++++++++++- .../v1alpha1/routes/route_controller.go | 21 +++++++++++--- 9 files changed, 138 insertions(+), 28 deletions(-) diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index e088d8636..3792118fc 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -27,6 +27,8 @@ import ( "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" @@ -77,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 := &appHandler{r.Client, app, applog} + handler := &appHandler{r, app, applog} app.Status.Conditions = []v1alpha1.Condition{} @@ -149,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 @@ -160,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()) diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go index f79f81302..cce108aa9 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go @@ -40,13 +40,13 @@ func readyCondition(tpy string) runtimev1alpha1.Condition { } type appHandler struct { - c client.Client + r *Reconciler app *v1alpha2.Application l logr.Logger } func (ret *appHandler) Err(err error) (ctrl.Result, error) { - nerr := ret.c.Status().Update(context.Background(), ret.app) + nerr := ret.r.UpdateStatus(context.Background(), ret.app) if err == nil && nerr == nil { return ctrl.Result{}, nil } @@ -92,7 +92,7 @@ func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.App } } - workloadHealth, err := wl.EvalHealth(pCtx, ret.c, ret.app.Namespace) + 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) } @@ -101,7 +101,7 @@ func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.App status.Healthy = false healthy = false } - status.Message, err = wl.EvalStatus(pCtx, ret.c, ret.app.Namespace) + 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) } @@ -111,7 +111,7 @@ func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.App Type: trait.Name, Healthy: true, } - traitHealth, err := trait.EvalHealth(pCtx, ret.c, ret.app.Namespace) + 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) } @@ -120,7 +120,7 @@ func (ret *appHandler) statusAggregate(appfile *appfile.Appfile) ([]v1alpha2.App traitStatus.Healthy = false healthy = false } - traitStatus.Message, err = trait.EvalStatus(pCtx, ret.c, ret.app.Namespace) + 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) } @@ -167,12 +167,12 @@ func CreateOrUpdateAppConfig(ctx context.Context, client client.Client, appConfi // Sync perform synchronization operations 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 } @@ -191,7 +191,7 @@ func (ret *appHandler) Sync(ctx context.Context, ac *v1alpha2.ApplicationConfigu } // 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 } } diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go index 8b9c63d28..3b2d588eb 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go @@ -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" @@ -245,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) } @@ -260,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 { @@ -280,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)) } @@ -292,7 +294,7 @@ 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)))) @@ -302,7 +304,7 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (result reco 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)))) @@ -322,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")) @@ -342,6 +344,18 @@ 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) diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go index a9a086def..a05a49453 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" @@ -191,7 +192,7 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim return nil, false } - err = c.Client.Status().Update(context.Background(), comp) + err = c.UpdateStatus(context.Background(), comp) if err != nil { c.Logger.Info(fmt.Sprintf("update component status latestRevision %s err %v", revisionName, err), "componentName", mt.GetName()) return nil, false @@ -277,6 +278,18 @@ func (c *ComponentHandler) cleanupControllerRevision(curComp *v1alpha2.Component return nil } +// UpdateStatus updates v1alpha2.Component's Status with retry.RetryOnConflict +func (c *ComponentHandler) UpdateStatus(ctx context.Context, comp *v1alpha2.Component, opts ...client.UpdateOption) error { + status := comp.DeepCopy().Status + return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if err = c.Client.Get(ctx, types.NamespacedName{Namespace: comp.Namespace, Name: comp.Name}, comp); err != nil { + return + } + comp.Status = status + return c.Client.Status().Update(ctx, comp, opts...) + }) +} + // ConstructRevisionName will generate revisionName from componentName // will be -v, for example: comp-v1 func ConstructRevisionName(componentName string, revision int64) string { diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go index a63efe935..a6615db6a 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go @@ -23,6 +23,8 @@ import ( "time" "github.com/pkg/errors" + "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" @@ -172,7 +174,7 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) hs.Status.ScopeHealthCondition = scopeCondition hs.Status.WorkloadHealthConditions = wlConditions - return reconcile.Result{RequeueAfter: interval - elapsed}, errors.Wrap(r.client.Status().Update(ctx, hs), errUpdateHealthScopeStatus) + return reconcile.Result{RequeueAfter: interval - elapsed}, errors.Wrap(r.UpdateStatus(ctx, hs), errUpdateHealthScopeStatus) } // GetScopeHealthStatus get the status of the healthscope based on workload resources. @@ -257,3 +259,15 @@ func (r *Reconciler) GetScopeHealthStatus(ctx context.Context, healthScope *v1al return scopeCondition, workloadHealthConditions } + +// UpdateStatus updates v1alpha2.HealthScope's Status with retry.RetryOnConflict +func (r *Reconciler) UpdateStatus(ctx context.Context, hs *v1alpha2.HealthScope, opts ...client.UpdateOption) error { + status := hs.DeepCopy().Status + return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if err = r.client.Get(ctx, types.NamespacedName{Namespace: hs.Namespace, Name: hs.Name}, hs); err != nil { + return + } + hs.Status = status + return r.client.Status().Update(ctx, hs, opts...) + }) +} diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go index e83a484f1..0b382b283 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go @@ -29,6 +29,8 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "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/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -172,12 +174,24 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { }, ) - if err := r.Status().Update(ctx, &workload); err != nil { + if err := r.UpdateStatus(ctx, &workload); err != nil { return util.ReconcileWaitResult, err } return ctrl.Result{}, util.PatchCondition(ctx, r, &workload, cpv1alpha1.ReconcileSuccess()) } +// UpdateStatus updates v1alpha2.ContainerizedWorkload's Status with retry.RetryOnConflict +func (r *Reconciler) UpdateStatus(ctx context.Context, workload *v1alpha2.ContainerizedWorkload, opts ...client.UpdateOption) error { + status := workload.DeepCopy().Status + return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if err = r.Get(ctx, types.NamespacedName{Namespace: workload.Namespace, Name: workload.Name}, workload); err != nil { + return + } + workload.Status = status + return r.Status().Update(ctx, workload, opts...) + }) +} + // SetupWithManager setups up k8s controller. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { src := &v1alpha2.ContainerizedWorkload{} diff --git a/pkg/controller/standard.oam.dev/v1alpha1/metrics/metricstrait_controller.go b/pkg/controller/standard.oam.dev/v1alpha1/metrics/metricstrait_controller.go index c7e20d183..2779a3e1b 100644 --- a/pkg/controller/standard.oam.dev/v1alpha1/metrics/metricstrait_controller.go +++ b/pkg/controller/standard.oam.dev/v1alpha1/metrics/metricstrait_controller.go @@ -31,7 +31,9 @@ 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/apimachinery/pkg/util/intstr" + "k8s.io/client-go/util/retry" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -163,7 +165,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { r.gcOrphanServiceMonitor(ctx, mLog, &metricsTrait) (&metricsTrait).SetConditions(cpv1alpha1.ReconcileSuccess()) - return ctrl.Result{}, errors.Wrap(r.Status().Update(ctx, &metricsTrait), common.ErrUpdateStatus) + return ctrl.Result{}, errors.Wrap(r.UpdateStatus(ctx, &metricsTrait), common.ErrUpdateStatus) } // fetch the label of the service that is associated with the workload @@ -336,6 +338,18 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } +// UpdateStatus updates v1alpha1.MetricsTrait's Status with retry.RetryOnConflict +func (r *Reconciler) UpdateStatus(ctx context.Context, mt *v1alpha1.MetricsTrait, opts ...client.UpdateOption) error { + status := mt.DeepCopy().Status + return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if err = r.Get(ctx, types.NamespacedName{Namespace: mt.Namespace, Name: mt.Name}, mt); err != nil { + return + } + mt.Status = status + return r.Status().Update(ctx, mt, opts...) + }) +} + // Setup adds a controller that reconciles MetricsTrait. func Setup(mgr ctrl.Manager) error { dm, err := discoverymapper.New(mgr.GetConfig()) diff --git a/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload/podspecworkload_controller.go b/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload/podspecworkload_controller.go index 5a0b83b0e..6d71459e5 100644 --- a/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload/podspecworkload_controller.go +++ b/pkg/controller/standard.oam.dev/v1alpha1/podspecworkload/podspecworkload_controller.go @@ -30,7 +30,9 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -151,7 +153,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { }) } - if err := r.Status().Update(ctx, &workload); err != nil { + if err := r.UpdateStatus(ctx, &workload); err != nil { return util.ReconcileWaitResult, err } return ctrl.Result{}, util.PatchCondition(ctx, r, &workload, cpv1alpha1.ReconcileSuccess()) @@ -277,6 +279,18 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } +// UpdateStatus updates *v1alpha1.PodSpecWorkload's Status with retry.RetryOnConflict +func (r *Reconciler) UpdateStatus(ctx context.Context, workload *v1alpha1.PodSpecWorkload, opts ...client.UpdateOption) error { + status := workload.DeepCopy().Status + return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if err = r.Get(ctx, types.NamespacedName{Namespace: workload.Namespace, Name: workload.Name}, workload); err != nil { + return + } + workload.Status = status + return r.Status().Update(ctx, workload, opts...) + }) +} + // Setup adds a controller that reconciles PodSpecWorkload. func Setup(mgr ctrl.Manager) error { reconciler := Reconciler{ diff --git a/pkg/controller/standard.oam.dev/v1alpha1/routes/route_controller.go b/pkg/controller/standard.oam.dev/v1alpha1/routes/route_controller.go index e91678501..ec343d6e9 100644 --- a/pkg/controller/standard.oam.dev/v1alpha1/routes/route_controller.go +++ b/pkg/controller/standard.oam.dev/v1alpha1/routes/route_controller.go @@ -23,11 +23,10 @@ import ( "reflect" "time" - "github.com/oam-dev/kubevela/pkg/controller/utils" - standardv1alpha1 "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/standard.oam.dev/v1alpha1/routes/ingress" + "github.com/oam-dev/kubevela/pkg/controller/utils" runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" "github.com/crossplane/crossplane-runtime/pkg/event" @@ -39,7 +38,9 @@ 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/apimachinery/pkg/util/intstr" + "k8s.io/client-go/util/retry" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -146,9 +147,9 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { routeTrait.Status.Status, conditions = routeIngress.CheckStatus(&routeTrait) routeTrait.Status.Conditions = conditions if routeTrait.Status.Status != ingress.StatusReady { - return ctrl.Result{RequeueAfter: requeueNotReady}, r.Status().Update(ctx, &routeTrait) + return ctrl.Result{RequeueAfter: requeueNotReady}, r.UpdateStatus(ctx, &routeTrait) } - err = r.Status().Update(ctx, &routeTrait) + err = r.UpdateStatus(ctx, &routeTrait) if err != nil { return oamutil.ReconcileWaitResult, err } @@ -247,6 +248,18 @@ func (r *Reconciler) fillBackendByCreatedService(ctx context.Context, mLog logr. }, nil } +// UpdateStatus updates standardv1alpha1.Route's Status with retry.RetryOnConflict +func (r *Reconciler) UpdateStatus(ctx context.Context, route *standardv1alpha1.Route, opts ...client.UpdateOption) error { + status := route.DeepCopy().Status + return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { + if err = r.Get(ctx, types.NamespacedName{Namespace: route.Namespace, Name: route.Name}, route); err != nil { + return + } + route.Status = status + return r.Status().Update(ctx, route, opts...) + }) +} + // DiscoverPortsLabel assume the workload or it's childResource will always having spec.template as PodTemplate if discoverable func DiscoverPortsLabel(ctx context.Context, workload *unstructured.Unstructured, r client.Reader, dm discoverymapper.DiscoveryMapper, childResources []*unstructured.Unstructured) ([]intstr.IntOrString, map[string]string, error) { From 58bd00f137e53dcb97f59a24966f2c2bfd61afed Mon Sep 17 00:00:00 2001 From: wangkai1994 Date: Sun, 7 Feb 2021 15:11:18 +0800 Subject: [PATCH 38/38] fix cap error message, --- pkg/serverlib/capability.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/serverlib/capability.go b/pkg/serverlib/capability.go index 476660767..cff295b75 100644 --- a/pkg/serverlib/capability.go +++ b/pkg/serverlib/capability.go @@ -191,7 +191,7 @@ func GetCapabilityFromCenter(repoName, addonName string) (types.Capability, erro return t, nil } } - return types.Capability{}, fmt.Errorf("%s/%s not exist, try vela cap:center:sync %s to sync from remote", repoName, addonName, repoName) + return types.Capability{}, fmt.Errorf("%s/%s not exist, try 'vela cap center sync %s' to sync from remote", repoName, addonName, repoName) } // ListCapabilityCenters will list all capabilities from center