mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Feat: Semantic versioning support for Definitions (#6648)
CodeQL / Analyze (go) (push) Failing after 1m43s
Definition-Lint / definition-doc (push) Failing after 6m13s
E2E MultiCluster Test / detect-noop (push) Successful in 24s
E2E Test / detect-noop (push) Successful in 17s
Go / detect-noop (push) Successful in 21s
license / Check for unapproved licenses (push) Failing after 2m38s
Registry / publish-core-images (push) Failing after 40s
Unit-Test / detect-noop (push) Successful in 20s
E2E MultiCluster Test / e2e-multi-cluster-tests (v1.29) (push) Failing after 1m55s
E2E Test / e2e-tests (v1.29) (push) Failing after 1m18s
Go / staticcheck (push) Successful in 18m35s
Go / lint (push) Failing after 19m38s
Go / check-diff (push) Failing after 15m7s
Go / check-core-image-build (push) Failing after 3m45s
Go / check-cli-image-build (push) Failing after 2m23s
Unit-Test / unit-tests (push) Failing after 12m43s
Go / check-windows (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Failing after 48s
CodeQL / Analyze (go) (push) Failing after 1m43s
Definition-Lint / definition-doc (push) Failing after 6m13s
E2E MultiCluster Test / detect-noop (push) Successful in 24s
E2E Test / detect-noop (push) Successful in 17s
Go / detect-noop (push) Successful in 21s
license / Check for unapproved licenses (push) Failing after 2m38s
Registry / publish-core-images (push) Failing after 40s
Unit-Test / detect-noop (push) Successful in 20s
E2E MultiCluster Test / e2e-multi-cluster-tests (v1.29) (push) Failing after 1m55s
E2E Test / e2e-tests (v1.29) (push) Failing after 1m18s
Go / staticcheck (push) Successful in 18m35s
Go / lint (push) Failing after 19m38s
Go / check-diff (push) Failing after 15m7s
Go / check-core-image-build (push) Failing after 3m45s
Go / check-cli-image-build (push) Failing after 2m23s
Unit-Test / unit-tests (push) Failing after 12m43s
Go / check-windows (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Failing after 48s
* feature: Add Semantic versioning to KubeVela Definitions Fixes https://github.com/kubevela/kubevela/issues/6435 Fixes https://github.com/kubevela/kubevela/issues/6534 Changes: - Adds an optional "Version" field for all Definition Specs. - Adds the following new validations to Webhooks for Definitions: - Validate the "Version" field follows Semantic versioning. - Dis-allow conflicting versioning fields ( Name annotation, Spec.Version) - Adds the following new validations to Webhooks for Application: - Dis-allow the use of both the "publishVersion" & "autoUpdate" annotations. - Enahnce "multiStageComponentApply" feature to support auto updates. Boy Scout Changes: - Fixes Plugin e2e tests broken by the fix for 6534. - Fixes the dryRun and livediff commands to respect the "-n" namespace flag. - Fixes the Application ValidationWebhook to respect the "-n" namespace flag. Co-authored-by: Rahul Kumar <35751394+bugbounce@users.noreply.github.com> Co-authored-by: Chaitanya Reddy <chaitanyareddy0702@gmail.com> Co-authored-by: Vibhor Chinda <vibhorchinda@gmail.com> Co-authored-by: Shivin Gopalani <gopalanishivin@gmail.com> Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> * feature: Add KEP to define the proposal Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> * fix: Rebase and fix merge conflicts Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> * Fix: Adds unit test cases Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> --------- Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> Co-authored-by: bugbounce <35751394+bugbounce@users.noreply.github.com>
This commit is contained in:
co-authored by
Rahul Kumar
Chaitanya Reddy
Vibhor Chinda
Shivin Gopalani
parent
d0d7beb700
commit
bc15e5b359
@@ -27,6 +27,9 @@ import (
|
||||
|
||||
// ComponentDefinitionSpec defines the desired state of ComponentDefinition
|
||||
type ComponentDefinitionSpec struct {
|
||||
// +optional
|
||||
Version string `json:"version,omitempty"`
|
||||
|
||||
// Workload is a workload type descriptor
|
||||
Workload common.WorkloadTypeDescriptor `json:"workload"`
|
||||
|
||||
|
||||
@@ -164,6 +164,9 @@ type TraitDefinitionSpec struct {
|
||||
// pre-process and post-process respectively.
|
||||
// +optional
|
||||
Stage StageType `json:"stage,omitempty"`
|
||||
|
||||
// +optional
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// StageType describes how the manifests should be dispatched.
|
||||
|
||||
@@ -37,6 +37,9 @@ type PolicyDefinitionSpec struct {
|
||||
// ManageHealthCheck means the policy will handle health checking and skip application controller
|
||||
// built-in health checking.
|
||||
ManageHealthCheck bool `json:"manageHealthCheck,omitempty"`
|
||||
|
||||
//+optional
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// PolicyDefinitionStatus is the status of PolicyDefinition
|
||||
|
||||
@@ -33,6 +33,9 @@ type WorkflowStepDefinitionSpec struct {
|
||||
// Only CUE schematic is supported for now.
|
||||
// +optional
|
||||
Schematic *common.Schematic `json:"schematic,omitempty"`
|
||||
|
||||
// +optional
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowStepDefinitionStatus is the status of WorkflowStepDefinition
|
||||
|
||||
@@ -1053,6 +1053,8 @@ spec:
|
||||
for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
workload:
|
||||
description: Workload is a workload type descriptor
|
||||
properties:
|
||||
@@ -1356,6 +1358,8 @@ spec:
|
||||
- configuration
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: PolicyDefinitionStatus is the status of PolicyDefinition
|
||||
@@ -1651,6 +1655,8 @@ spec:
|
||||
for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait
|
||||
accepts a workloadRef object
|
||||
@@ -2059,6 +2065,8 @@ spec:
|
||||
- configuration
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: WorkflowStepDefinitionStatus is the status of WorkflowStepDefinition
|
||||
|
||||
@@ -195,6 +195,8 @@ spec:
|
||||
the abstraction
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
workload:
|
||||
description: Workload is a workload type descriptor
|
||||
properties:
|
||||
|
||||
@@ -241,6 +241,8 @@ spec:
|
||||
for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
workload:
|
||||
description: Workload is a workload type descriptor
|
||||
properties:
|
||||
@@ -489,6 +491,8 @@ spec:
|
||||
- configuration
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: PolicyDefinitionStatus is the status of PolicyDefinition
|
||||
@@ -774,6 +778,8 @@ spec:
|
||||
for the abstraction
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts
|
||||
a workloadRef object
|
||||
@@ -992,6 +998,8 @@ spec:
|
||||
- configuration
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: WorkflowStepDefinitionStatus is the status of WorkflowStepDefinition
|
||||
|
||||
@@ -156,6 +156,8 @@ spec:
|
||||
- configuration
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: PolicyDefinitionStatus is the status of PolicyDefinition
|
||||
|
||||
@@ -225,6 +225,8 @@ spec:
|
||||
the abstraction
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a
|
||||
workloadRef object
|
||||
|
||||
@@ -152,6 +152,8 @@ spec:
|
||||
- configuration
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: WorkflowStepDefinitionStatus is the status of WorkflowStepDefinition
|
||||
|
||||
@@ -14,13 +14,13 @@ webhooks:
|
||||
service:
|
||||
name: {{ template "kubevela.name" . }}-webhook
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-core-oam-dev-v1alpha2-traitdefinitions
|
||||
path: /validating-core-oam-dev-v1beta1-traitdefinitions
|
||||
{{- if .Values.admissionWebhooks.patch.enabled }}
|
||||
failurePolicy: Ignore
|
||||
{{- else }}
|
||||
failurePolicy: {{ .Values.admissionWebhooks.failurePolicy }}
|
||||
{{- end }}
|
||||
name: validating.core.oam.dev.v1alpha2.traitdefinitions
|
||||
name: validating.core.oam.dev.v1beta1.traitdefinitions
|
||||
sideEffects: None
|
||||
admissionReviewVersions:
|
||||
- v1beta1
|
||||
@@ -35,7 +35,6 @@ webhooks:
|
||||
- UPDATE
|
||||
resources:
|
||||
- traitdefinitions
|
||||
scope: Cluster
|
||||
timeoutSeconds: 5
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
|
||||
@@ -296,4 +296,4 @@ authentication:
|
||||
## @param sharding.schedulableShards The shards available for scheduling. If empty, dynamic discovery will be used.
|
||||
sharding:
|
||||
enabled: false
|
||||
schedulableShards: ""
|
||||
schedulableShards: ""
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
# Versioning Support for KubeVela Definitions
|
||||
|
||||
<!-- toc -->
|
||||
- [Versioning Support for KubeVela Definitions](#versioning-support-for-kubevela-definitions)
|
||||
- [Summary](#summary)
|
||||
- [Scope](#scope)
|
||||
- [Motivation](#motivation)
|
||||
- [Goals](#goals)
|
||||
- [Non-Goals](#non-goals)
|
||||
- [Acceptance Criteria](#acceptance-criteria)
|
||||
- [Current Implementation](#current-implementation)
|
||||
- [Versioning](#versioning)
|
||||
- [Auto Upgrade](#auto-upgrade)
|
||||
- [Reference:](#reference)
|
||||
- [Proposal](#proposal)
|
||||
- [Details](#details)
|
||||
- [Issues](#issues)
|
||||
- [Examples](#examples)
|
||||
<!-- /toc -->
|
||||
|
||||
## Summary
|
||||
|
||||
Support Semantic versioning for KubeVela Components and a way to allow fine control over auto-upgrades of KubeVela Applications to new versions of a Component. The implementation should include support for consistent versioning across environments/clusters, meaning specific Revisions/Versions of a Component should have consistent behaviour.
|
||||
|
||||
## Scope
|
||||
|
||||
Although, this document limits the scope of discussion to ComponentDefinition Revisions/Versions, due to the current implementation, the changes will most likely apply to all [`Definition`](https://kubevela.io/docs/getting-started/definition/) types. These changes are planned to be explored and validated as part of the implementation.
|
||||
|
||||
## Motivation
|
||||
|
||||
OAM/KubeVela Definitions (referred to as ComponentDefinitions of Components in the rest
|
||||
of the document) are the basic building blocks of the KubeVela platform. They
|
||||
expose a contract similar to an API contract, which evolves from minor to major
|
||||
versions. Applications are composed of Components that the KubeVela engine stitches
|
||||
together.
|
||||
|
||||
KubeVela creates a `DefinitionRevision` for all changes in a Component `spec`.
|
||||
Currently, Applications can refer to a particular Revision of a Component.
|
||||
But, this versioning scheme has the following issues:
|
||||
|
||||
- The `DefinitionRevision` does not denote the type of the change (patch/bug, minor or major). This hinders automation of automatic upgrades.
|
||||
- The current scheme also doesn't allow much control over automatic upgrades to new Component Revisions. KubeVela automatically upgrades/reconciles the Application to the
|
||||
latest when no Component Revision is specified.
|
||||
> While we don't ideally want Application developers to bother with such details, there are use cases where
|
||||
> an automatic upgrade to the latest Component version is not desired.
|
||||
|
||||
|
||||
### Goals
|
||||
|
||||
- Support Component versioning with Semantic Versions.
|
||||
- Allow pinning specific and non-specific versions of a Component in the
|
||||
KubeVela Application.
|
||||
|
||||
### Non-Goals
|
||||
|
||||
- Support for version range in Application. For eg. "type: my-component@>1.2.0"
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**User Story: Component version specification**
|
||||
|
||||
>**AS A** Component author\
|
||||
>**I SHOULD** be able to publish every version of my Component with the Semantic Versioning scheme\
|
||||
>**SO THAT** an Application developer can use a specific version of the Component.
|
||||
|
||||
**BDD Acceptance Criteria**
|
||||
|
||||
>**GIVEN** an updated ComponentDefinition Specification \
|
||||
>**AND** a version denoted by the ComponentDefinition is set to V\
|
||||
>**WHEN** the Component is applied to KubeVela\
|
||||
>**THEN** `V` should be listed as one of the many versions in the DefinitionRevision list
|
||||
|
||||
**User Story: Application Component version specification**
|
||||
|
||||
>**AS AN** Application developer\
|
||||
>**I SHOULD** be able to specify a version (complete or partial) for every Component used\
|
||||
>**SO THAT** I can control which version are deployed.
|
||||
|
||||
**BDD Acceptance Criteria**
|
||||
|
||||
>Scenario 1: Use the version specified in the Application manifest when deploying the service\
|
||||
>**GIVEN** a Component A with versions 1.2.2 | 1.2.3\
|
||||
>**AND** a Component B with versions 4.4.2 | 4.5.6\
|
||||
>**AND** an Application composed of A 1.2.2 and B 4.4.2\
|
||||
>**WHEN** the Application is deployed\
|
||||
>**THEN** it uses Component A 1.2.2 and B 4.4.2
|
||||
>
|
||||
>**Variant:** Use the latest version for the part of the SemVer that is not specified.\
|
||||
>**GIVEN** component A latest version is 1.2.3
|
||||
>**AND** Component B latest version is 4.5.6
|
||||
>**AND** an Application composed of A 1.2 and B 4\
|
||||
>**WHEN** the Application is deployed\
|
||||
>**THEN** it uses Component A 1.2.3 and B 4.5.6
|
||||
|
||||
> Scenario 2: Behaviour when auto-upgrade is disabled \
|
||||
> **GIVEN** a component A with version 1.2.3\
|
||||
> **AND** an Application composed of A-1.2.3\
|
||||
> **IF** Auto-upgrade is disabled\
|
||||
> **WHEN** a new version of Component A (A-1.2.5) is released\
|
||||
> **THEN** the Application should continue to use A-1.2.3
|
||||
>
|
||||
> **Variant** Behaviour when auto-upgrade is disabled and exact version is unavailable.\
|
||||
> **GIVEN** a component A with version 1.2.3\
|
||||
> **AND** a new Application composed of A-1.2.2\
|
||||
> **IF** Auto-upgrade is disabled\
|
||||
> **WHEN** the Application is applied\
|
||||
> **THEN** the Application deployment should fail.
|
||||
>
|
||||
> **Variant** Behaviour when auto-upgrade is disabled and exact version is unavailable.\
|
||||
> **GIVEN** a component A with version 1.2.3\
|
||||
> **AND** a new Application composed of A-1.2\
|
||||
> **IF** Auto-upgrade is disabled\
|
||||
> **WHEN** the Application is applied\
|
||||
> **THEN** the Application deployment should fail.
|
||||
|
||||
> Scenario 3: Behaviour when auto-upgrade is enabled \
|
||||
> **GIVEN** a component A with version 1.2.3\
|
||||
> **AND** an Application composed of A-1.2\
|
||||
> **IF** Auto-upgrade is enabled\
|
||||
> **THEN** the Application should use A-1.2.3
|
||||
> **AND WHEN** a new version of Component A (A-1.2.5) is released\
|
||||
> **THEN** the Application should update to use A-1.2.5
|
||||
>
|
||||
> **Variant** Behaviour when auto-upgrade is enabled and exact version is unavailable \
|
||||
> **GIVEN** a component A with version 1.2.3\
|
||||
> **AND** a new Application composed of A-1.2.2\
|
||||
> **IF** Auto-upgrade is enabled\
|
||||
> **WHEN** the Application is applied\
|
||||
> **THEN** the Application deployment should fail.
|
||||
>
|
||||
> **Variant** Behaviour when auto-upgrade is enabled and exact version is unavailable \
|
||||
> **GIVEN** a component A with version 1.2.3\
|
||||
> **AND** a new Application composed of A-1.2\
|
||||
> **IF** Auto-upgrade is enabled\
|
||||
> **WHEN** the Application is applied\
|
||||
> **THEN** the Application deployment should use A-1.2.3.
|
||||
|
||||
> Scenario 4: Expectations of consistent versioning across Environments/Clusters. \
|
||||
> **GIVEN** a component A with versions 1.2.1|1.2.2|2.2.1\
|
||||
> **AND** an Application composed of A-1.2.2\
|
||||
> **IF** the Application needs to be deployed across Environments (Dev, Prod etc)\
|
||||
> **OR** the Application needs to be deployed in multiple clusters managed independently\
|
||||
> **WHEN** the Application is deployed across Environments/Clusters \
|
||||
> **THEN** The Application should behave consistently, as in all the clusters A-1.2.2 map to the same ComponentDefinition changes.
|
||||
|
||||
## Current Implementation
|
||||
|
||||
### Versioning
|
||||
|
||||
Currently, KubeVela has some support for controlling Definition versions based on K8s annotations and DefinitionRevisions. The annotation `definitionrevision.oam.dev/name` can be used to version the ComponentDefinition. For example if the following annotation is added to a ComponentDefinition, it produces a new DefinitionRevision and names the ComponentDefinition as `component-name-v4.4` .
|
||||
|
||||
> definitionrevision.oam.dev/name: "4.4"
|
||||
|
||||
This Component can then be referred in the Application as follows:
|
||||
|
||||
>"component-name@v4.4" - `NamedDefinitionRevision`
|
||||
|
||||
Alternatively, since DefinitionRevisions are maintained even if a **"named"** Revision is not specified via the annotation `definitionrevision.oam.dev/name`, Applications can still refer to a particular Revision of a Component via the auto-incrementing Revision numbers.
|
||||
|
||||
>"component-name@v2" - `DefinitionRevision`
|
||||
|
||||

|
||||
|
||||
This versioning scheme, although convenient, has the following issues:
|
||||
|
||||
- Applications which do not explicitly specify a target Revision of a ComponentDefinition, the "latest" applied revision of the ComponentDefinition is used. In scenarios where a cluster has to be replicated or re-created, this means that the sequence in which revisions of a ComponentDefinition are applied becomes important. Implicitly, this also means that the Component maintainers need to keep all Revisions of a ComponentDefinition in their deployment pipeline.\
|
||||
If `definitionrevision.oam.dev/name` annotation is not added to ComponentDefinitions, even if the Applications are explicit about a Component Revision, there is currently no guarantee that the Application behaviour will be consistent across Environments/Clusters. For example, a `Dev` environment will typically have more churn in Revisions than a `Prod` one and a reference to Component Revision `v3` in an Application will not be the same in both environments.
|
||||
|
||||
|
||||
### Auto Upgrade
|
||||
KubeVela utilises the annotation `app.oam.dev/autoUpdate` for automatic upgrade.
|
||||
|
||||
Application reconciliation behaviour when the `app.oam.dev/autoUpdate` annotation is specified in the Application:
|
||||
- If a ComponentDefinition Revision is not specified, the Application will always use the latest available Revision.
|
||||
- If a ComponentDefinition Revision is specified and a new Revision is released after the Application was created, the latest changes will not reflect in the Application.
|
||||
|
||||
Note: This feature is not documented in KubeVela documentation.
|
||||
|
||||
#### Reference:
|
||||
|
||||
- https://kubevela.io/docs/platform-engineers/x-def-version/
|
||||
- [Auto Upgrade PR](https://github.com/kubevela/kubevela/pull/3217)
|
||||
|
||||
|
||||
## Proposal
|
||||
|
||||
### Introduce `spec.version` as an optional field in the Definition
|
||||
|
||||
- Add an optional field `version` in the Definition `spec` and use it to generate the ComponentDefinition Revisions.
|
||||
|
||||
- Update the auto-upgrade behaviour to also allow limiting upgrades for an Application within a specified Definition version range. The existing annotation `app.oam.dev/autoUpdate` for enabling automatic updates will be used for this new behaviour and will maintain backward compatibility.
|
||||
|
||||
- Implement Validating webhook to:
|
||||
- Ensure that the values of the annotation `definitionrevision.oam.dev/version`, `definitionrevision.oam.dev/name` or `spec.version` field adhere to semantic versioning.
|
||||
- Ensure that the `definitionrevision.oam.dev/name` annotation and the `spec.version` field are not present together in the ComponentDefinition to avoid conflicts.
|
||||
- Ensure that `app.oam.dev/publishVersion` and `app.oam.dev/autoUpdate` both annotation are not present in Application to avoid conflicts.
|
||||
|
||||
### Issues
|
||||
|
||||
The following issues assume adherence to strict backward compatibility, meaning the `definitionrevision.oam.dev/name` annotation should continue to work as is.
|
||||
|
||||
- It does not resolve inconsistent versioning behaviour across Environments/Clusters when explicit versions are not specified or named DefinitionRevisions are not used.
|
||||
|
||||
## Examples
|
||||
1. Create a `configmap-component` ComponentDefinition with `1.2.5` version
|
||||
```
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: configmap-component
|
||||
namespace: vela-system
|
||||
spec:
|
||||
version: 1.2.5
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: "comptest"
|
||||
}
|
||||
data: {
|
||||
version: "125"
|
||||
}
|
||||
}
|
||||
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
```
|
||||
|
||||
2. Create a `configmap-component` ComponentDefinition with `2.0.5` version
|
||||
```apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: configmap-component
|
||||
namespace: vela-system
|
||||
spec:
|
||||
version: 2.5.0
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: "comptest"
|
||||
}
|
||||
data: {
|
||||
version: "250"
|
||||
}
|
||||
}
|
||||
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
```
|
||||
3. List DefinitionRevisions
|
||||
```
|
||||
kubectl get definitionrevision -n vela-system | grep -i my-component
|
||||
my-component-v1.2.5 1 1a4f3ac77e4fcfef Component
|
||||
my-component-v2.5.0 2 e61e9b5e55b01c2b Component
|
||||
```
|
||||
|
||||
4. Create Application using `configmap-component@v1.2` version and enable the Auto Update using `app.oam.dev/autoUpdate` annotation.
|
||||
```apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: test-app
|
||||
namespace: test
|
||||
annotations:
|
||||
app.oam.dev/autoUpdate: "true"
|
||||
spec:
|
||||
components:
|
||||
- name: test
|
||||
type: my-component@v1
|
||||
```
|
||||
|
||||
Expected Behavior:
|
||||
- Application will use `configmap-component@v1.2.5`, as `1.2.5` is highest version in specified range(`1`).
|
||||
|
||||
5. Create a `configmap-component` ComponentDefinition with `1.2.7` version
|
||||
```
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: configmap-component
|
||||
namespace: vela-system
|
||||
spec:
|
||||
version: 1.2.7
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: "comptest"
|
||||
}
|
||||
data: {
|
||||
version: "127"
|
||||
}
|
||||
}
|
||||
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
```
|
||||
|
||||
Expected Behavior:
|
||||
- After the Application is reconciled, it will use `configmap-component@v1.2.7`, as `1.2.7` is the latest version within the specified range (1).
|
||||
|
||||
6. List Definitionrevision
|
||||
```kubectl get definitionrevision -n vela-system | grep -i my-component
|
||||
my-component-v1.2.5 1 1a4f3ac77e4fcfef Component
|
||||
my-component-v1.2.7 3 86d7fb1a36566dea Component
|
||||
my-component-v2.5.0 2 e61e9b5e55b01c2b Component```
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
Copyright 2024 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
oamcommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/e2e"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var _ = Describe("Application Auto update", Ordered, func() {
|
||||
ctx := context.Background()
|
||||
var k8sClient client.Client
|
||||
var namespace string
|
||||
var ns corev1.Namespace
|
||||
var err error
|
||||
var velaCommandPrefix string
|
||||
|
||||
BeforeEach(func() {
|
||||
k8sClient, err = common.NewK8sClient()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Create namespace for app-autoupdate-e2e-test")
|
||||
namespace = randomNamespaceName("app-autoupdate-e2e-test")
|
||||
ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
|
||||
k8sClient.Create(ctx, &ns)
|
||||
velaCommandPrefix = fmt.Sprintf("vela -n %s", namespace)
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
By("Clean up resources after a test")
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace))
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace))
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.DefinitionRevision{}, client.InNamespace(namespace))
|
||||
Expect(k8sClient.Delete(ctx, &ns)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("dry-run command", func() {
|
||||
By("Create configmap-component with 1.2.0 version")
|
||||
component := configMapComponent.DeepCopy()
|
||||
component.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
|
||||
By("Execute a dry-run for application having configmap-component@v1 component")
|
||||
output, err := e2e.Exec(fmt.Sprintf("%s dry-run -f data/app.yaml", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring(fmt.Sprintf(dryRunResult1, namespace)))
|
||||
|
||||
By("Create application using configmap-component@v1 component")
|
||||
_, err = e2e.Exec(fmt.Sprintf("%s up -f data/app.yaml", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Create configmap-component with 1.4.0 version")
|
||||
updatedComponent := new(v1beta1.ComponentDefinition)
|
||||
updatedComponentVersion := "1.4.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "configmap-component", Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Schematic.CUE.Template = strings.Replace(configMapOutputTemplate, updatedComponent.Spec.Version, updatedComponentVersion, 1)
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Execute a dry-run for application having configmap-component@v1 component")
|
||||
output, err = e2e.Exec(fmt.Sprintf("%s dry-run -f data/app.yaml", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring(fmt.Sprintf(dryRunResult2, namespace, namespace)))
|
||||
})
|
||||
|
||||
It("live-diff between application file and revision", func() {
|
||||
By("Create configmap-component with 1.2.0 version")
|
||||
component := configMapComponent.DeepCopy()
|
||||
component.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
|
||||
By("Create application using configmap-component@v1 component")
|
||||
_, err = e2e.Exec(fmt.Sprintf("%s up -f data/app.yaml", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Create configmap-component with 1.4.0 version")
|
||||
updatedComponent := new(v1beta1.ComponentDefinition)
|
||||
updatedComponentVersion := "1.4.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "configmap-component", Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Schematic.CUE.Template = strings.Replace(configMapOutputTemplate, updatedComponent.Spec.Version, updatedComponentVersion, 1)
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Execute a live-diff command for application file and previous application")
|
||||
output, err := e2e.Exec(fmt.Sprintf("%s live-diff -f data/app.yaml -r app-with-auto-update-v1", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring(liveDiffResult))
|
||||
})
|
||||
|
||||
It("live-diff between revisions", func() {
|
||||
By("Create configmap-component with 1.2.0 version")
|
||||
component := configMapComponent.DeepCopy()
|
||||
component.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
|
||||
By("Create application using configmap-component@v1 component")
|
||||
_, err = e2e.Exec(fmt.Sprintf("%s up -f data/app.yaml", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Create configmap-component with 1.4.0 version")
|
||||
updatedComponent := new(v1beta1.ComponentDefinition)
|
||||
updatedComponentVersion := "1.4.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "configmap-component", Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Schematic.CUE.Template = strings.Replace(configMapOutputTemplate, updatedComponent.Spec.Version, updatedComponentVersion, 1)
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Create application using configmap-component@v1 component")
|
||||
_, err = e2e.Exec(fmt.Sprintf("%s up -f data/app.yaml", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Execute a live-diff command for previous two application versions")
|
||||
output, err := e2e.Exec(fmt.Sprintf("%s live-diff --revision app-with-auto-update-v2,app-with-auto-update-v1", velaCommandPrefix))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring("Application (app-with-auto-update) has no change"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var configMapComponent = &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-component",
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Version: "1.2.0",
|
||||
Schematic: &oamcommon.Schematic{
|
||||
CUE: &oamcommon.CUE{
|
||||
Template: configMapOutputTemplate,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var configMapOutputTemplate = `output: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: name: "comptest"
|
||||
data: {
|
||||
expectedVersion: "1.2.0"
|
||||
}
|
||||
}`
|
||||
|
||||
func randomNamespaceName(basic string) string {
|
||||
return fmt.Sprintf("%s-%s", basic, strconv.FormatInt(rand.Int63(), 16))
|
||||
}
|
||||
|
||||
var dryRunResult1 = `---
|
||||
# Application(app-with-auto-update) -- Component(test)
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
data:
|
||||
expectedVersion: 1.2.0
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/autoUpdate: "true"
|
||||
labels:
|
||||
app.oam.dev/appRevision: ""
|
||||
app.oam.dev/component: test
|
||||
app.oam.dev/name: app-with-auto-update
|
||||
app.oam.dev/namespace: %[1]s
|
||||
app.oam.dev/resourceType: WORKLOAD
|
||||
workload.oam.dev/type: configmap-component-v1
|
||||
name: comptest
|
||||
namespace: %[1]s
|
||||
|
||||
---`
|
||||
|
||||
var dryRunResult2 = `---
|
||||
# Application(app-with-auto-update) -- Component(test)
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
data:
|
||||
expectedVersion: 1.4.0
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/autoUpdate: "true"
|
||||
labels:
|
||||
app.oam.dev/appRevision: ""
|
||||
app.oam.dev/component: test
|
||||
app.oam.dev/name: app-with-auto-update
|
||||
app.oam.dev/namespace: %[1]s
|
||||
app.oam.dev/resourceType: WORKLOAD
|
||||
workload.oam.dev/type: configmap-component-v1
|
||||
name: comptest
|
||||
namespace: %[1]s
|
||||
|
||||
---
|
||||
|
||||
|
||||
`
|
||||
|
||||
var liveDiffResult = `
|
||||
- expectedVersion: 1.2.0
|
||||
+ expectedVersion: 1.4.0
|
||||
`
|
||||
@@ -184,8 +184,10 @@ var ApplicationInitIntercativeCliContext = func(context string, appName string,
|
||||
})
|
||||
}
|
||||
|
||||
// debug test
|
||||
var ApplicationDeleteWithWaitOptions = func(context string, appName string) bool {
|
||||
return ginkgo.It(context+": should print successful deletion information", func() {
|
||||
return ginkgo.It(context+": should print successful deletion information ", func() {
|
||||
time.Sleep(1 * time.Minute)
|
||||
cli := fmt.Sprintf("vela delete %s --wait -y", appName)
|
||||
output, err := e2e.LongTimeExec(cli, 10*time.Second)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-with-auto-update
|
||||
annotations:
|
||||
app.oam.dev/autoUpdate: "true"
|
||||
spec:
|
||||
components:
|
||||
- name: test
|
||||
type: configmap-component@v1
|
||||
+1
-1
@@ -56,7 +56,7 @@ func ExecAndTerminate(cli string) (string, error) {
|
||||
if err != nil {
|
||||
return string(output), err
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
time.Sleep(10 * time.Second)
|
||||
s := session.Terminate()
|
||||
return string(s.Out.Contents()) + string(s.Err.Contents()), nil
|
||||
}
|
||||
|
||||
+19
-17
@@ -462,24 +462,26 @@ spec:
|
||||
- backend
|
||||
podDisruptive: true
|
||||
schematic:
|
||||
kube:
|
||||
template:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
metadata: {
|
||||
name: "my-service"
|
||||
}
|
||||
spec:{
|
||||
ports: [{
|
||||
protocol: "TCP"
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
parameters:
|
||||
- name: targetPort
|
||||
required: true
|
||||
type: number
|
||||
fieldPaths:
|
||||
- "spec.template.spec.ports[0].targetPort"
|
||||
description: "target port num for service provider."
|
||||
targetPort: parameters.targetPort
|
||||
}]
|
||||
}
|
||||
parameters:{
|
||||
//+usage=target port num for service provider
|
||||
targetPort: *9376 | int
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var componentWithDeepCue = `
|
||||
|
||||
@@ -6,6 +6,7 @@ require (
|
||||
cuelang.org/go v0.9.2
|
||||
github.com/AlecAivazis/survey/v2 v2.1.1
|
||||
github.com/FogDong/uitable v0.0.5
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||
@@ -105,7 +106,6 @@ require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
|
||||
+6
-5
@@ -24,13 +24,14 @@ e2e-setup-core-wo-auth:
|
||||
--namespace vela-system \
|
||||
--set image.pullPolicy=IfNotPresent \
|
||||
--set image.repository=vela-core-test \
|
||||
--set applicationRevisionLimit=5 \
|
||||
--set applicationRevisionLimit=5 \
|
||||
--set controllerArgs.reSyncPeriod=1m \
|
||||
--set optimize.disableComponentRevision=false \
|
||||
--set image.tag=$(GIT_COMMIT) \
|
||||
--set multicluster.clusterGateway.image.repository=ghcr.io/oam-dev/cluster-gateway \
|
||||
--set admissionWebhooks.patch.image.repository=ghcr.io/oam-dev/kube-webhook-certgen/kube-webhook-certgen \
|
||||
--set multicluster.clusterGateway.image.repository=ghcr.io/oam-dev/cluster-gateway \
|
||||
--set admissionWebhooks.patch.image.repository=ghcr.io/oam-dev/kube-webhook-certgen/kube-webhook-certgen \
|
||||
--wait kubevela ./charts/vela-core \
|
||||
--debug
|
||||
--debug
|
||||
|
||||
.PHONY: e2e-setup-core-w-auth
|
||||
e2e-setup-core-w-auth:
|
||||
@@ -109,4 +110,4 @@ end-e2e-core-shards: end-e2e-core
|
||||
|
||||
.PHONY: end-e2e
|
||||
end-e2e:
|
||||
sh ./hack/e2e/end_e2e.sh
|
||||
sh ./hack/e2e/end_e2e.sh
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
// NewLiveDiffOption creates a live-diff option
|
||||
@@ -118,9 +119,11 @@ func (l *LiveDiffOption) RenderlessDiff(ctx context.Context, base, comparor Live
|
||||
switch {
|
||||
case obj.Application != nil:
|
||||
app = obj.Application.DeepCopy()
|
||||
ctx = context.WithValue(ctx, oamutil.AppDefinitionNamespace, app.Namespace)
|
||||
af, err = l.Parser.GenerateAppFileFromApp(ctx, obj.Application)
|
||||
case obj.ApplicationRevision != nil:
|
||||
app = obj.ApplicationRevision.Spec.Application.DeepCopy()
|
||||
ctx = context.WithValue(ctx, oamutil.AppDefinitionNamespace, app.Namespace)
|
||||
af, err = l.Parser.GenerateAppFileFromRevision(obj.ApplicationRevision)
|
||||
default:
|
||||
err = errors.Errorf("either application or application revision should be set for LiveDiffObject")
|
||||
|
||||
@@ -109,9 +109,15 @@ func (d *Option) ValidateApp(ctx context.Context, filename string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(app.GetNamespace()) == 0 {
|
||||
|
||||
namespace := oamutil.GetDefinitionNamespaceWithCtx(ctx)
|
||||
|
||||
if namespace != "" {
|
||||
app.SetNamespace(namespace)
|
||||
} else if len(app.GetNamespace()) == 0 {
|
||||
app.SetNamespace(corev1.NamespaceDefault)
|
||||
}
|
||||
|
||||
app2 := app.DeepCopy()
|
||||
|
||||
err = d.Client.Get(ctx, client.ObjectKey{Namespace: app.GetNamespace(), Name: app.GetName()}, app2)
|
||||
|
||||
+19
-18
@@ -52,11 +52,11 @@ import (
|
||||
)
|
||||
|
||||
// TemplateLoaderFn load template of a capability definition
|
||||
type TemplateLoaderFn func(context.Context, client.Client, string, types.CapType) (*Template, error)
|
||||
type TemplateLoaderFn func(context.Context, client.Client, string, types.CapType, map[string]string) (*Template, error)
|
||||
|
||||
// LoadTemplate load template of a capability definition
|
||||
func (fn TemplateLoaderFn) LoadTemplate(ctx context.Context, c client.Client, capName string, capType types.CapType) (*Template, error) {
|
||||
return fn(ctx, c, capName, capType)
|
||||
func (fn TemplateLoaderFn) LoadTemplate(ctx context.Context, c client.Client, capName string, capType types.CapType, annotations map[string]string) (*Template, error) {
|
||||
return fn(ctx, c, capName, capType, annotations)
|
||||
}
|
||||
|
||||
// Parser is an application parser
|
||||
@@ -253,7 +253,8 @@ func (p *Parser) parseWorkflowStepsForLegacyRevision(ctx context.Context, af *Ap
|
||||
continue
|
||||
}
|
||||
def := &v1beta1.WorkflowStepDefinition{}
|
||||
if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStep.Type); err != nil {
|
||||
|
||||
if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStep.Type, af.app.Annotations); err != nil {
|
||||
return errors.Wrapf(err, "failed to get workflow step definition %s", workflowStep.Type)
|
||||
}
|
||||
af.RelatedWorkflowStepDefinitions[workflowStep.Type] = def
|
||||
@@ -383,7 +384,7 @@ func (p *Parser) parsePolicies(ctx context.Context, af *Appfile) (err error) {
|
||||
af.RelatedTraitDefinitions[def.Name] = def
|
||||
}
|
||||
default:
|
||||
w, err := p.makeComponent(ctx, policy.Name, policy.Type, types.TypePolicy, policy.Properties)
|
||||
w, err := p.makeComponent(ctx, policy.Name, policy.Type, types.TypePolicy, policy.Properties, af.app.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -471,15 +472,15 @@ func (p *Parser) fetchAndSetWorkflowStepDefinition(ctx context.Context, af *Appf
|
||||
return nil
|
||||
}
|
||||
def := &v1beta1.WorkflowStepDefinition{}
|
||||
if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStepType); err != nil {
|
||||
if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStepType, af.AppAnnotations); err != nil {
|
||||
return errors.Wrapf(err, "failed to get workflow step definition %s", workflowStepType)
|
||||
}
|
||||
af.RelatedWorkflowStepDefinitions[workflowStepType] = def
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) makeComponent(ctx context.Context, name, typ string, capType types.CapType, props *runtime.RawExtension) (*Component, error) {
|
||||
templ, err := p.tmplLoader.LoadTemplate(ctx, p.client, typ, capType)
|
||||
func (p *Parser) makeComponent(ctx context.Context, name, typ string, capType types.CapType, props *runtime.RawExtension, annotations map[string]string) (*Component, error) {
|
||||
templ, err := p.tmplLoader.LoadTemplate(ctx, p.client, typ, capType, annotations)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "fetch component/policy type of %s", name)
|
||||
}
|
||||
@@ -519,7 +520,7 @@ func (p *Parser) convertTemplate2Component(name, typ string, props *runtime.RawE
|
||||
func (p *Parser) parseComponents(ctx context.Context, af *Appfile) error {
|
||||
var comps []*Component
|
||||
for _, c := range af.app.Spec.Components {
|
||||
comp, err := p.parseComponent(ctx, c)
|
||||
comp, err := p.parseComponent(ctx, c, af.app.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -568,25 +569,25 @@ func setComponentDefinitionsFromRevision(af *Appfile) {
|
||||
|
||||
// parseComponent resolve an ApplicationComponent and generate a Component
|
||||
// containing ALL information required by an Appfile.
|
||||
func (p *Parser) parseComponent(ctx context.Context, comp common.ApplicationComponent) (*Component, error) {
|
||||
workload, err := p.makeComponent(ctx, comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties)
|
||||
func (p *Parser) parseComponent(ctx context.Context, comp common.ApplicationComponent, annotations map[string]string) (*Component, error) {
|
||||
workload, err := p.makeComponent(ctx, comp.Name, comp.Type, types.TypeComponentDefinition, comp.Properties, annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = p.parseTraits(ctx, workload, comp); err != nil {
|
||||
if err = p.parseTraits(ctx, workload, comp, annotations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseTraits(ctx context.Context, workload *Component, comp common.ApplicationComponent) error {
|
||||
func (p *Parser) parseTraits(ctx context.Context, workload *Component, comp common.ApplicationComponent, annotations map[string]string) error {
|
||||
for _, traitValue := range comp.Traits {
|
||||
properties, err := util.RawExtension2Map(traitValue.Properties)
|
||||
if err != nil {
|
||||
return errors.Errorf("fail to parse properties of %s for %s", traitValue.Type, comp.Name)
|
||||
}
|
||||
trait, err := p.parseTrait(ctx, traitValue.Type, properties)
|
||||
trait, err := p.parseTrait(ctx, traitValue.Type, properties, annotations)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "component(%s) parse trait(%s)", comp.Name, traitValue.Type)
|
||||
}
|
||||
@@ -649,7 +650,7 @@ func (p *Parser) parseTraitsFromRevision(comp common.ApplicationComponent, appRe
|
||||
func (p *Parser) ParseComponentFromRevisionAndClient(ctx context.Context, c common.ApplicationComponent, appRev *v1beta1.ApplicationRevision) (*Component, error) {
|
||||
comp, err := p.makeComponentFromRevision(c.Name, c.Type, types.TypeComponentDefinition, c.Properties, appRev)
|
||||
if IsNotFoundInAppRevision(err) {
|
||||
comp, err = p.makeComponent(ctx, c.Name, c.Type, types.TypeComponentDefinition, c.Properties)
|
||||
comp, err = p.makeComponent(ctx, c.Name, c.Type, types.TypeComponentDefinition, c.Properties, appRev.Annotations)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -662,7 +663,7 @@ func (p *Parser) ParseComponentFromRevisionAndClient(ctx context.Context, c comm
|
||||
}
|
||||
trait, err := p.parseTraitFromRevision(traitValue.Type, properties, appRev)
|
||||
if IsNotFoundInAppRevision(err) {
|
||||
trait, err = p.parseTrait(ctx, traitValue.Type, properties)
|
||||
trait, err = p.parseTrait(ctx, traitValue.Type, properties, appRev.Annotations)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "component(%s) parse trait(%s)", c.Name, traitValue.Type)
|
||||
@@ -674,8 +675,8 @@ func (p *Parser) ParseComponentFromRevisionAndClient(ctx context.Context, c comm
|
||||
return comp, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}) (*Trait, error) {
|
||||
templ, err := p.tmplLoader.LoadTemplate(ctx, p.client, name, types.TypeTrait)
|
||||
func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}, annotations map[string]string) (*Trait, error) {
|
||||
templ, err := p.tmplLoader.LoadTemplate(ctx, p.client, name, types.TypeTrait, annotations)
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil, errors.Errorf("trait definition of %s not found", name)
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ func TestParser_parseTraits(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
mockTemplateLoaderFn: func(context.Context, client.Client, string, types.CapType) (*Template, error) {
|
||||
mockTemplateLoaderFn: func(context.Context, client.Client, string, types.CapType, map[string]string) (*Template, error) {
|
||||
return nil, fmt.Errorf("unsupported key not found")
|
||||
},
|
||||
wantErr: assert.Error,
|
||||
@@ -580,7 +580,7 @@ func TestParser_parseTraits(t *testing.T) {
|
||||
workload: &Component{},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
mockTemplateLoaderFn: func(ctx context.Context, reader client.Client, s string, capType types.CapType) (*Template, error) {
|
||||
mockTemplateLoaderFn: func(ctx context.Context, reader client.Client, s string, capType types.CapType, annotations map[string]string) (*Template, error) {
|
||||
return &Template{
|
||||
TemplateStr: "template",
|
||||
CapabilityCategory: "network",
|
||||
@@ -598,7 +598,8 @@ func TestParser_parseTraits(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p.tmplLoader = tt.mockTemplateLoaderFn
|
||||
err := p.parseTraits(context.Background(), tt.args.workload, tt.args.comp)
|
||||
annotations := make(map[string]string)
|
||||
err := p.parseTraits(context.Background(), tt.args.workload, tt.args.comp, annotations)
|
||||
tt.wantErr(t, err, fmt.Sprintf("parseTraits(%v, %v)", tt.args.workload, tt.args.comp))
|
||||
if tt.validateFunc != nil {
|
||||
assert.True(t, tt.validateFunc(tt.args.workload))
|
||||
|
||||
@@ -66,13 +66,13 @@ type Template struct {
|
||||
// LoadTemplate gets the capability definition from cluster and resolve it.
|
||||
// It returns a helper struct, Template, which will be used for further
|
||||
// processing.
|
||||
func LoadTemplate(ctx context.Context, cli client.Client, capName string, capType types.CapType) (*Template, error) {
|
||||
func LoadTemplate(ctx context.Context, cli client.Client, capName string, capType types.CapType, annotations map[string]string) (*Template, error) {
|
||||
ctx = multicluster.WithCluster(ctx, multicluster.Local)
|
||||
// Application Controller only loads template from ComponentDefinition and TraitDefinition
|
||||
switch capType {
|
||||
case types.TypeComponentDefinition, types.TypeWorkload:
|
||||
cd := new(v1beta1.ComponentDefinition)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, cd, capName)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, cd, capName, annotations)
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
wd := new(v1beta1.WorkloadDefinition)
|
||||
@@ -108,7 +108,7 @@ func LoadTemplate(ctx context.Context, cli client.Client, capName string, capTyp
|
||||
|
||||
case types.TypeTrait:
|
||||
td := new(v1beta1.TraitDefinition)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, td, capName)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, td, capName, annotations)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "load template from trait definition [%s] ", capName)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func LoadTemplate(ctx context.Context, cli client.Client, capName string, capTyp
|
||||
return tmpl, nil
|
||||
case types.TypePolicy:
|
||||
d := new(v1beta1.PolicyDefinition)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, d, capName)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, d, capName, annotations)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "load template from policy definition [%s] ", capName)
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func LoadTemplate(ctx context.Context, cli client.Client, capName string, capTyp
|
||||
return tmpl, nil
|
||||
case types.TypeWorkflowStep:
|
||||
d := new(v1beta1.WorkflowStepDefinition)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, d, capName)
|
||||
err := oamutil.GetCapabilityDefinition(ctx, cli, d, capName, annotations)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "load template from workflow step definition [%s] ", capName)
|
||||
}
|
||||
@@ -252,7 +252,7 @@ func verifyRevisionName(capName string, capType types.CapType, apprev *v1beta1.A
|
||||
// LoadTemplate, but load template from provided ones before loading from
|
||||
// cluster through LoadTemplate
|
||||
func DryRunTemplateLoader(defs []*unstructured.Unstructured) TemplateLoaderFn {
|
||||
return func(ctx context.Context, r client.Client, capName string, capType types.CapType) (*Template, error) {
|
||||
return func(ctx context.Context, r client.Client, capName string, capType types.CapType, annotations map[string]string) (*Template, error) {
|
||||
// retrieve provided cap definitions
|
||||
for _, def := range defs {
|
||||
if def.GetKind() == v1beta1.ComponentDefinitionKind &&
|
||||
@@ -282,7 +282,7 @@ func DryRunTemplateLoader(defs []*unstructured.Unstructured) TemplateLoaderFn {
|
||||
}
|
||||
// not found in provided cap definitions
|
||||
// then try to retrieve from cluster
|
||||
tmpl, err := LoadTemplate(ctx, r, capName, capType)
|
||||
tmpl, err := LoadTemplate(ctx, r, capName, capType, annotations)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "cannot load template %q from cluster and provided ones", capName)
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ spec:
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
temp, err := LoadTemplate(context.TODO(), &tclient, "worker", types.TypeComponentDefinition)
|
||||
var annotations = make(map[string]string)
|
||||
temp, err := LoadTemplate(context.TODO(), &tclient, "worker", types.TypeComponentDefinition, annotations)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -219,8 +219,8 @@ spec:
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
temp, err := LoadTemplate(context.TODO(), &tclient, "ingress", types.TypeTrait)
|
||||
var annotations = make(map[string]string)
|
||||
temp, err := LoadTemplate(context.TODO(), &tclient, "ingress", types.TypeTrait, annotations)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -362,8 +362,9 @@ spec:
|
||||
TraitDefinition: traitDef,
|
||||
}
|
||||
|
||||
var annotations = make(map[string]string)
|
||||
dryRunLoadTemplate := DryRunTemplateLoader([]*unstructured.Unstructured{unstrctCompDef, unstrctTraitDef})
|
||||
compTmpl, err := dryRunLoadTemplate(nil, nil, "myworker", types.TypeComponentDefinition)
|
||||
compTmpl, err := dryRunLoadTemplate(nil, nil, "myworker", types.TypeComponentDefinition, annotations)
|
||||
if err != nil {
|
||||
t.Error("failed load template of component defintion", err)
|
||||
}
|
||||
@@ -371,7 +372,7 @@ spec:
|
||||
t.Fatal("failed load template of component defintion", diff)
|
||||
}
|
||||
|
||||
traitTmpl, err := dryRunLoadTemplate(nil, nil, "myingress", types.TypeTrait)
|
||||
traitTmpl, err := dryRunLoadTemplate(nil, nil, "myingress", types.TypeTrait, annotations)
|
||||
if err != nil {
|
||||
t.Error("failed load template of component defintion", err)
|
||||
}
|
||||
|
||||
@@ -58,17 +58,17 @@ var _ = Describe("Test dispatch stage", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
stage, err := getTraitDispatchStage(k8sClient, "kruise-rollout", &appRev)
|
||||
var annotations = make(map[string]string)
|
||||
stage, err := getTraitDispatchStage(k8sClient, "kruise-rollout", &appRev, annotations)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(stage).Should(BeEquivalentTo(PreDispatch))
|
||||
stage, err = getTraitDispatchStage(k8sClient, "gateway", &appRev)
|
||||
stage, err = getTraitDispatchStage(k8sClient, "gateway", &appRev, annotations)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(stage).Should(BeEquivalentTo(PostDispatch))
|
||||
stage, err = getTraitDispatchStage(k8sClient, "hpa", &appRev)
|
||||
stage, err = getTraitDispatchStage(k8sClient, "hpa", &appRev, annotations)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(stage).Should(BeEquivalentTo(DefaultDispatch))
|
||||
stage, err = getTraitDispatchStage(k8sClient, "not-exist", &appRev)
|
||||
stage, err = getTraitDispatchStage(k8sClient, "not-exist", &appRev, annotations)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
Expect(stage).Should(BeEquivalentTo(DefaultDispatch))
|
||||
})
|
||||
|
||||
@@ -112,7 +112,7 @@ type manifestDispatcher struct {
|
||||
healthCheck func(ctx context.Context, c *appfile.Component, appRev *v1beta1.ApplicationRevision) (bool, error)
|
||||
}
|
||||
|
||||
func (h *AppHandler) generateDispatcher(appRev *v1beta1.ApplicationRevision, readyWorkload *unstructured.Unstructured, readyTraits []*unstructured.Unstructured, overrideNamespace string) ([]*manifestDispatcher, error) {
|
||||
func (h *AppHandler) generateDispatcher(appRev *v1beta1.ApplicationRevision, readyWorkload *unstructured.Unstructured, readyTraits []*unstructured.Unstructured, overrideNamespace string, annotations map[string]string) ([]*manifestDispatcher, error) {
|
||||
dispatcherGenerator := func(options DispatchOptions) *manifestDispatcher {
|
||||
assembleManifestFn := func(skipApplyWorkload bool) (bool, []*unstructured.Unstructured) {
|
||||
manifests := options.Traits
|
||||
@@ -138,7 +138,13 @@ func (h *AppHandler) generateDispatcher(appRev *v1beta1.ApplicationRevision, rea
|
||||
}
|
||||
dispatcher.run = func(ctx context.Context, comp *appfile.Component, appRev *v1beta1.ApplicationRevision, clusterName string) (bool, error) {
|
||||
skipWorkload, dispatchManifests := assembleManifestFn(comp.SkipApplyWorkload)
|
||||
if isHealth, err := dispatcher.healthCheck(ctx, comp, appRev); !isHealth || err != nil {
|
||||
|
||||
var isAutoUpdateEnabled bool
|
||||
if annotations[oam.AnnotationAutoUpdate] == "true" {
|
||||
isAutoUpdateEnabled = true
|
||||
}
|
||||
|
||||
if isHealth, err := dispatcher.healthCheck(ctx, comp, appRev); !isHealth || err != nil || (!comp.SkipApplyWorkload && isAutoUpdateEnabled) {
|
||||
if err := h.Dispatch(ctx, h.Client, clusterName, common.WorkflowResourceCreator, dispatchManifests...); err != nil {
|
||||
return false, errors.WithMessage(err, "Dispatch")
|
||||
}
|
||||
@@ -179,7 +185,7 @@ func (h *AppHandler) generateDispatcher(appRev *v1beta1.ApplicationRevision, rea
|
||||
traitType = splitName
|
||||
}
|
||||
}
|
||||
stageType, err = getTraitDispatchStage(h.Client, traitType, appRev)
|
||||
stageType, err = getTraitDispatchStage(h.Client, traitType, appRev, annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -210,11 +216,11 @@ func (h *AppHandler) generateDispatcher(appRev *v1beta1.ApplicationRevision, rea
|
||||
return manifestDispatchers, nil
|
||||
}
|
||||
|
||||
func getTraitDispatchStage(client client.Client, traitType string, appRev *v1beta1.ApplicationRevision) (StageType, error) {
|
||||
func getTraitDispatchStage(client client.Client, traitType string, appRev *v1beta1.ApplicationRevision, annotations map[string]string) (StageType, error) {
|
||||
trait, ok := appRev.Spec.TraitDefinitions[traitType]
|
||||
if !ok {
|
||||
trait = &v1beta1.TraitDefinition{}
|
||||
err := oamutil.GetCapabilityDefinition(context.Background(), client, trait, traitType)
|
||||
err := oamutil.GetCapabilityDefinition(context.Background(), client, trait, traitType, annotations)
|
||||
if err != nil {
|
||||
return DefaultDispatch, err
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ func (h *AppHandler) applyComponentFunc(appParser *appfile.Parser, af *appfile.A
|
||||
|
||||
isHealth := true
|
||||
if utilfeature.DefaultMutableFeatureGate.Enabled(features.MultiStageComponentApply) {
|
||||
manifestDispatchers, err := h.generateDispatcher(appRev, readyWorkload, readyTraits, overrideNamespace)
|
||||
manifestDispatchers, err := h.generateDispatcher(appRev, readyWorkload, readyTraits, overrideNamespace, af.AppAnnotations)
|
||||
if err != nil {
|
||||
return nil, nil, false, errors.WithMessage(err, "generateDispatcher")
|
||||
}
|
||||
|
||||
+36
-1
@@ -194,6 +194,18 @@ var _ = Describe("Test DefinitionRevision created by ComponentDefinition", func(
|
||||
Expect(defRev1.Spec.RevisionHash).Should(Equal(defRev2.Spec.RevisionHash))
|
||||
})
|
||||
|
||||
It("Test ComponentDefinition with name specified in spec.version, Should create definitaion with specified name", func() {
|
||||
cd := cdWithNoTemplate.DeepCopy()
|
||||
cd.Name = "test-cd-with-custom-version"
|
||||
cd.Spec.Version = "1.3.0"
|
||||
cd.Spec.Schematic.CUE.Template = fmt.Sprintf(cdTemplate, "test-defrev")
|
||||
|
||||
defRev, _, err := coredef.GenerateDefinitionRevision(ctx, r.Client, cd)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(defRev.Name).Should(Equal("test-cd-with-custom-version-v1.3.0"))
|
||||
|
||||
})
|
||||
|
||||
It("Test only update ComponentDefinition Labels, Shouldn't create new revision", func() {
|
||||
cd := cdWithNoTemplate.DeepCopy()
|
||||
cdName := "test-cd"
|
||||
@@ -258,6 +270,29 @@ var _ = Describe("Test DefinitionRevision created by ComponentDefinition", func(
|
||||
By("check the DefinitionRevision's RevisionNum")
|
||||
Expect(cdRev.Spec.Revision).Should(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("Test specified DefinitionRevision name in spec.version", func() {
|
||||
cdName := "test-specified-defrev1-name"
|
||||
req := reconcile.Request{NamespacedName: client.ObjectKey{Name: cdName, Namespace: namespace}}
|
||||
|
||||
cd := cdWithNoTemplate.DeepCopy()
|
||||
cd.Name = cdName
|
||||
cd.Spec.Schematic.CUE.Template = fmt.Sprintf(cdTemplate, "test")
|
||||
cd.Spec.Version = "1.1.3"
|
||||
By("create componentDefinition")
|
||||
Expect(k8sClient.Create(ctx, cd)).Should(SatisfyAll(BeNil()))
|
||||
testutil.ReconcileRetry(&r, req)
|
||||
|
||||
By("check whether DefinitionRevision is created")
|
||||
cdRevName := fmt.Sprintf("%s-v1.1.3", cdName)
|
||||
var cdRev v1beta1.DefinitionRevision
|
||||
Eventually(func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: cdRevName}, &cdRev)
|
||||
}, 10*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("check the DefinitionRevision's RevisionNum")
|
||||
Expect(cdRev.Spec.Revision).Should(Equal(int64(1)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Test ComponentDefinition Controller clean up", func() {
|
||||
@@ -381,7 +416,7 @@ var _ = Describe("Test DefinitionRevision created by ComponentDefinition", func(
|
||||
}, time.Second*30, time.Microsecond*300).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test clean up definitionRevision contains definitionRevision with custom name", func() {
|
||||
It("Test clean up definitionRevision contains definitionRevision with custom name using annotation", func() {
|
||||
var revKey client.ObjectKey
|
||||
var defRev v1beta1.DefinitionRevision
|
||||
revisionNames := []string{"1.3.1", "", "1.3.3", "", "prod"}
|
||||
+12
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
coredef "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/core"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/testutil"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -128,6 +129,17 @@ var _ = Describe("Test DefinitionRevision created by PolicyDefinition", func() {
|
||||
newRevKey := client.ObjectKey{Namespace: namespace, Name: newDefRevName}
|
||||
Expect(k8sClient.Get(ctx, newRevKey, &defRev)).Should(HaveOccurred())
|
||||
})
|
||||
It("Test Policy Definition with name specified in spec.version, Should create definitaion with specified name", func() {
|
||||
def := defWithNoTemplate.DeepCopy()
|
||||
def.Name = "test-policy-def-custom-version"
|
||||
def.Spec.Version = "1.3.0"
|
||||
def.Spec.Schematic.CUE.Template = fmt.Sprintf(defTemplate, "test-defrev")
|
||||
|
||||
defRev, _, err := coredef.GenerateDefinitionRevision(ctx, r.Client, def)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(defRev.Name).Should(Equal("test-policy-def-custom-version-v1.3.0"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Context("Test PolicyDefinition Controller clean up", func() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/pkg/errors"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
@@ -44,12 +45,20 @@ import (
|
||||
// GenerateDefinitionRevision will generate a definition revision the generated revision
|
||||
// will be compare with the last revision to see if there's any difference.
|
||||
func GenerateDefinitionRevision(ctx context.Context, cli client.Client, def runtime.Object) (*v1beta1.DefinitionRevision, bool, error) {
|
||||
isNamedRev, defRevNamespacedName, err := isNamedRevision(def)
|
||||
isSpecVersion, defRevNamespacedName, err := isSpecVersionRevision(def)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if isSpecVersion {
|
||||
return generateDefinitionRevision(ctx, cli, def, defRevNamespacedName)
|
||||
}
|
||||
|
||||
isNamedRev, defRevNamespacedName, err := isNameAnnotationRevision(def)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if isNamedRev {
|
||||
return generateNamedDefinitionRevision(ctx, cli, def, defRevNamespacedName)
|
||||
return generateDefinitionRevision(ctx, cli, def, defRevNamespacedName)
|
||||
}
|
||||
|
||||
defRev, lastRevision, err := GatherRevisionInfo(def)
|
||||
@@ -68,7 +77,9 @@ func GenerateDefinitionRevision(ctx context.Context, cli client.Client, def runt
|
||||
return defRev, isNewRev, nil
|
||||
}
|
||||
|
||||
func isNamedRevision(def runtime.Object) (bool, types.NamespacedName, error) {
|
||||
// isNameAnnotationRevision is for Definition Version specified in the
|
||||
// Definition's "definitionrevision.oam.dev/name" annotation.
|
||||
func isNameAnnotationRevision(def runtime.Object) (bool, types.NamespacedName, error) {
|
||||
defMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(def)
|
||||
if err != nil {
|
||||
return false, types.NamespacedName{}, err
|
||||
@@ -86,7 +97,42 @@ func isNamedRevision(def runtime.Object) (bool, types.NamespacedName, error) {
|
||||
return true, types.NamespacedName{Name: defRevName, Namespace: defNs}, nil
|
||||
}
|
||||
|
||||
func generateNamedDefinitionRevision(ctx context.Context, cli client.Client, def runtime.Object, defRevNamespacedName types.NamespacedName) (*v1beta1.DefinitionRevision, bool, error) {
|
||||
// isSpecVersionRevision is for Definition Version specified in the Definition spec.Version
|
||||
func isSpecVersionRevision(def runtime.Object) (bool, types.NamespacedName, error) {
|
||||
|
||||
var definitionVersion, definitionNamespace, definitionName string
|
||||
switch definition := def.(type) {
|
||||
case *v1beta1.ComponentDefinition:
|
||||
definitionVersion = definition.Spec.Version
|
||||
definitionNamespace = definition.Namespace
|
||||
definitionName = definition.Name
|
||||
case *v1beta1.TraitDefinition:
|
||||
definitionVersion = definition.Spec.Version
|
||||
definitionNamespace = definition.Namespace
|
||||
definitionName = definition.Name
|
||||
case *v1beta1.PolicyDefinition:
|
||||
definitionVersion = definition.Spec.Version
|
||||
definitionNamespace = definition.Namespace
|
||||
definitionName = definition.Name
|
||||
case *v1beta1.WorkflowStepDefinition:
|
||||
definitionVersion = definition.Spec.Version
|
||||
definitionNamespace = definition.Namespace
|
||||
definitionName = definition.Name
|
||||
}
|
||||
|
||||
if definitionVersion == "" {
|
||||
return false, types.NamespacedName{}, nil
|
||||
}
|
||||
semVersion, err := semver.NewVersion(definitionVersion)
|
||||
if err != nil {
|
||||
return false, types.NamespacedName{}, err
|
||||
}
|
||||
|
||||
definitionRevisionName := ConstructDefinitionRevisionName(definitionName, semVersion.String())
|
||||
return true, types.NamespacedName{Name: definitionRevisionName, Namespace: definitionNamespace}, nil
|
||||
}
|
||||
|
||||
func generateDefinitionRevision(ctx context.Context, cli client.Client, def runtime.Object, defRevNamespacedName types.NamespacedName) (*v1beta1.DefinitionRevision, bool, error) {
|
||||
oldDefRev := new(v1beta1.DefinitionRevision)
|
||||
|
||||
// definitionRevision is immutable, if the requested definitionRevision already exists, return directly.
|
||||
@@ -230,24 +276,27 @@ func DeepEqualDefRevision(old, new *v1beta1.DefinitionRevision) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func getDefNextRevision(defRev *v1beta1.DefinitionRevision, lastRevision *common.Revision) (string, int64) {
|
||||
func getDefNextRevision(definitionRevision *v1beta1.DefinitionRevision, lastRevision *common.Revision) (string, int64) {
|
||||
var nextRevision int64 = 1
|
||||
var definitionRevisionName string
|
||||
if lastRevision != nil {
|
||||
nextRevision = lastRevision.Revision + 1
|
||||
}
|
||||
var name string
|
||||
switch defRev.Spec.DefinitionType {
|
||||
switch definitionRevision.Spec.DefinitionType {
|
||||
case common.ComponentType:
|
||||
name = defRev.Spec.ComponentDefinition.Name
|
||||
name = definitionRevision.Spec.ComponentDefinition.Name
|
||||
case common.TraitType:
|
||||
name = defRev.Spec.TraitDefinition.Name
|
||||
name = definitionRevision.Spec.TraitDefinition.Name
|
||||
case common.PolicyType:
|
||||
name = defRev.Spec.PolicyDefinition.Name
|
||||
name = definitionRevision.Spec.PolicyDefinition.Name
|
||||
case common.WorkflowStepType:
|
||||
name = defRev.Spec.WorkflowStepDefinition.Name
|
||||
name = definitionRevision.Spec.WorkflowStepDefinition.Name
|
||||
}
|
||||
defRevName := strings.Join([]string{name, fmt.Sprintf("v%d", nextRevision)}, "-")
|
||||
return defRevName, nextRevision
|
||||
|
||||
definitionRevisionName = strings.Join([]string{name, fmt.Sprintf("v%v", nextRevision)}, "-")
|
||||
|
||||
return definitionRevisionName, nextRevision
|
||||
}
|
||||
|
||||
// ConstructDefinitionRevisionName construct the name of DefinitionRevision.
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
coredef "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/core"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/testutil"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -128,6 +129,17 @@ var _ = Describe("Test DefinitionRevision created by TraitDefinition", func() {
|
||||
newRevKey := client.ObjectKey{Namespace: namespace, Name: newDefRevName}
|
||||
Expect(k8sClient.Get(ctx, newRevKey, &defRev)).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Test Trait Definition with name specified in spec.version, Should create definitaion with specified name", func() {
|
||||
td := tdWithNoTemplate.DeepCopy()
|
||||
td.Name = "test-trait-def-custom-version"
|
||||
td.Spec.Version = "1.3.0"
|
||||
td.Spec.Schematic.CUE.Template = fmt.Sprintf(tdTemplate, fmt.Sprintf("test-v%d", 1))
|
||||
defRev, _, err := coredef.GenerateDefinitionRevision(ctx, r.Client, td)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(defRev.Name).Should(Equal("test-trait-def-custom-version-v1.3.0"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Context("Test TraitDefinition Controller clean up", func() {
|
||||
|
||||
+11
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
coredef "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/core"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/testutil"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -128,6 +129,16 @@ var _ = Describe("Test DefinitionRevision created by WorkflowStepDefinition", fu
|
||||
newRevKey := client.ObjectKey{Namespace: namespace, Name: newDefRevName}
|
||||
Expect(k8sClient.Get(ctx, newRevKey, &defRev)).Should(HaveOccurred())
|
||||
})
|
||||
It("Test WorkflowStep Definition with name specified in spec.version, Should create definitaion with specified name", func() {
|
||||
def := defWithNoTemplate.DeepCopy()
|
||||
def.Name = "test-workflow-def-custom-version"
|
||||
def.Spec.Version = "1.3.0"
|
||||
def.Spec.Schematic.CUE.Template = fmt.Sprintf(defTemplate, fmt.Sprintf("test-v%d", 1))
|
||||
defRev, _, err := coredef.GenerateDefinitionRevision(ctx, r.Client, def)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(defRev.Name).Should(Equal("test-workflow-def-custom-version-v1.3.0"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Context("Test WorkflowStepDefinition Controller clean up", func() {
|
||||
|
||||
+117
-3
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -39,6 +40,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -122,6 +125,14 @@ const (
|
||||
XDefinitionNamespace
|
||||
)
|
||||
|
||||
// DefinitionKindToNameLabel records DefinitionRevision types and labels to search its name
|
||||
var DefinitionKindToNameLabel = map[common.DefinitionType]string{
|
||||
common.ComponentType: oam.LabelComponentDefinitionName,
|
||||
common.TraitType: oam.LabelTraitDefinitionName,
|
||||
common.PolicyType: oam.LabelPolicyDefinitionName,
|
||||
common.WorkflowStepType: oam.LabelWorkflowStepDefinitionName,
|
||||
}
|
||||
|
||||
// A ConditionedObject is an Object type with condition field
|
||||
type ConditionedObject interface {
|
||||
client.Object
|
||||
@@ -192,6 +203,7 @@ func GetDefinition(ctx context.Context, cli client.Reader, definition client.Obj
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -213,12 +225,17 @@ func GetDefinitionFromNamespace(ctx context.Context, cli client.Reader, definiti
|
||||
|
||||
// GetCapabilityDefinition can get different versions of ComponentDefinition/TraitDefinition
|
||||
func GetCapabilityDefinition(ctx context.Context, cli client.Reader, definition client.Object,
|
||||
definitionName string) error {
|
||||
isLatestRevision, defRev, err := fetchDefinitionRev(ctx, cli, definitionName)
|
||||
definitionName string, annotations map[string]string) error {
|
||||
definitionType, err := getDefinitionType(definition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isLatestRevision, defRev, err := fetchDefinitionRevision(ctx, cli, definitionName, definitionType, annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isLatestRevision {
|
||||
|
||||
return GetDefinition(ctx, cli, definition, definitionName)
|
||||
}
|
||||
switch def := definition.(type) {
|
||||
@@ -235,7 +252,24 @@ func GetCapabilityDefinition(ctx context.Context, cli client.Reader, definition
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchDefinitionRev(ctx context.Context, cli client.Reader, definitionName string) (bool, *v1beta1.DefinitionRevision, error) {
|
||||
func getDefinitionType(definition client.Object) (common.DefinitionType, error) {
|
||||
var definitionType common.DefinitionType
|
||||
switch definition.(type) {
|
||||
case *v1beta1.ComponentDefinition:
|
||||
definitionType = common.ComponentType
|
||||
case *v1beta1.TraitDefinition:
|
||||
definitionType = common.TraitType
|
||||
case *v1beta1.PolicyDefinition:
|
||||
definitionType = common.PolicyType
|
||||
case *v1beta1.WorkflowStepDefinition:
|
||||
definitionType = common.WorkflowStepType
|
||||
default:
|
||||
return definitionType, fmt.Errorf("invalid definition type for %v", definition.GetName())
|
||||
}
|
||||
return definitionType, nil
|
||||
}
|
||||
|
||||
func fetchDefinitionRevision(ctx context.Context, cli client.Reader, definitionName string, definitionType common.DefinitionType, annotations map[string]string) (bool, *v1beta1.DefinitionRevision, error) {
|
||||
// if the component's type doesn't contain '@' means user want to use the latest Definition.
|
||||
if !strings.Contains(definitionName, "@") {
|
||||
return true, nil, nil
|
||||
@@ -245,13 +279,93 @@ func fetchDefinitionRev(ctx context.Context, cli client.Reader, definitionName s
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
defName := strings.Split(definitionName, "@")[0]
|
||||
autoUpdate, ok := annotations[oam.AnnotationAutoUpdate]
|
||||
if ok && autoUpdate == "true" {
|
||||
latestRevisionName, err := GetLatestDefinitionRevisionName(ctx, cli.(client.Client), defName, defRevName, definitionType)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defRevName = latestRevisionName
|
||||
}
|
||||
|
||||
defRev := new(v1beta1.DefinitionRevision)
|
||||
if err := GetDefinition(ctx, cli, defRev, defRevName); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return false, defRev, nil
|
||||
}
|
||||
|
||||
// GetLatestDefinitionRevisionName returns the latest definition revision name in specified version range.
|
||||
func GetLatestDefinitionRevisionName(ctx context.Context, cli client.Client, definitionName, revisionName string, definitionType common.DefinitionType) (string, error) {
|
||||
for _, ns := range []string{GetDefinitionNamespaceWithCtx(ctx), oam.SystemDefinitionNamespace} {
|
||||
|
||||
revisionListForDefinition, err := fetchAllRevisionsForDefinitionName(ctx, cli, ns, definitionName, definitionType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
matchedDefinitionRevision, err := getMatchingDefinitionRevision(revisionName, definitionName, revisionListForDefinition, definitionType)
|
||||
if err == nil && matchedDefinitionRevision != "" {
|
||||
return matchedDefinitionRevision, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("error finding definition revision for Name: %v, Type: %v", definitionName, definitionType)
|
||||
|
||||
}
|
||||
|
||||
func fetchAllRevisionsForDefinitionName(ctx context.Context, cli client.Client, ns, definitionName string, definitionType common.DefinitionType) (*v1beta1.DefinitionRevisionList, error) {
|
||||
var listOptions []client.ListOption
|
||||
listOptions = append(listOptions, client.InNamespace(ns),
|
||||
client.MatchingLabels{
|
||||
DefinitionKindToNameLabel[definitionType]: definitionName,
|
||||
})
|
||||
|
||||
revisionList := v1beta1.DefinitionRevisionList{}
|
||||
revisionList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: v1beta1.Group,
|
||||
Version: v1beta1.Version,
|
||||
Kind: v1beta1.DefinitionRevisionKind,
|
||||
})
|
||||
|
||||
err := cli.List(ctx, &revisionList, listOptions...)
|
||||
|
||||
return &revisionList, err
|
||||
}
|
||||
|
||||
func getMatchingDefinitionRevision(exactRevisionName, definitionName string, revisionList *v1beta1.DefinitionRevisionList, definitionType common.DefinitionType) (string, error) {
|
||||
var definitionVersions []*semver.Version
|
||||
revisionPrefix := exactRevisionName + "."
|
||||
orignalVersions := make(map[string]string)
|
||||
|
||||
for _, revision := range revisionList.Items {
|
||||
if definitionType != "" && definitionType != revision.Spec.DefinitionType {
|
||||
continue
|
||||
}
|
||||
if revision.Name == exactRevisionName {
|
||||
return exactRevisionName, nil
|
||||
}
|
||||
// Only get the revisions that the user expects
|
||||
if strings.HasPrefix(revision.Name, revisionPrefix) {
|
||||
version := strings.Split(revision.Name, definitionName+"-")[1]
|
||||
v, err := semver.NewVersion(version)
|
||||
orignalVersions[v.String()] = version
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
definitionVersions = append(definitionVersions, v)
|
||||
}
|
||||
}
|
||||
if len(definitionVersions) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
sort.Sort(semver.Collection(definitionVersions))
|
||||
latestVersion := definitionVersions[len(definitionVersions)-1]
|
||||
return definitionName + "-" + orignalVersions[latestVersion.String()], nil
|
||||
}
|
||||
|
||||
// ConvertDefinitionRevName can help convert definition type defined in Application to DefinitionRevision Name
|
||||
// e.g., worker@v1.3.1 will be convert to worker-v1.3.1
|
||||
func ConvertDefinitionRevName(definitionName string) (string, error) {
|
||||
|
||||
@@ -980,3 +980,339 @@ func TestXDefinitionNamespaceInCtx(t *testing.T) {
|
||||
assert.Equal(t, tc.expectedNamespace, ns)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestDefinitionRevisionName(t *testing.T) {
|
||||
componetListCli := test.MockClient{MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
|
||||
defRevisionList := getComponentDefRevisionList()
|
||||
defRevisionList.DeepCopyInto(list.(*v1beta1.DefinitionRevisionList))
|
||||
return nil
|
||||
}}
|
||||
traitListCli := test.MockClient{MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
|
||||
defRevisionList := getTraitDefRevisionList()
|
||||
defRevisionList.DeepCopyInto(list.(*v1beta1.DefinitionRevisionList))
|
||||
return nil
|
||||
}}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
inputRevisionName string
|
||||
definitionName string
|
||||
definitionType string
|
||||
expectedDefRevisionName string
|
||||
revisionList *v1beta1.DefinitionRevisionList
|
||||
client client.Client
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Complete Component version name specified",
|
||||
inputRevisionName: "configmap-component-v1.3.0",
|
||||
definitionName: "configmap-component",
|
||||
definitionType: "Component",
|
||||
expectedDefRevisionName: "configmap-component-v1.3.0",
|
||||
client: &componetListCli,
|
||||
err: nil,
|
||||
}, {
|
||||
name: "Partial Component version name specified",
|
||||
inputRevisionName: "configmap-component-v1.2",
|
||||
definitionName: "configmap-component",
|
||||
definitionType: "Component",
|
||||
expectedDefRevisionName: "configmap-component-v1.2.4",
|
||||
client: &componetListCli,
|
||||
err: nil,
|
||||
}, {
|
||||
name: "Component version not present",
|
||||
inputRevisionName: "configmap-component-v1.6",
|
||||
definitionName: "configmap-component",
|
||||
definitionType: "Component",
|
||||
expectedDefRevisionName: "",
|
||||
client: &componetListCli,
|
||||
err: fmt.Errorf("error finding definition revision for Name: configmap-component, Type: Component"),
|
||||
}, {
|
||||
name: "Complete Trait version name specified",
|
||||
inputRevisionName: "scaler-trait-v1.3.0",
|
||||
definitionName: "scaler-trait",
|
||||
definitionType: "Trait",
|
||||
expectedDefRevisionName: "scaler-trait-v1.3.0",
|
||||
client: &traitListCli,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Partial Trait version name specified",
|
||||
inputRevisionName: "scaler-trait-v1.2",
|
||||
definitionName: "scaler-trait",
|
||||
definitionType: "Trait",
|
||||
expectedDefRevisionName: "scaler-trait-v1.2.4",
|
||||
client: &traitListCli,
|
||||
err: nil,
|
||||
}, {
|
||||
name: "Trait version name not present",
|
||||
inputRevisionName: "scaler-trait-v1.5",
|
||||
definitionName: "scaler-trait",
|
||||
definitionType: "Trait",
|
||||
expectedDefRevisionName: "",
|
||||
client: &traitListCli,
|
||||
err: fmt.Errorf("error finding definition revision for Name: scaler-trait, Type: Trait"),
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, tc := range testcases {
|
||||
defRevisionName, err := util.GetLatestDefinitionRevisionName(ctx, tc.client, tc.definitionName, tc.inputRevisionName, common.DefinitionType(tc.definitionType))
|
||||
assert.Equal(t, defRevisionName, tc.expectedDefRevisionName)
|
||||
assert.Equal(t, err, tc.err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionComponentAutoUpdateEnabled(t *testing.T) {
|
||||
cli := test.MockClient{MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
|
||||
defRevisionList := getComponentDefRevisionList()
|
||||
defRevisionList.DeepCopyInto(list.(*v1beta1.DefinitionRevisionList))
|
||||
return nil
|
||||
}, MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
componentDefinitionRevision.DeepCopyInto(obj.(*v1beta1.DefinitionRevision))
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "true"
|
||||
definitionName := "configmap-component@v1"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.ComponentDefinition)
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, definition.Spec.Version, "1.0.0")
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionOfTraitAutoUpdateEnabled(t *testing.T) {
|
||||
cli := test.MockClient{MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
|
||||
defRevisionList := getTraitDefRevisionList()
|
||||
defRevisionList.DeepCopyInto(list.(*v1beta1.DefinitionRevisionList))
|
||||
return nil
|
||||
}, MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
traitDefinitionRevision.DeepCopyInto(obj.(*v1beta1.DefinitionRevision))
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "true"
|
||||
definitionName := "scaler-trait@v1"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.TraitDefinition)
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, definition.Spec.Version, "1.0.0")
|
||||
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionComponentAutoUpdateDisabled(t *testing.T) {
|
||||
cli := test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
componentDefinitionRevision.Spec.ComponentDefinition.DeepCopyInto(obj.(*v1beta1.ComponentDefinition))
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
definitionName := "configmap-component"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.ComponentDefinition)
|
||||
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, definition.Spec.Version, "1.0.0")
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionPolicyAutoUpdateDisabled(t *testing.T) {
|
||||
cli := test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
policydefinition := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mock-policy-definition",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
}
|
||||
policydefinition.DeepCopyInto(obj.(*v1beta1.PolicyDefinition))
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
definitionName := "mock-policy-definition"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.PolicyDefinition)
|
||||
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionWorkflowStepAutoUpdateDisabled(t *testing.T) {
|
||||
cli := test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
workflowStepDefinition := &v1beta1.WorkflowStepDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mock-workflow-definition",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
}
|
||||
workflowStepDefinition.DeepCopyInto(obj.(*v1beta1.WorkflowStepDefinition))
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
definitionName := "mock-workflow-definition"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.WorkflowStepDefinition)
|
||||
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionInvalidDefinition(t *testing.T) {
|
||||
cli := test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
workflowStepDefinition := &v1beta1.WorkloadDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mock-workload-definition",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
}
|
||||
workflowStepDefinition.DeepCopyInto(obj.(*v1beta1.WorkloadDefinition))
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
definitionName := "mock-workload-definition"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.WorkloadDefinition)
|
||||
definition.ObjectMeta.Name = "mock-workload-definition"
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err.Error(), "invalid definition type for mock-workload-definition")
|
||||
}
|
||||
|
||||
func TestGetCapabilityDefinitionOfTraitAutoUpdateDisabled(t *testing.T) {
|
||||
cli := test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
o := new(v1beta1.TraitDefinition)
|
||||
*o = traitDefinitionRevision.Spec.TraitDefinition
|
||||
return nil
|
||||
}}
|
||||
annotations := make(map[string]string)
|
||||
annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
definitionName := "scaler-trait"
|
||||
ctx := context.Background()
|
||||
definition := new(v1beta1.TraitDefinition)
|
||||
err := util.GetCapabilityDefinition(ctx, &cli, definition, definitionName, annotations)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
}
|
||||
|
||||
func getComponentDefRevisionList() v1beta1.DefinitionRevisionList {
|
||||
compDefRevision1 := componentDefinitionRevision.DeepCopy()
|
||||
compDefRevision1.Spec.ComponentDefinition.Spec.Version = "1.2.0"
|
||||
compDefRevision1.Name = "configmap-component-v1.2.0"
|
||||
|
||||
compDefRevision2 := componentDefinitionRevision.DeepCopy()
|
||||
compDefRevision2.Spec.ComponentDefinition.Spec.Version = "1.2.4"
|
||||
compDefRevision2.Name = "configmap-component-v1.2.4"
|
||||
|
||||
compDefRevision3 := componentDefinitionRevision.DeepCopy()
|
||||
compDefRevision3.Spec.ComponentDefinition.Spec.Version = "1.3.0"
|
||||
compDefRevision3.Name = "configmap-component-v1.3.0"
|
||||
|
||||
compRevisionList := v1beta1.DefinitionRevisionList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "DefinitionRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
Items: []v1beta1.DefinitionRevision{
|
||||
*compDefRevision1, *compDefRevision2, *compDefRevision3,
|
||||
},
|
||||
}
|
||||
return compRevisionList
|
||||
}
|
||||
|
||||
func getTraitDefRevisionList() v1beta1.DefinitionRevisionList {
|
||||
traitDefRevision1 := traitDefinitionRevision.DeepCopy()
|
||||
traitDefRevision1.Spec.TraitDefinition.Spec.Version = "1.2.0"
|
||||
traitDefRevision1.Name = "scaler-trait-v1.2.0"
|
||||
|
||||
traitDefRevision2 := traitDefinitionRevision.DeepCopy()
|
||||
traitDefRevision2.Spec.TraitDefinition.Spec.Version = "1.2.4"
|
||||
traitDefRevision2.Name = "scaler-trait-v1.2.4"
|
||||
|
||||
traitDefRevision3 := traitDefinitionRevision.DeepCopy()
|
||||
traitDefRevision3.Spec.TraitDefinition.Spec.Version = "1.3.0"
|
||||
traitDefRevision3.Name = "scaler-trait-v1.3.0"
|
||||
|
||||
traitRevisionList := v1beta1.DefinitionRevisionList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "DefinitionRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
Items: []v1beta1.DefinitionRevision{
|
||||
*traitDefRevision1, *traitDefRevision2, *traitDefRevision3,
|
||||
},
|
||||
}
|
||||
return traitRevisionList
|
||||
}
|
||||
|
||||
var traitDefinitionRevision = v1beta1.DefinitionRevision{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "DefinitionRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "scaler-trait",
|
||||
Labels: map[string]string{
|
||||
"trait.oam.dev/name": "scaler-trait",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.DefinitionRevisionSpec{
|
||||
Revision: 1,
|
||||
RevisionHash: "5ceecfbe58dde83a",
|
||||
DefinitionType: "Trait",
|
||||
TraitDefinition: v1beta1.TraitDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "TraitDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "scaler-trait",
|
||||
},
|
||||
Spec: v1beta1.TraitDefinitionSpec{
|
||||
Version: "1.0.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// debug unit tests
|
||||
var componentDefinitionRevision = v1beta1.DefinitionRevision{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "DefinitionRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-component",
|
||||
Labels: map[string]string{
|
||||
"componentdefinition.oam.dev/name": "configmap-component",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.DefinitionRevisionSpec{
|
||||
Revision: 1,
|
||||
RevisionHash: "5ceecfbe58dde83a",
|
||||
DefinitionType: "Component",
|
||||
ComponentDefinition: v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-component",
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Version: "1.0.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1beta1/componentdefinition"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1beta1/policydefinition"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1beta1/traitdefinition"
|
||||
"github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1beta1/workflowstepdefinition"
|
||||
)
|
||||
|
||||
// Register will be called in main and register all validation handlers
|
||||
@@ -35,6 +36,7 @@ func Register(mgr manager.Manager, args controller.Args) {
|
||||
componentdefinition.RegisterValidatingHandler(mgr)
|
||||
traitdefinition.RegisterValidatingHandler(mgr, args)
|
||||
policydefinition.RegisterValidatingHandler(mgr)
|
||||
workflowstepdefinition.RegisterValidatingHandler(mgr)
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/convert", conversion.NewWebhookHandler(mgr.GetScheme()))
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
if err := h.Decoder.Decode(req, app); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
if req.Namespace != "" {
|
||||
app.Namespace = req.Namespace
|
||||
}
|
||||
|
||||
ctx = util.SetNamespaceInCtx(ctx, app.Namespace)
|
||||
switch req.Operation {
|
||||
case admissionv1.Create:
|
||||
|
||||
@@ -207,4 +207,52 @@ var _ = Describe("Test Application Validator", func() {
|
||||
resp := handler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
})
|
||||
|
||||
It("Test Application with PublishVersion and Autoupdate annotations", func() {
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: metav1.GroupVersionResource{Group: "core.oam.dev", Version: "v1alpha2", Resource: "applications"},
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`
|
||||
{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"name":"workflow-timeout","namespace":"default","annotations":{"app.oam.dev/publishVersion":"v1.0.0","app.oam.dev/autoUpdate":"true"}},"spec":{"components":[{"name":"comp","type":"worker","properties":{"image":"crccheck/hello-world"}}],"workflow":{"steps":[{"name":"group","type":"suspend","timeout":"1s"}]}}}
|
||||
`),
|
||||
},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
})
|
||||
|
||||
It("Test Application Publishversion Annotation", func() {
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: metav1.GroupVersionResource{Group: "core.oam.dev", Version: "v1alpha2", Resource: "applications"},
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`
|
||||
{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"name":"workflow-timeout","namespace":"default","annotations":{"app.oam.dev/publishVersion":"v1.0.0"}},"spec":{"components":[{"name":"comp","type":"worker","properties":{"image":"crccheck/hello-world"}}],"workflow":{"steps":[{"name":"group","type":"suspend","timeout":"1s"}]}}}
|
||||
`),
|
||||
},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test Application Autoupdate Annotation", func() {
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: metav1.GroupVersionResource{Group: "core.oam.dev", Version: "v1alpha2", Resource: "applications"},
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`
|
||||
{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"name":"workflow-timeout","namespace":"default","annotations":{"app.oam.dev/autoUpdate":"true"}},"spec":{"components":[{"name":"comp","type":"worker","properties":{"image":"crccheck/hello-world"}}],"workflow":{"steps":[{"name":"group","type":"suspend","timeout":"1s"}]}}}
|
||||
`),
|
||||
},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// ValidateWorkflow validates the Application workflow
|
||||
@@ -107,10 +108,24 @@ func (h *ValidatingHandler) ValidateComponents(ctx context.Context, app *v1beta1
|
||||
return componentErrs
|
||||
}
|
||||
|
||||
// ValidateAnnotations validates whether the application has both autoupdate and publish version annotations
|
||||
func (h *ValidatingHandler) ValidateAnnotations(_ context.Context, app *v1beta1.Application) field.ErrorList {
|
||||
var annotationsErrs field.ErrorList
|
||||
|
||||
hasPublishVersion := app.Annotations[oam.AnnotationPublishVersion]
|
||||
hasAutoUpdate := app.Annotations[oam.AnnotationAutoUpdate]
|
||||
if hasAutoUpdate == "true" && hasPublishVersion != "" {
|
||||
annotationsErrs = append(annotationsErrs, field.Invalid(field.NewPath("metadata", "annotations"), app,
|
||||
"Application has both autoUpdate and publishVersion annotations. Only one can be present"))
|
||||
}
|
||||
return annotationsErrs
|
||||
}
|
||||
|
||||
// ValidateCreate validates the Application on creation
|
||||
func (h *ValidatingHandler) ValidateCreate(ctx context.Context, app *v1beta1.Application) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
|
||||
errs = append(errs, h.ValidateAnnotations(ctx, app)...)
|
||||
errs = append(errs, h.ValidateWorkflow(ctx, app)...)
|
||||
errs = append(errs, h.ValidateComponents(ctx, app)...)
|
||||
return errs
|
||||
|
||||
@@ -46,7 +46,7 @@ type ValidatingHandler struct {
|
||||
|
||||
var _ admission.Handler = &ValidatingHandler{}
|
||||
|
||||
// Handle validate component definition
|
||||
// Handle validate ComponentDefinition Spec here
|
||||
func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
obj := &v1beta1.ComponentDefinition{}
|
||||
if req.Resource.String() != componentDefGVR.String() {
|
||||
@@ -71,6 +71,13 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
}
|
||||
}
|
||||
|
||||
if obj.Spec.Version != "" {
|
||||
err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
|
||||
if len(revisionName) != 0 {
|
||||
defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
|
||||
@@ -79,6 +86,12 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
version := obj.Spec.Version
|
||||
err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
return admission.ValidationResponse(true, "")
|
||||
}
|
||||
|
||||
@@ -234,5 +234,172 @@ var _ = Describe("Test ComponentDefinition validating handler", func() {
|
||||
Expect(resp.Result.Reason).Should(Equal(metav1.StatusReason(http.StatusText(http.StatusForbidden))))
|
||||
Expect(resp.Result.Message).Should(ContainSubstring("hello: reference \"world\" not found"))
|
||||
})
|
||||
|
||||
It("Test Version field validation passed", func() {
|
||||
cd := v1beta1.ComponentDefinition{}
|
||||
cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
|
||||
cd.SetName("CorrectCd")
|
||||
cd.Spec = v1beta1.ComponentDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Type: "deployments.apps",
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
cdRaw, _ := json.Marshal(cd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: cdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test Version field validation failed", func() {
|
||||
wrongCd := v1beta1.ComponentDefinition{}
|
||||
wrongCd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
|
||||
wrongCd.SetName("wrongCd")
|
||||
wrongCd.Spec = v1beta1.ComponentDefinitionSpec{
|
||||
Version: "1.10..0",
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Type: "deployments.apps",
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
correctCdRaw, _ := json.Marshal(wrongCd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: correctCdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Not a valid version"))
|
||||
})
|
||||
|
||||
It("Test ComponentDefintion has both spec.version and revision name annotation", func() {
|
||||
wrongCd := v1beta1.ComponentDefinition{}
|
||||
wrongCd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
|
||||
wrongCd.SetName("wrongCd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "1.0.0",
|
||||
}
|
||||
wrongCd.SetAnnotations(annotations)
|
||||
wrongCd.SetNamespace("default")
|
||||
wrongCd.Spec = v1beta1.ComponentDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Type: "deployments.apps",
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongCdRaw, _ := json.Marshal(wrongCd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongCdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Only one can be present"))
|
||||
})
|
||||
|
||||
It("Test ComponentDefintion with spec.version and without revision name annotation", func() {
|
||||
cd := v1beta1.ComponentDefinition{}
|
||||
cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
|
||||
cd.SetName("cd")
|
||||
cd.Spec = v1beta1.ComponentDefinitionSpec{
|
||||
// Version: "1.10.0",
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Type: "deployments.apps",
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
cdRaw, _ := json.Marshal(cd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: cdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test ComponentDefintion with revision name annotation and wihout spec.version", func() {
|
||||
cd := v1beta1.ComponentDefinition{}
|
||||
cd.SetGroupVersionKind(v1beta1.ComponentDefinitionGroupVersionKind)
|
||||
cd.SetName("cd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "1.0.0",
|
||||
}
|
||||
cd.SetAnnotations(annotations)
|
||||
cd.SetNamespace("default")
|
||||
cd.Spec = v1beta1.ComponentDefinitionSpec{
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Type: "deployments.apps",
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
cdRaw, _ := json.Marshal(cd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: cdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -64,6 +64,13 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
}
|
||||
}
|
||||
|
||||
if obj.Spec.Version != "" {
|
||||
err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
|
||||
if len(revisionName) != 0 {
|
||||
defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
|
||||
@@ -72,6 +79,12 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
version := obj.Spec.Version
|
||||
err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
return admission.ValidationResponse(true, "")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ package policydefinition
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -29,6 +32,9 @@ import (
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
)
|
||||
|
||||
@@ -39,8 +45,10 @@ var decoder *admission.Decoder
|
||||
var pd v1beta1.PolicyDefinition
|
||||
var pdRaw []byte
|
||||
var scheme = runtime.NewScheme()
|
||||
var testEnv *envtest.Environment
|
||||
var validCueTemplate string
|
||||
var inValidCueTemplate string
|
||||
var cfg *rest.Config
|
||||
|
||||
func TestPolicydefinition(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
@@ -54,19 +62,37 @@ var _ = BeforeSuite(func() {
|
||||
|
||||
pd = v1beta1.PolicyDefinition{}
|
||||
pd.SetGroupVersionKind(v1beta1.PolicyDefinitionGroupVersionKind)
|
||||
|
||||
decoder = admission.NewDecoder(scheme)
|
||||
var err error
|
||||
var yamlPath string
|
||||
if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set {
|
||||
yamlPath = "../../../../../test/compatibility-test/testdata"
|
||||
} else {
|
||||
yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds")
|
||||
}
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{yamlPath},
|
||||
}
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
})
|
||||
|
||||
var _ = Describe("Test PolicyDefinition validating handler", func() {
|
||||
BeforeEach(func() {
|
||||
cli, err := client.New(cfg, client.Options{})
|
||||
Expect(err).Should(BeNil())
|
||||
reqResource = metav1.GroupVersionResource{
|
||||
Group: v1beta1.Group,
|
||||
Version: v1beta1.Version,
|
||||
Resource: "policydefinitions"}
|
||||
handler = ValidatingHandler{
|
||||
Decoder: decoder,
|
||||
Client: cli,
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
It("Test wrong resource of admission request", func() {
|
||||
@@ -138,5 +164,139 @@ var _ = Describe("Test PolicyDefinition validating handler", func() {
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
})
|
||||
|
||||
It("Test Version field validation passed", func() {
|
||||
pd := v1beta1.PolicyDefinition{}
|
||||
pd.SetGroupVersionKind(v1beta1.PolicyDefinitionGroupVersionKind)
|
||||
pd.SetName("CorrectPd")
|
||||
pd.Spec = v1beta1.PolicyDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
pdRaw, _ := json.Marshal(pd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: pdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test Version field validation failed", func() {
|
||||
wrongPd := v1beta1.PolicyDefinition{}
|
||||
wrongPd.SetGroupVersionKind(v1beta1.PolicyDefinitionGroupVersionKind)
|
||||
wrongPd.SetName("WrongPd")
|
||||
wrongPd.Spec = v1beta1.PolicyDefinitionSpec{
|
||||
Version: "1.10..0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongPdRaw, _ := json.Marshal(wrongPd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongPdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Not a valid version"))
|
||||
})
|
||||
|
||||
It("Test PolicyDefintion has both spec.version and revision name annotation", func() {
|
||||
wrongPd := v1beta1.PolicyDefinition{}
|
||||
|
||||
wrongPd.SetGroupVersionKind(v1beta1.PolicyDefinitionGroupVersionKind)
|
||||
wrongPd.SetName("wrongPd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "v1.0.0",
|
||||
}
|
||||
wrongPd.SetAnnotations(annotations)
|
||||
wrongPd.SetNamespace("default")
|
||||
wrongPd.Spec = v1beta1.PolicyDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongPdRaw, _ := json.Marshal(wrongPd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongPdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Only one can be present"))
|
||||
})
|
||||
|
||||
It("Test PolicyDefintion with spec.version and without revision name annotation", func() {
|
||||
pd := v1beta1.PolicyDefinition{}
|
||||
|
||||
pd.SetGroupVersionKind(v1beta1.PolicyDefinitionGroupVersionKind)
|
||||
pd.SetName("pd")
|
||||
pd.Spec = v1beta1.PolicyDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
pdRaw, _ := json.Marshal(pd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: pdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test PolicyDefintion without spec.version and with revision name annotation", func() {
|
||||
pd := v1beta1.PolicyDefinition{}
|
||||
|
||||
pd.SetGroupVersionKind(v1beta1.PolicyDefinitionGroupVersionKind)
|
||||
pd.SetName("pd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "v1.0.0",
|
||||
}
|
||||
pd.SetAnnotations(annotations)
|
||||
pd.SetNamespace("default")
|
||||
pd.Spec = v1beta1.PolicyDefinitionSpec{
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
pdRaw, _ := json.Marshal(pd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: pdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -97,6 +97,13 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
}
|
||||
}
|
||||
|
||||
if obj.Spec.Version != "" {
|
||||
err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
revisionName := obj.GetAnnotations()[oam.AnnotationDefinitionRevisionName]
|
||||
if len(revisionName) != 0 {
|
||||
defRevName := fmt.Sprintf("%s-v%s", obj.Name, revisionName)
|
||||
@@ -105,6 +112,12 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
version := obj.Spec.Version
|
||||
err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
klog.Info("validation passed ", " name: ", obj.Name, " operation: ", string(req.Operation))
|
||||
}
|
||||
return admission.ValidationResponse(true, "")
|
||||
@@ -113,7 +126,7 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
|
||||
// RegisterValidatingHandler will register TraitDefinition validation to webhook
|
||||
func RegisterValidatingHandler(mgr manager.Manager, _ controller.Args) {
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/validating-core-oam-dev-v1alpha2-traitdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
|
||||
server.Register("/validating-core-oam-dev-v1beta1-traitdefinitions", &webhook.Admission{Handler: &ValidatingHandler{
|
||||
Client: mgr.GetClient(),
|
||||
Decoder: admission.NewDecoder(mgr.GetScheme()),
|
||||
Validators: []TraitDefValidator{
|
||||
|
||||
@@ -20,7 +20,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -28,6 +31,9 @@ import (
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
@@ -41,8 +47,10 @@ var decoder *admission.Decoder
|
||||
var td v1beta1.TraitDefinition
|
||||
var tdRaw []byte
|
||||
var scheme = runtime.NewScheme()
|
||||
var testEnv *envtest.Environment
|
||||
var validCueTemplate string
|
||||
var inValidCueTemplate string
|
||||
var cfg *rest.Config
|
||||
|
||||
func TestTraitdefinition(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
@@ -58,16 +66,37 @@ var _ = BeforeSuite(func() {
|
||||
td.SetGroupVersionKind(v1beta1.TraitDefinitionGroupVersionKind)
|
||||
|
||||
decoder = admission.NewDecoder(scheme)
|
||||
var err error
|
||||
var yamlPath string
|
||||
if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set {
|
||||
yamlPath = "../../../../../test/compatibility-test/testdata"
|
||||
} else {
|
||||
yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds")
|
||||
}
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{yamlPath},
|
||||
}
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
decoder = admission.NewDecoder(scheme)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
var _ = Describe("Test TraitDefinition validating handler", func() {
|
||||
BeforeEach(func() {
|
||||
cli, err := client.New(cfg, client.Options{})
|
||||
Expect(err).Should(BeNil())
|
||||
reqResource = metav1.GroupVersionResource{
|
||||
Group: v1beta1.Group,
|
||||
Version: v1beta1.Version,
|
||||
Resource: "traitdefinitions"}
|
||||
|
||||
handler = ValidatingHandler{
|
||||
Decoder: decoder,
|
||||
Client: cli,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -181,5 +210,136 @@ var _ = Describe("Test TraitDefinition validating handler", func() {
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
})
|
||||
|
||||
It("Test Version field validation passed", func() {
|
||||
td := v1beta1.TraitDefinition{}
|
||||
td.SetGroupVersionKind(v1beta1.TraitDefinitionGroupVersionKind)
|
||||
td.SetName("Correcttd")
|
||||
td.Spec = v1beta1.TraitDefinitionSpec{
|
||||
Version: "1.10.1",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
tdRaw, _ := json.Marshal(td)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: tdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test Version validation failed", func() {
|
||||
wrongtd := v1beta1.TraitDefinition{}
|
||||
wrongtd.SetGroupVersionKind(v1beta1.TraitDefinitionGroupVersionKind)
|
||||
wrongtd.SetName("Wrongtd")
|
||||
wrongtd.Spec = v1beta1.TraitDefinitionSpec{
|
||||
Version: "a.b.c",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongtdRaw, _ := json.Marshal(wrongtd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongtdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Not a valid version"))
|
||||
})
|
||||
|
||||
It("Test TraitDefintion has both spec.version and revision name annotation", func() {
|
||||
wrongtd := v1beta1.TraitDefinition{}
|
||||
wrongtd.SetGroupVersionKind(v1beta1.TraitDefinitionGroupVersionKind)
|
||||
wrongtd.SetName("Wrongtd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "v1.0.0",
|
||||
}
|
||||
wrongtd.SetAnnotations(annotations)
|
||||
wrongtd.SetNamespace("default")
|
||||
wrongtd.Spec = v1beta1.TraitDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongtdRaw, _ := json.Marshal(wrongtd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongtdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Only one can be present"))
|
||||
})
|
||||
|
||||
It("Test TraitDefintion with spec.version and without revision name annotation", func() {
|
||||
td := v1beta1.TraitDefinition{}
|
||||
td.SetGroupVersionKind(v1beta1.TraitDefinitionGroupVersionKind)
|
||||
td.SetName("td")
|
||||
td.Spec = v1beta1.TraitDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
tdRaw, _ := json.Marshal(td)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: tdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test TraitDefintion without spec.version and with revision name annotation", func() {
|
||||
td := v1beta1.TraitDefinition{}
|
||||
td.SetGroupVersionKind(v1beta1.TraitDefinitionGroupVersionKind)
|
||||
td.SetName("td")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "v1.0.0",
|
||||
}
|
||||
td.SetAnnotations(annotations)
|
||||
td.SetNamespace("default")
|
||||
td.Spec = v1beta1.TraitDefinitionSpec{
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
tdRaw, _ := json.Marshal(td)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: tdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2024 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package workflowstepdefinition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
webhookutils "github.com/oam-dev/kubevela/pkg/webhook/utils"
|
||||
)
|
||||
|
||||
var workflowStepDefGVR = v1beta1.SchemeGroupVersion.WithResource("workflowstepdefinitions")
|
||||
|
||||
// ValidatingHandler handles validation of workflow step definition
|
||||
type ValidatingHandler struct {
|
||||
// Decoder decodes object
|
||||
Decoder *admission.Decoder
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
// InjectClient injects the client into the ValidatingHandler
|
||||
func (h *ValidatingHandler) InjectClient(c client.Client) error {
|
||||
h.Client = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// InjectDecoder injects the decoder into the ValidatingHandler
|
||||
func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
|
||||
h.Decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle validate WorkflowStepDefinition Spec here
|
||||
func (h *ValidatingHandler) Handle(_ context.Context, req admission.Request) admission.Response {
|
||||
obj := &v1beta1.WorkflowStepDefinition{}
|
||||
if req.Resource.String() != workflowStepDefGVR.String() {
|
||||
return admission.Errored(http.StatusBadRequest, fmt.Errorf("expect resource to be %s", workflowStepDefGVR))
|
||||
}
|
||||
|
||||
if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
|
||||
err := h.Decoder.Decode(req, obj)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if obj.Spec.Version != "" {
|
||||
err = webhookutils.ValidateSemanticVersion(obj.Spec.Version)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
revisionName := obj.Annotations[oam.AnnotationDefinitionRevisionName]
|
||||
version := obj.Spec.Version
|
||||
err = webhookutils.ValidateMultipleDefVersionsNotPresent(version, revisionName, obj.Kind)
|
||||
if err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
}
|
||||
return admission.ValidationResponse(true, "")
|
||||
}
|
||||
|
||||
// RegisterValidatingHandler will register WorkflowStepDefinition validation to webhook
|
||||
func RegisterValidatingHandler(mgr manager.Manager) {
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/validating-core-oam-dev-v1beta1-workflowstepdefinitions", &webhook.Admission{Handler: &ValidatingHandler{}})
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright 2024 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package workflowstepdefinition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
core "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
var handler ValidatingHandler
|
||||
var reqResource metav1.GroupVersionResource
|
||||
var decoder *admission.Decoder
|
||||
var td v1beta1.WorkflowStepDefinition
|
||||
var validCueTemplate string
|
||||
var inValidCueTemplate string
|
||||
var cfg *rest.Config
|
||||
var testScheme = runtime.NewScheme()
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestWorkflowStepDefinition(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Traitdefinition Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
|
||||
validCueTemplate = "{hello: 'world'}"
|
||||
inValidCueTemplate = "{hello: world}"
|
||||
|
||||
var yamlPath string
|
||||
if _, set := os.LookupEnv("COMPATIBILITY_TEST"); set {
|
||||
yamlPath = "../../../../../test/compatibility-test/testdata"
|
||||
} else {
|
||||
yamlPath = filepath.Join("../../../../..", "charts", "vela-core", "crds")
|
||||
}
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{yamlPath},
|
||||
}
|
||||
|
||||
err := core.AddToScheme(testScheme)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
decoder = admission.NewDecoder(testScheme)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
td = v1beta1.WorkflowStepDefinition{}
|
||||
td.SetGroupVersionKind(v1beta1.WorkflowStepDefinitionGroupVersionKind)
|
||||
})
|
||||
|
||||
var _ = Describe("Test workflowstepdefinition validating handler", func() {
|
||||
BeforeEach(func() {
|
||||
cli, err := client.New(cfg, client.Options{})
|
||||
Expect(err).Should(BeNil())
|
||||
reqResource = metav1.GroupVersionResource{
|
||||
Group: v1beta1.Group,
|
||||
Version: v1beta1.Version,
|
||||
Resource: "workflowstepdefinitions"}
|
||||
handler = ValidatingHandler{Client: cli}
|
||||
handler.InjectDecoder(decoder)
|
||||
})
|
||||
|
||||
Context("Test create/update operation admission request", func() {
|
||||
It("Test Version validation passed", func() {
|
||||
wsd := v1beta1.WorkflowStepDefinition{}
|
||||
wsd.SetGroupVersionKind(v1beta1.WorkflowStepDefinitionGroupVersionKind)
|
||||
wsd.SetName("Correctwsd")
|
||||
wsd.Spec = v1beta1.WorkflowStepDefinitionSpec{
|
||||
Reference: common.DefinitionReference{Name: "testname", Version: "1"},
|
||||
Version: "1.10.1",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wsdRaw, _ := json.Marshal(wsd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wsdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test Version validation passed", func() {
|
||||
wrongWsd := v1beta1.WorkflowStepDefinition{}
|
||||
wrongWsd.SetGroupVersionKind(v1beta1.WorkflowStepDefinitionGroupVersionKind)
|
||||
wrongWsd.SetName("wrongwsd")
|
||||
wrongWsd.Spec = v1beta1.WorkflowStepDefinitionSpec{
|
||||
Version: "1.B.1",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongWsdRaw, _ := json.Marshal(wrongWsd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongWsdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Not a valid version"))
|
||||
})
|
||||
|
||||
It("Test workflowstepdefinition has both spec.version and revision name annotation", func() {
|
||||
wrongWsd := v1beta1.WorkflowStepDefinition{}
|
||||
wrongWsd.SetGroupVersionKind(v1beta1.WorkflowStepDefinitionGroupVersionKind)
|
||||
wrongWsd.SetName("wrongwsd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "v1.0.0",
|
||||
}
|
||||
wrongWsd.SetAnnotations(annotations)
|
||||
wrongWsd.SetNamespace("default")
|
||||
wrongWsd.Spec = v1beta1.WorkflowStepDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongWsdRaw, _ := json.Marshal(wrongWsd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wrongWsdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(string(resp.Result.Message)).Should(ContainSubstring("Only one can be present"))
|
||||
})
|
||||
|
||||
It("Test workflowstepdefinition without spec.version and with revision name annotation", func() {
|
||||
wsd := v1beta1.WorkflowStepDefinition{}
|
||||
wsd.SetGroupVersionKind(v1beta1.WorkflowStepDefinitionGroupVersionKind)
|
||||
wsd.SetName("wsd")
|
||||
annotations := map[string]string{
|
||||
"definitionrevision.oam.dev/name": "v1.0.0",
|
||||
}
|
||||
wsd.SetAnnotations(annotations)
|
||||
wsd.SetNamespace("default")
|
||||
wsd.Spec = v1beta1.WorkflowStepDefinitionSpec{
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wsdRaw, _ := json.Marshal(wsd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wsdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Test workflowstepdefinition with spec.version and without revision name annotation", func() {
|
||||
wsd := v1beta1.WorkflowStepDefinition{}
|
||||
wsd.SetGroupVersionKind(v1beta1.WorkflowStepDefinitionGroupVersionKind)
|
||||
wsd.SetName("wsd")
|
||||
wsd.Spec = v1beta1.WorkflowStepDefinitionSpec{
|
||||
Version: "1.10.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: validCueTemplate,
|
||||
},
|
||||
},
|
||||
}
|
||||
wsdRaw, _ := json.Marshal(wsd)
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: reqResource,
|
||||
Object: runtime.RawExtension{Raw: wsdRaw},
|
||||
},
|
||||
}
|
||||
resp := handler.Handle(context.TODO(), req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -18,7 +18,9 @@ package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
@@ -41,7 +43,6 @@ func ValidateDefinitionRevision(ctx context.Context, cli client.Client, def runt
|
||||
if errs := validation.IsQualifiedName(defRevNamespacedName.Name); len(errs) != 0 {
|
||||
return errors.Errorf("invalid definitionRevision name %s:%s", defRevNamespacedName.Name, strings.Join(errs, ","))
|
||||
}
|
||||
|
||||
defRev := new(v1beta1.DefinitionRevision)
|
||||
if err := cli.Get(ctx, defRevNamespacedName, defRev); err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
@@ -84,3 +85,29 @@ func checkError(err error) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSemanticVersion validates if a Definition's version includes all of
|
||||
// major,minor & patch version values.
|
||||
func ValidateSemanticVersion(version string) error {
|
||||
if version != "" {
|
||||
versionParts := strings.Split(version, ".")
|
||||
if len(versionParts) != 3 {
|
||||
return errors.New("Not a valid version")
|
||||
}
|
||||
|
||||
for _, versionPart := range versionParts {
|
||||
if _, err := strconv.Atoi(versionPart); err != nil {
|
||||
return errors.New("Not a valid version")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateMultipleDefVersionsNotPresent validates that both Name Annotation Revision and Spec.Version are not present
|
||||
func ValidateMultipleDefVersionsNotPresent(version, revisionName, objectType string) error {
|
||||
if version != "" && revisionName != "" {
|
||||
return fmt.Errorf("%s has both spec.version and revision name annotation. Only one can be present", objectType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@ limitations under the License.
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue/errors"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateCueTemplate(t *testing.T) {
|
||||
@@ -67,3 +69,69 @@ func TestValidateCueTemplate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSemanticVersion(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
version string
|
||||
want error
|
||||
}{
|
||||
"validVersion": {
|
||||
version: "1.2.3",
|
||||
want: nil,
|
||||
},
|
||||
"versionWithAlphabets": {
|
||||
version: "1.2.3-alpha",
|
||||
want: errors.New("Not a valid version"),
|
||||
},
|
||||
"invalidVersion": {
|
||||
version: "1.2",
|
||||
want: errors.New("Not a valid version"),
|
||||
},
|
||||
}
|
||||
for caseName, cs := range cases {
|
||||
t.Run(caseName, func(t *testing.T) {
|
||||
err := ValidateSemanticVersion(cs.version)
|
||||
if cs.want != nil {
|
||||
assert.Equal(t, err.Error(), cs.want.Error())
|
||||
} else {
|
||||
assert.Equal(t, err, cs.want)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMultipleDefVersionsNotPresent(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
version string
|
||||
revisionName string
|
||||
want error
|
||||
}{
|
||||
"versionPresent": {
|
||||
version: "1.2.3",
|
||||
revisionName: "",
|
||||
want: nil,
|
||||
},
|
||||
"revisionNamePresent": {
|
||||
version: "",
|
||||
revisionName: "2.3",
|
||||
want: nil,
|
||||
},
|
||||
"versionAndRevisionNamePresent": {
|
||||
version: "1.2.3",
|
||||
revisionName: "2.3",
|
||||
want: fmt.Errorf("ComponentDefinition has both spec.version and revision name annotation. Only one can be present"),
|
||||
},
|
||||
}
|
||||
for caseName, cs := range cases {
|
||||
t.Run(caseName, func(t *testing.T) {
|
||||
err := ValidateMultipleDefVersionsNotPresent(cs.version, cs.revisionName, "ComponentDefinition")
|
||||
if cs.want != nil {
|
||||
assert.Equal(t, err.Error(), cs.want.Error())
|
||||
} else {
|
||||
assert.Equal(t, err, cs.want)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,10 @@ func (loader *WorkflowStepLoader) LoadTemplate(ctx context.Context, name string)
|
||||
}
|
||||
|
||||
// NewWorkflowStepTemplateLoader create a task template loader.
|
||||
func NewWorkflowStepTemplateLoader(client client.Client) template.Loader {
|
||||
func NewWorkflowStepTemplateLoader(client client.Client, annotations map[string]string) template.Loader {
|
||||
return &WorkflowStepLoader{
|
||||
loadCapabilityDefinition: func(ctx context.Context, capName string) (*appfile.Template, error) {
|
||||
return appfile.LoadTemplate(ctx, client, capName, types.TypeWorkflowStep)
|
||||
return appfile.LoadTemplate(ctx, client, capName, types.TypeWorkflowStep, annotations)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestLoad(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
loader := NewWorkflowStepTemplateLoader(cli)
|
||||
loader := NewWorkflowStepTemplateLoader(cli, make(map[string]string))
|
||||
|
||||
tmpl, err := loader.LoadTemplate(context.Background(), "builtin-apply-component")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
wfv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
@@ -272,6 +274,11 @@ var _ = Describe("Testing dry-run", func() {
|
||||
c := common2.Args{}
|
||||
c.SetConfig(cfg)
|
||||
c.SetClient(k8sClient)
|
||||
|
||||
ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: appNamespace}}
|
||||
err := k8sClient.Create(context.Background(), &ns)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-1.yaml"}, OfflineMode: false}
|
||||
buff, err := DryRunApplication(&opt, c, appNamespace)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
@@ -199,6 +199,9 @@ func (o *LiveDiffCmdOptions) renderlessDiff(cli client.Client, option *dryrun.Li
|
||||
if err := cli.Get(ctx, client.ObjectKey{Name: o.AppName, Namespace: o.Namespace}, app); err != nil {
|
||||
return buf, errors.Wrapf(err, "cannot get application %s/%s", o.Namespace, o.AppName)
|
||||
}
|
||||
if app.Namespace == "" {
|
||||
app.SetNamespace(o.Namespace)
|
||||
}
|
||||
base = dryrun.LiveDiffObject{Application: app}
|
||||
if o.Revision == "" {
|
||||
if app.Status.LatestRevision == nil {
|
||||
@@ -211,12 +214,18 @@ func (o *LiveDiffCmdOptions) renderlessDiff(cli client.Client, option *dryrun.Li
|
||||
if err := cli.Get(ctx, client.ObjectKey{Name: o.Revision, Namespace: o.Namespace}, rev); err != nil {
|
||||
return buf, errors.Wrapf(err, "cannot get application revision %s/%s", o.Namespace, o.Revision)
|
||||
}
|
||||
if rev.Namespace == "" {
|
||||
rev.SetNamespace(o.Namespace)
|
||||
}
|
||||
if o.SecondaryRevision == "" {
|
||||
comparor = dryrun.LiveDiffObject{ApplicationRevision: rev}
|
||||
} else {
|
||||
if err := cli.Get(ctx, client.ObjectKey{Name: o.SecondaryRevision, Namespace: o.Namespace}, secondaryRev); err != nil {
|
||||
return buf, errors.Wrapf(err, "cannot get application revision %s/%s", o.Namespace, o.SecondaryRevision)
|
||||
}
|
||||
if secondaryRev.Namespace == "" {
|
||||
secondaryRev.SetNamespace(o.Namespace)
|
||||
}
|
||||
base = dryrun.LiveDiffObject{ApplicationRevision: rev}
|
||||
comparor = dryrun.LiveDiffObject{ApplicationRevision: secondaryRev}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
/*
|
||||
Copyright 2024 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Application AutoUpdate", func() {
|
||||
ctx := context.Background()
|
||||
var namespace string
|
||||
var ns corev1.Namespace
|
||||
var reconcileSleepTime = 70 * time.Second
|
||||
var sleepTime = 5 * time.Second
|
||||
|
||||
BeforeEach(func() {
|
||||
By("Create namespace for app-autoupdate-e2e-test")
|
||||
namespace = randomNamespaceName("app-autoupdate-e2e-test")
|
||||
ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
|
||||
Eventually(func() error {
|
||||
return k8sClient.Create(ctx, &ns)
|
||||
}, time.Second*3, time.Microsecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
By("Clean up resources after a test")
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace))
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace))
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace))
|
||||
k8sClient.DeleteAllOf(ctx, &v1beta1.DefinitionRevision{}, client.InNamespace(namespace))
|
||||
Expect(k8sClient.Delete(ctx, &ns)).Should(BeNil())
|
||||
})
|
||||
|
||||
Context("Enabled", func() {
|
||||
It("When specified exact component version available. App should use exact specified version.", func() {
|
||||
By("Create configmap-component with 1.0.0 version")
|
||||
componentVersion := "1.0.0"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
|
||||
By("Create configmap-component with 1.2.0 version")
|
||||
updatedComponent := new(v1beta1.ComponentDefinition)
|
||||
updatedComponentVersion := "1.2.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: componentType, Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
updatedComponent.Spec.Schematic.CUE.Template = createOutputConfigMap(updatedComponentVersion)
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@1.2.0")
|
||||
app := updateAppComponent(appTemplate, "app1", namespace, componentType, "first-component", componentVersion)
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
cm := new(corev1.ConfigMap)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(componentVersion))
|
||||
|
||||
By("Create configmap-component with 1.4.0 version")
|
||||
updatedComponentVersion = "1.4.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: componentType, Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
updatedComponent.Spec.Schematic.CUE.Template = createOutputConfigMap(componentVersion)
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Wait for application to reconcile")
|
||||
time.Sleep(reconcileSleepTime)
|
||||
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(componentVersion))
|
||||
|
||||
})
|
||||
|
||||
It("When speicified component version is unavailable. App should use latest version in specified range.", func() {
|
||||
By("Create configmap-component with 1.4.5 version")
|
||||
componentVersion := "1.4.5"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@1.4")
|
||||
app := updateAppComponent(appTemplate, "app1", namespace, componentType, "first-component", "1.4")
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
cm := new(corev1.ConfigMap)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(componentVersion))
|
||||
|
||||
})
|
||||
|
||||
It("When new component version release after app creation, app should use new version during reconciliation", func() {
|
||||
By("Create configmap-component with 2.2.0 version")
|
||||
componentVersion := "2.2.0"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
|
||||
By("Create configmap-component with 2.3.0 version")
|
||||
updatedComponent := new(v1beta1.ComponentDefinition)
|
||||
updatedComponentVersion := "2.3.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: componentType, Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
updatedComponent.Spec.Schematic.CUE.Template = createOutputConfigMap(updatedComponentVersion)
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@2")
|
||||
app := updateAppComponent(appTemplate, "app1", namespace, componentType, "first-component", "2")
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
cm := new(corev1.ConfigMap)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(updatedComponentVersion))
|
||||
|
||||
By("Create configmap-component with 2.4.0 version")
|
||||
updatedComponentVersion = "2.4.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: componentType, Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
updatedComponent.Spec.Schematic.CUE.Template = createOutputConfigMap(updatedComponentVersion)
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Wait for application to reconcile")
|
||||
time.Sleep(reconcileSleepTime)
|
||||
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(updatedComponentVersion))
|
||||
|
||||
})
|
||||
|
||||
It("When speicified version is available for one component and unavailable for other, app should use autoupdate the latter", func() {
|
||||
By("Create configmap-component with 1.4.5 version")
|
||||
componentVersion := "1.4.5"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@1.4 version")
|
||||
app := updateAppComponent(appWithTwoComponentTemplate, "app1", namespace, componentType, "first-component", "1.4")
|
||||
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
cm := new(corev1.ConfigMap)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(componentVersion))
|
||||
|
||||
pods := new(corev1.PodList)
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(1))
|
||||
|
||||
})
|
||||
|
||||
It("When specified exact trait version available, app should use exact version", func() {
|
||||
By("Create scaler-trait with 1.0.0 version and 1 replica")
|
||||
traitVersion := "1.0.0"
|
||||
traitType := "scaler-trait"
|
||||
trait := createTrait(traitVersion, namespace, traitType, "1")
|
||||
trait.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(ctx, trait)).Should(Succeed())
|
||||
|
||||
By("Create scaler-trait with 1.2.0 version and 2 replicas")
|
||||
updatedTrait := new(v1beta1.TraitDefinition)
|
||||
updatedTraitVersion := "1.2.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: traitType, Namespace: namespace}, updatedTrait)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedTrait.Spec.Version = updatedTraitVersion
|
||||
updatedTrait.Spec.Schematic.CUE.Template = createScalerTraitOutput("2")
|
||||
return k8sClient.Update(ctx, updatedTrait)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
app := updateAppTrait(traitApp, "app1", namespace, traitType, updatedTraitVersion)
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
pods := new(corev1.PodList)
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(2))
|
||||
|
||||
By("Create scaler-trait with 1.4.0 version")
|
||||
updatedTraitVersion = "1.4.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: traitType, Namespace: namespace}, updatedTrait)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedTrait.Spec.Version = updatedTraitVersion
|
||||
updatedTrait.Spec.Schematic.CUE.Template = createScalerTraitOutput("3")
|
||||
return k8sClient.Update(ctx, updatedTrait)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Wait for application to reconcile")
|
||||
time.Sleep(reconcileSleepTime)
|
||||
pods = new(corev1.PodList)
|
||||
opts = []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(2))
|
||||
})
|
||||
|
||||
It("When specified trait version is unavailable, app should use latest version specified in range", func() {
|
||||
By("Create scaler-trait with 1.4.5 version and 4 replica")
|
||||
traitVersion := "1.4.5"
|
||||
traitType := "scaler-trait"
|
||||
|
||||
trait := createTrait(traitVersion, namespace, traitType, "4")
|
||||
Expect(k8sClient.Create(ctx, trait)).Should(Succeed())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using scaler-trait@v1.4")
|
||||
app := updateAppTrait(traitApp, "app1", namespace, traitType, "1.4")
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
pods := new(corev1.PodList)
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(4))
|
||||
})
|
||||
|
||||
It("When new trait version is created after app creation, app should use new version during reconciliation", func() {
|
||||
By("Create scaler-trait with 1.4.5 version and 4 replica")
|
||||
traitVersion := "1.4.5"
|
||||
traitType := "scaler-trait"
|
||||
|
||||
trait := createTrait(traitVersion, namespace, traitType, "4")
|
||||
Expect(k8sClient.Create(ctx, trait)).Should(Succeed())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using scaler-trait@v1.4")
|
||||
app := updateAppTrait(traitApp, "app1", namespace, traitType, "1.4")
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
pods := new(corev1.PodList)
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(4))
|
||||
|
||||
By("Create scaler-trait with 1.4.8 version and 2 replicas")
|
||||
updatedTrait := new(v1beta1.TraitDefinition)
|
||||
updatedTraitVersion := "1.4.8"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: traitType, Namespace: namespace}, updatedTrait)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedTrait.Spec.Version = updatedTraitVersion
|
||||
updatedTrait.Spec.Schematic.CUE.Template = createScalerTraitOutput("2")
|
||||
return k8sClient.Update(ctx, updatedTrait)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
|
||||
By("Wait for application to reconcile")
|
||||
time.Sleep(reconcileSleepTime)
|
||||
pods = new(corev1.PodList)
|
||||
opts = []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(2))
|
||||
|
||||
})
|
||||
|
||||
It("When Autoupdate and Publish version annotation are specified in application, app creation should fail", func() {
|
||||
By("Create configmap-component with 1.4.5 version")
|
||||
componentVersion := "1.4.5"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@1.4.5")
|
||||
app := updateAppComponent(appTemplate, "app1", namespace, componentType, "first-component", "1.4.5")
|
||||
app.ObjectMeta.Annotations[oam.AnnotationPublishVersion] = "alpha"
|
||||
err := k8sClient.Create(ctx, app)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
Expect(err.Error()).Should(ContainSubstring("Application has both autoUpdate and publishVersion annotations. Only one can be present"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Context("Disabled", func() {
|
||||
It("When specified component version is available, app should use specified version", func() {
|
||||
By("Create configmap-component with 1.0.0 version")
|
||||
componentVersion := "1.0.0"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
|
||||
By("Create configmap-component with 1.2.0 version")
|
||||
updatedComponent := new(v1beta1.ComponentDefinition)
|
||||
updatedComponentVersion := "1.2.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: componentType, Namespace: namespace}, updatedComponent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedComponent.Spec.Version = updatedComponentVersion
|
||||
updatedComponent.Spec.Schematic.CUE.Template = createOutputConfigMap(componentVersion)
|
||||
return k8sClient.Update(ctx, updatedComponent)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@1.0.0 version")
|
||||
app := updateAppComponent(appTemplate, "app1", namespace, componentType, "first-component", componentVersion)
|
||||
app.ObjectMeta.Annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
cm := new(corev1.ConfigMap)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
Expect(cm.Data["expectedVersion"]).To(BeEquivalentTo(componentVersion))
|
||||
})
|
||||
|
||||
It("When specified component version is unavailable, app creation should fail", func() {
|
||||
By("Create configmap-component with 1.2.0 version")
|
||||
componentVersion := "1.2.0"
|
||||
componentType := "configmap-component"
|
||||
component := createComponent(componentVersion, namespace, componentType)
|
||||
Expect(k8sClient.Create(ctx, component)).Should(Succeed())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using configmap-component@1 version")
|
||||
app := updateAppComponent(appTemplate, "app1", namespace, componentType, "first-component", "1")
|
||||
app.ObjectMeta.Annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
Expect(k8sClient.Create(ctx, app)).ShouldNot(Succeed())
|
||||
cm := new(corev1.ConfigMap)
|
||||
time.Sleep(reconcileSleepTime)
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "comptest", Namespace: namespace}, cm)).ShouldNot(BeNil())
|
||||
|
||||
configmaps := new(corev1.ConfigMapList)
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.List(ctx, configmaps, opts...)).To(BeNil())
|
||||
Expect(len(configmaps.Items)).To(BeEquivalentTo(0))
|
||||
})
|
||||
|
||||
It("When specified trait version is available, app should specified trait version", func() {
|
||||
By("Create scaler-trait with 1.0.0 version and 1 replica")
|
||||
traitVersion := "1.0.0"
|
||||
traitType := "scaler-trait"
|
||||
trait := createTrait(traitVersion, namespace, traitType, "1")
|
||||
Expect(k8sClient.Create(ctx, trait)).Should(Succeed())
|
||||
|
||||
By("Create scaler-trait with 1.2.0 version and 2 replica")
|
||||
updatedTrait := new(v1beta1.TraitDefinition)
|
||||
updatedTraitVersion := "1.2.0"
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: traitType, Namespace: namespace}, updatedTrait)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedTrait.Spec.Version = updatedTraitVersion
|
||||
updatedTrait.Spec.Schematic.CUE.Template = createScalerTraitOutput("2")
|
||||
return k8sClient.Update(ctx, updatedTrait)
|
||||
}, 15*time.Second, time.Second).Should(BeNil())
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
By("Create application using scaler-trait@1.0.0 version")
|
||||
app := updateAppTrait(traitApp, "app1", namespace, traitType, traitVersion)
|
||||
app.ObjectMeta.Annotations[oam.AnnotationAutoUpdate] = "false"
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
|
||||
By("Wait for application to be created")
|
||||
time.Sleep(reconcileSleepTime)
|
||||
pods := new(corev1.PodList)
|
||||
opts := []client.ListOption{
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: "app1",
|
||||
},
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
Expect(k8sClient.List(ctx, pods, opts...)).To(BeNil())
|
||||
Expect(len(pods.Items)).To(BeEquivalentTo(1))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// TODO Add test cases for policydefinition and worflowstepdefinition
|
||||
|
||||
func updateAppComponent(appTemplate v1beta1.Application, appName, namespace, typeName, componentName, componentVersion string) *v1beta1.Application {
|
||||
app := appTemplate.DeepCopy()
|
||||
app.ObjectMeta.Name = appName
|
||||
app.SetNamespace(namespace)
|
||||
app.Spec.Components[0].Type = fmt.Sprintf("%s@v%s", typeName, componentVersion)
|
||||
|
||||
app.Spec.Components[0].Name = componentName
|
||||
return app
|
||||
}
|
||||
|
||||
func updateAppTrait(traitApp v1beta1.Application, appName, namespace, typeName, traitVersion string) *v1beta1.Application {
|
||||
app := traitApp.DeepCopy()
|
||||
app.ObjectMeta.Name = appName
|
||||
app.SetNamespace(namespace)
|
||||
app.Spec.Components[0].Traits[0].Type = fmt.Sprintf("%s@v%s", typeName, traitVersion)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func createComponent(componentVersion, namespace, name string) *v1beta1.ComponentDefinition {
|
||||
component := configMapComponent.DeepCopy()
|
||||
component.ObjectMeta.Name = name
|
||||
component.Spec.Version = componentVersion
|
||||
component.Spec.Schematic.CUE.Template = createOutputConfigMap(componentVersion)
|
||||
component.SetNamespace(namespace)
|
||||
return component
|
||||
}
|
||||
|
||||
func createTrait(traitVersion, namespace, name, replicas string) *v1beta1.TraitDefinition {
|
||||
trait := scalerTrait.DeepCopy()
|
||||
trait.ObjectMeta.Name = name
|
||||
trait.Spec.Version = traitVersion
|
||||
trait.Spec.Schematic.CUE.Template = createScalerTraitOutput(replicas)
|
||||
trait.SetNamespace(namespace)
|
||||
return trait
|
||||
}
|
||||
|
||||
func createScalerTraitOutput(replicas string) string {
|
||||
return strings.Replace(scalerTraitOutputTemplate, "1", replicas, 1)
|
||||
}
|
||||
|
||||
func createOutputConfigMap(toVersion string) string {
|
||||
return strings.Replace(configMapOutputTemplate, "1.0.0", toVersion, 1)
|
||||
}
|
||||
|
||||
var configMapComponent = &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "configmap-component",
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Version: "1.0.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var configMapOutputTemplate = `output: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: name: "comptest"
|
||||
data: {
|
||||
expectedVersion: "1.0.0"
|
||||
}
|
||||
}`
|
||||
|
||||
var appTemplate = v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Name",
|
||||
Namespace: "Namespace",
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationAutoUpdate: "true",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "comp1Name",
|
||||
Type: "type",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var appWithTwoComponentTemplate = v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Name",
|
||||
Namespace: "Namespace",
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationAutoUpdate: "true",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "first-component",
|
||||
Type: "configmap-component",
|
||||
},
|
||||
{
|
||||
Name: "second-component",
|
||||
Type: "webservice@v1",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "nginx",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var scalerTraitOutputTemplate = `patch: spec: replicas: 1`
|
||||
|
||||
var scalerTrait = &v1beta1.TraitDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "TraitDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "scaler-trait",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1beta1.TraitDefinitionSpec{
|
||||
Version: "1.0.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var traitApp = v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-trait",
|
||||
Namespace: "Namespace",
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationAutoUpdate: "true",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "webservice-component",
|
||||
Type: "webservice",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"image": "busybox"}`)},
|
||||
Traits: []common.ApplicationTrait{
|
||||
{
|
||||
Type: "scaler-trait",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -122,6 +122,79 @@ var _ = Describe("Test application of the specified definition version", func()
|
||||
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test tries to deploy component which has both spec.version and revision name annotation", func() {
|
||||
workerV1 := workerWithNoTemplate.DeepCopy()
|
||||
workerV1.Spec.Workload = common.WorkloadTypeDescriptor{
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "batch/v1",
|
||||
Kind: "Job",
|
||||
},
|
||||
}
|
||||
workerV1.ObjectMeta.Annotations[oam.AnnotationDefinitionRevisionName] = "1.0.0"
|
||||
workerV1.Spec.Version = "1.0.0"
|
||||
workerV1.Spec.Schematic.CUE.Template = workerV1Template
|
||||
workerV1.SetNamespace(namespace)
|
||||
|
||||
Expect(k8sClient.Create(ctx, workerV1)).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
It("Test tries to deploy component which has spec.version and but no revision name annotation", func() {
|
||||
workerV1 := workerWithNoTemplate.DeepCopy()
|
||||
workerV1.Spec.Workload = common.WorkloadTypeDescriptor{
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "batch/v1",
|
||||
Kind: "Job",
|
||||
},
|
||||
}
|
||||
workerV1.Spec.Version = "1.0.0"
|
||||
workerV1.Spec.Schematic.CUE.Template = workerV1Template
|
||||
workerV1.SetNamespace(namespace)
|
||||
|
||||
Expect(k8sClient.Create(ctx, workerV1)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test tries to deploy trait which has both spec.version and revision name annotation", func() {
|
||||
traitV1 := scalerTrait.DeepCopy()
|
||||
|
||||
traitV1.Spec.Schematic.CUE.Template = scalerTraitOutputTemplate
|
||||
traitV1.ObjectMeta.Annotations[oam.AnnotationDefinitionRevisionName] = "1.0.0"
|
||||
// traitV1.Spec.Version = "1.0.0"
|
||||
traitV1.SetNamespace(namespace)
|
||||
|
||||
Expect(k8sClient.Create(ctx, traitV1)).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
It("Test tries to deploy trait which has spec.version and but no revision name annotation", func() {
|
||||
traitV1 := scalerTrait.DeepCopy()
|
||||
|
||||
traitV1.Spec.Schematic.CUE.Template = scalerTraitOutputTemplate
|
||||
traitV1.Spec.Version = "1.0.0"
|
||||
traitV1.SetNamespace(namespace)
|
||||
|
||||
Expect(k8sClient.Create(ctx, traitV1)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test tries to deploy policy which has both spec.version and revision name annotation", func() {
|
||||
policyV1 := policyDef.DeepCopy()
|
||||
|
||||
policyV1.ObjectMeta.Annotations[oam.AnnotationDefinitionRevisionName] = "1.0.0"
|
||||
policyV1.Spec.Version = "1.0.0"
|
||||
policyV1.Spec.Schematic.CUE.Template = workerV1Template
|
||||
policyV1.SetNamespace(namespace)
|
||||
|
||||
Expect(k8sClient.Create(ctx, policyV1)).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
It("Test tries to deploy policy which has spec.version and but no revision name annotation", func() {
|
||||
policyV1 := policyDef.DeepCopy()
|
||||
|
||||
policyV1.Spec.Version = "1.0.0"
|
||||
policyV1.Spec.Schematic.CUE.Template = workerV1Template
|
||||
policyV1.SetNamespace(namespace)
|
||||
|
||||
Expect(k8sClient.Create(ctx, policyV1)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test deploy application which containing cue rendering module", func() {
|
||||
var (
|
||||
appName = "test-website-app"
|
||||
@@ -465,7 +538,8 @@ var workerWithNoTemplate = &v1beta1.ComponentDefinition{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "worker",
|
||||
Name: "worker",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Schematic: &common.Schematic{
|
||||
@@ -502,6 +576,27 @@ var jobComponentDef = &v1beta1.ComponentDefinition{
|
||||
},
|
||||
}
|
||||
|
||||
var policyDefOutputTemplate = `properties: enable: true`
|
||||
|
||||
var policyDef = &v1beta1.PolicyDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PolicyDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "policy-apply-once",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Version: "1.0.0",
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: policyDefOutputTemplate,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var KUBEWorker = &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
|
||||
Reference in New Issue
Block a user