implemente kube schematic (#1298)

* implemente kube schematic

generate schema for kube schematic parameters

add e2e test

Signed-off-by: roywang <seiwy2010@gmail.com>

* add unit test

Signed-off-by: roy wang <seiwy2010@gmail.com>

* add doc

Signed-off-by: roy wang <seiwy2010@gmail.com>
This commit is contained in:
Yue Wang
2021-03-27 00:02:58 +09:00
committed by GitHub
parent 1c89435515
commit baa8d87e71
32 changed files with 2158 additions and 36 deletions

View File

@@ -23,6 +23,51 @@ import (
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
)
// Kube defines the encapsulation in raw Kubernetes resource format
type Kube struct {
// Template defines the raw Kubernetes resource
// +kubebuilder:pruning:PreserveUnknownFields
Template runtime.RawExtension `json:"template"`
// Parameters defines configurable parameters
Parameters []KubeParameter `json:"parameters,omitempty"`
}
// ParameterValueType refers to a data type of parameter
type ParameterValueType string
// data types of parameter value
const (
StringType ParameterValueType = "string"
NumberType ParameterValueType = "number"
BooleanType ParameterValueType = "boolean"
)
// A KubeParameter defines a configurable parameter of a component.
type KubeParameter struct {
// Name of this parameter
Name string `json:"name"`
// +kubebuilder:validation:Enum:=string;number;boolean
// ValueType indicates the type of the parameter value, and
// only supports basic data types: string, number, boolean.
ValueType ParameterValueType `json:"type"`
// FieldPaths specifies an array of fields within this workload that will be
// overwritten by the value of this parameter. All fields must be of the
// same type. Fields are specified as JSON field paths without a leading
// dot, for example 'spec.replicas'.
FieldPaths []string `json:"fieldPaths"`
// +kubebuilder:default:=false
// Required specifies whether or not a value for this parameter must be
// supplied when authoring an Application.
Required *bool `json:"required,omitempty"`
// Description of this parameter.
Description *string `json:"description,omitempty"`
}
// CUE defines the encapsulation in CUE format
type CUE struct {
// Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field.
@@ -33,11 +78,13 @@ type CUE struct {
// Schematic defines the encapsulation of this capability(workload/trait/scope),
// the encapsulation can be defined in different ways, e.g. CUE/HCL(terraform)/KUBE(K8s Object)/HELM, etc...
type Schematic struct {
KUBE *Kube `json:"kube,omitempty"`
CUE *CUE `json:"cue,omitempty"`
HELM *Helm `json:"helm,omitempty"`
// TODO(wonderflow): support HCL(terraform)/KUBE(K8s Object) here.
// TODO(wonderflow): support HCL(terraform)here.
}
// A Helm represents resources used by a Helm module

View File

@@ -161,6 +161,59 @@ func (in *Helm) DeepCopy() *Helm {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Kube) DeepCopyInto(out *Kube) {
*out = *in
in.Template.DeepCopyInto(&out.Template)
if in.Parameters != nil {
in, out := &in.Parameters, &out.Parameters
*out = make([]KubeParameter, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kube.
func (in *Kube) DeepCopy() *Kube {
if in == nil {
return nil
}
out := new(Kube)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeParameter) DeepCopyInto(out *KubeParameter) {
*out = *in
if in.FieldPaths != nil {
in, out := &in.FieldPaths, &out.FieldPaths
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Required != nil {
in, out := &in.Required, &out.Required
*out = new(bool)
**out = **in
}
if in.Description != nil {
in, out := &in.Description, &out.Description
*out = new(string)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeParameter.
func (in *KubeParameter) DeepCopy() *KubeParameter {
if in == nil {
return nil
}
out := new(KubeParameter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RawComponent) DeepCopyInto(out *RawComponent) {
*out = *in
@@ -195,6 +248,11 @@ func (in *Revision) DeepCopy() *Revision {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Schematic) DeepCopyInto(out *Schematic) {
*out = *in
if in.KUBE != nil {
in, out := &in.KUBE, &out.KUBE
*out = new(Kube)
(*in).DeepCopyInto(*out)
}
if in.CUE != nil {
in, out := &in.CUE, &out.CUE
*out = new(CUE)

View File

@@ -101,11 +101,15 @@ const (
// CapabilityCategory defines the category of a capability
type CapabilityCategory string
// categories of capability schematic
const (
// TerraformCategory means the capability is in Terraform format
TerraformCategory CapabilityCategory = "terraform"
// HelmCategory means the capability is a helm capability
HelmCategory CapabilityCategory = "helm"
KubeCategory CapabilityCategory = "kube"
CUECategory CapabilityCategory = "cue"
)
// Parameter defines a parameter for cli from capability template

View File

@@ -518,6 +518,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload
@@ -715,6 +758,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for trait
@@ -853,6 +939,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload
@@ -1412,6 +1541,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload
@@ -1610,6 +1782,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for trait
@@ -1748,6 +1963,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload

View File

@@ -91,6 +91,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload
@@ -237,6 +280,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload

View File

@@ -93,6 +93,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for trait
@@ -224,6 +267,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for trait

View File

@@ -107,6 +107,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload
@@ -247,6 +290,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
default: false
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload

View File

@@ -26,6 +26,10 @@
- [Define Components in Chart](/en/helm/component.md)
- [Attach Traits](/en/helm/trait.md)
- [Known Limitations](/en/helm/known-issues.md)
- Using Raw Kube
- [Define Components With Raw K8s](/en/kube/component.md)
- [Attach Traits](/en/kube/trait.md)
- Developer Experience Guide
- Appfile

View File

@@ -64,7 +64,7 @@ spec:
tag: "5.1.2"
```
Helm module workload will use data in `settings` as [Helm chart values](https://github.com/captainroy-hy/podinfo/blob/master/charts/podinfo/values.yaml).
Helm module workload will use data in `properties` as [Helm chart values](https://github.com/captainroy-hy/podinfo/blob/master/charts/podinfo/values.yaml).
You can learn the schema of settings by reading the `README.md` of the Helm
chart, and the schema are totally align with
[`values.yaml`](https://github.com/captainroy-hy/podinfo/blob/master/charts/podinfo/values.yaml)

110
docs/en/kube/component.md Normal file
View File

@@ -0,0 +1,110 @@
# Use Raw Kubernetes Resource To Extend a Component type
This documentation explains how to use raw K8s resource to define an application component.
Before reading this part, please make sure you've learned [the definition and template concepts](../platform-engineers/definition-and-templates.md).
## Write ComponentDefinition
Here is an example `ComponentDefinition` about how to use raw k8s resource as schematic module.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: kube-worker
namespace: default
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
schematic:
kube:
template:
apiVersion: apps/v1
kind: Deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
ports:
- containerPort: 80
parameters:
- name: image
required: true
type: string
fieldPaths:
- "spec.template.spec.containers[0].image"
```
Just like using CUE as schematic module, we also have some rules and contracts
to use raw k8s resource as schematic module.
`.spec.schematic.kube` contains template of the raw k8s resource and
configurable parameters.
- `.spec.schematic.kube.template` is the raw k8s resource in YAML format just like
we usually defined in a YAML file.
- `.spec.schematic.kube.parameters` contains a set of configurable parameters.
`name`, `type`, and `fieldPaths` are required fields.
`description` and `required` are optional fields.
- The parameter `name` must be unique in a `ComponentDefinition`.
- `type` indicates the data type of value set to the field in a workload.
This is a required field which will help Vela to generate a OpenAPI JSON schema
for the parameters automatically.
Currently, only basic data types are allowed, including `string`, `number`, and
`boolean`, while `array` and `object` are not.
- `fieldPaths` in the parameter specifies an array of fields within this workload
that will be overwritten by the value of this parameter.
All fields must be of the same type.
Fields are specified as JSON field paths without a leading dot, for example
`spec.replicas`, `spec.containers[0].image`.
## Create an Application using Kube schematic ComponentDefinition
Here is an example `Application`.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
namespace: default
spec:
components:
- name: mycomp
type: kube-worker
properties:
image: nginx:1.14.0
```
Kube schematic workload will use data in `properties` as the values of
parameters.
Since parameters only support basic data type, values in `properties` should be
formatted as simple key-value, `<parameterName>: <parameterValue>`.
And don't forget to set value to required parameter.
Deploy the `Application` and verify the resulting workload.
```shell
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
mycomp-v1 1/1 1 1 66m
```
And check the parameter works.
```shell
$ kubectl get deployment mycomp-v1 -o json | jq '.spec.template.spec.containers[0].image'
"nginx:1.14.0"
```

108
docs/en/kube/trait.md Normal file
View File

@@ -0,0 +1,108 @@
# Attach Traits to Kube Based Components
Most traits in KubeVela can be attached to Kube based component seamlessly.
In this sample application below, we add two traits,
[scaler](https://github.com/oam-dev/kubevela/blob/master/charts/vela-core/templates/defwithtemplate/manualscale.yaml)
and
[virtualgroup](https://github.com/oam-dev/kubevela/blob/master/docs/examples/kube-module/virtual-group-td.yaml), to a Kube based component.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
namespace: default
spec:
components:
- name: mycomp
type: kube-worker
properties:
image: nginx:1.14.0
traits:
- type: scaler
properties:
replicas: 2
- type: virtualgroup
properties:
group: "my-group1"
type: "cluster"
```
## Verify traits work correctly
Deploy the application and verify traits work.
Check the scaler trait.
```shell
$ kubectl get manualscalertrait
NAME AGE
demo-podinfo-scaler-3x1sfcd34 2m
```
```shell
$ kubectl get deployment mycomp-v1 -o json | jq .spec.replicas
2
```
Check the virtualgroup trait.
```shell
$ kubectl get deployment mycomp-v1 -o json | jq .spec.template.metadata.labels
{
"app.cluster.virtual.group": "my-group1",
"app.kubernetes.io/name": "myapp"
}
```
## Update an Application
After the application is deployed and workloads/traits are created successfully,
you can update the application, and corresponding changes will be applied to the
workload.
Let's make several changes on the configuration of the sample application.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
namespace: default
spec:
components:
- name: mycomp
type: kube-worker
properties:
image: nginx:1.14.1 # 1.14.0 => 1.14.1
traits:
- type: scaler
properties:
replicas: 4 # 2 => 4
- type: virtualgroup
properties:
group: "my-group2" # my-group1 => my-group2
type: "cluster"
```
Apply the new configuration and check the results after several seconds.
> After updating, the workload name is changed from `mycomp-v1` to `mycomp-v2`.
Check the new parameter works.
```shell
$ kubectl get deployment mycomp-v2 -o json | jq '.spec.template.spec.containers[0].image'
"nginx:1.14.1"
```
Check the scaler trait.
```shell
$ kubectl get deployment mycomp-v2 -o json | jq .spec.replicas
4
```
Check the virtualgroup trait.
```shell
$ kubectl get deployment mycomp-v2 -o json | jq .spec.template.metadata.labels
{
"app.cluster.virtual.group": "my-group2",
"app.kubernetes.io/name": "myapp"
}
```

View File

@@ -0,0 +1,19 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
namespace: default
spec:
components:
- name: mycomp
type: kube-worker
properties:
image: nginx:1.14.0
traits:
- type: scaler
properties:
replicas: 2
- type: virtualgroup
properties:
group: "my-group1"
type: "cluster"

View File

@@ -0,0 +1,34 @@
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: kube-worker
namespace: default
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
schematic:
kube:
template:
apiVersion: apps/v1
kind: Deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
ports:
- containerPort: 80
parameters:
- name: image
required: true
type: string
fieldPaths:
- "spec.template.spec.containers[0].image"

View File

@@ -0,0 +1,29 @@
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: "Add virtual group labels"
name: virtualgroup
spec:
appliesToWorkloads:
- webservice
- worker
- deployments.apps
extension:
template: |-
patch: {
spec: template: {
metadata: labels: {
if parameter.type == "namespace" {
"app.namespace.virtual.group": parameter.group
}
if parameter.type == "cluster" {
"app.cluster.virtual.group": parameter.group
}
}
}
}
parameter: {
group: *"default" | string
type: *"namespace" | string
}

View File

@@ -516,6 +516,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload
@@ -714,6 +757,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for trait
@@ -852,6 +938,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload

View File

@@ -91,6 +91,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload

View File

@@ -93,6 +93,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for trait

View File

@@ -106,6 +106,49 @@ spec:
- release
- repository
type: object
kube:
description: Kube defines the encapsulation in raw Kubernetes resource format
properties:
parameters:
description: Parameters defines configurable parameters
items:
description: A KubeParameter defines a configurable parameter of a component.
properties:
description:
description: Description of this parameter.
type: string
fieldPaths:
description: "FieldPaths specifies an array of fields within this workload that will be overwritten by the value of this parameter. \tAll fields must be of the same type. Fields are specified as JSON field paths without a leading dot, for example 'spec.replicas'."
items:
type: string
type: array
name:
description: Name of this parameter
type: string
required:
description: Required specifies whether or not a value for this parameter must be supplied when authoring an Application.
type: boolean
type:
description: 'ValueType indicates the type of the parameter value, and only supports basic data types: string, number, boolean.'
enum:
- string
- number
- boolean
type: string
required:
- fieldPaths
- name
- type
type: object
type: array
template:
description: Template defines the raw Kubernetes resource
type: object
required:
- template
type: object
type: object
status:
description: Status defines the custom health policy and status message for workload

View File

@@ -0,0 +1,204 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package appfile
import (
"testing"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/utils/pointer"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
)
func TestResolveKubeParameters(t *testing.T) {
stringParam := &common.KubeParameter{
Name: "strParam",
ValueType: common.StringType,
FieldPaths: []string{"spec"},
}
requiredParam := &common.KubeParameter{
Name: "reqParam",
Required: pointer.BoolPtr(true),
ValueType: common.StringType,
FieldPaths: []string{"spec"},
}
tests := map[string]struct {
reason string
params []common.KubeParameter
settings map[string]interface{}
want paramValueSettings
wantErr error
}{
"EmptyParam": {
reason: "Empty value settings and no error should be returned",
want: make(paramValueSettings),
},
"UnsupportedParam": {
reason: "An error shoulde be returned because of unsupported param",
params: []common.KubeParameter{*stringParam},
settings: map[string]interface{}{"unsupported": "invalid parameter"},
want: nil,
wantErr: errors.Errorf("unsupported parameter %q", "unsupported"),
},
"MissingRequiredParam": {
reason: "An error should be returned because of missing required param",
params: []common.KubeParameter{*stringParam, *requiredParam},
settings: map[string]interface{}{"strParam": "string"},
want: nil,
wantErr: errors.Errorf("require parameter %q", "reqParam"),
},
"Succeed": {
reason: "No error should be returned",
params: []common.KubeParameter{*stringParam, *requiredParam},
settings: map[string]interface{}{"strParam": "test", "reqParam": "test"},
want: paramValueSettings{
"strParam": paramValueSetting{
Value: "test",
ValueType: common.StringType,
FieldPaths: stringParam.FieldPaths,
},
"reqParam": paramValueSetting{
Value: "test",
ValueType: common.StringType,
FieldPaths: requiredParam.FieldPaths,
},
},
wantErr: nil,
},
}
for tcName, tc := range tests {
t.Run(tcName, func(t *testing.T) {
result, err := resolveKubeParameters(tc.params, tc.settings)
if diff := cmp.Diff(tc.want, result); diff != "" {
t.Fatalf("\nresolveKubeParameters(...)(...) -want +get \nreason:%s\n%s\n", tc.reason, diff)
}
if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
t.Fatalf("\nresolveKubeParameters(...)(...) -want +get \nreason:%s\n%s\n", tc.reason, diff)
}
})
}
}
func TestSetParameterValuesToKubeObj(t *testing.T) {
tests := map[string]struct {
reason string
obj unstructured.Unstructured
values paramValueSettings
wantObj unstructured.Unstructured
wantErr error
}{
"InvalidStringType": {
reason: "An error should be returned",
values: paramValueSettings{
"strParam": paramValueSetting{
Value: int32(100),
ValueType: common.StringType,
FieldPaths: []string{"spec.test"},
},
},
wantErr: errors.Errorf(errInvalidValueType, common.StringType),
},
"InvalidNumberType": {
reason: "An error should be returned",
values: paramValueSettings{
"intParam": paramValueSetting{
Value: "test",
ValueType: common.NumberType,
FieldPaths: []string{"spec.test"},
},
},
wantErr: errors.Errorf(errInvalidValueType, common.NumberType),
},
"InvalidBoolType": {
reason: "An error should be returned",
values: paramValueSettings{
"boolParam": paramValueSetting{
Value: "test",
ValueType: common.BooleanType,
FieldPaths: []string{"spec.test"},
},
},
wantErr: errors.Errorf(errInvalidValueType, common.BooleanType),
},
"InvalidFieldPath": {
reason: "An error should be returned",
values: paramValueSettings{
"strParam": paramValueSetting{
Value: "test",
ValueType: common.StringType,
FieldPaths: []string{"spec[.test"}, // a invalid field path
},
},
wantErr: errors.Wrap(errors.New(`cannot parse path "spec[.test": unterminated '[' at position 4`),
`cannot set parameter "strParam" to field "spec[.test"`),
},
"Succeed": {
reason: "No error should be returned",
obj: unstructured.Unstructured{Object: make(map[string]interface{})},
values: paramValueSettings{
"strParam": paramValueSetting{
Value: "test",
ValueType: common.StringType,
FieldPaths: []string{"spec.strField"},
},
"intParam": paramValueSetting{
Value: 10,
ValueType: common.NumberType,
FieldPaths: []string{"spec.intField"},
},
"floatParam": paramValueSetting{
Value: float64(10.01),
ValueType: common.NumberType,
FieldPaths: []string{"spec.floatField"},
},
"boolParam": paramValueSetting{
Value: true,
ValueType: common.BooleanType,
FieldPaths: []string{"spec.boolField"},
},
},
wantObj: unstructured.Unstructured{Object: map[string]interface{}{
"spec": map[string]interface{}{
"strField": "test",
"intField": int64(10),
"floatField": float64(10.01),
"boolField": true,
},
}},
},
}
for tcName, tc := range tests {
t.Run(tcName, func(t *testing.T) {
obj := tc.obj.DeepCopy()
err := setParameterValuesToKubeObj(obj, tc.values)
if diff := cmp.Diff(tc.wantObj, *obj); diff != "" {
t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
}
if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
}
})
}
}

View File

@@ -21,9 +21,14 @@ import (
"encoding/json"
"fmt"
"cuelang.org/go/cue"
"cuelang.org/go/cue/format"
json2cue "cuelang.org/go/encoding/json"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -46,6 +51,11 @@ const (
AppfileBuiltinConfig = "config"
)
// constant error information
const (
errInvalidValueType = "require %q type parameter value"
)
// Workload is component
type Workload struct {
Name string
@@ -271,6 +281,11 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a
if err != nil {
return nil, nil, err
}
case types.KubeCategory:
comp, acComp, err = generateComponentFromKubeModule(p.client, wl, app.Name, app.RevisionName, ns)
if err != nil {
return nil, nil, err
}
default:
comp, acComp, err = generateComponentFromCUEModule(p.client, wl, app.Name, app.RevisionName, ns)
if err != nil {
@@ -320,6 +335,124 @@ func generateComponentFromCUEModule(c client.Client, wl *Workload, appName, revi
return comp, acComp, nil
}
func generateComponentFromKubeModule(c client.Client, wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
kubeObj := &unstructured.Unstructured{}
err := json.Unmarshal(wl.FullTemplate.Kube.Template.Raw, kubeObj)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot decode Kube template into K8s object")
}
paramValues, err := resolveKubeParameters(wl.FullTemplate.Kube.Parameters, wl.Params)
if err != nil {
return nil, nil, errors.WithMessage(err, "cannot resolve parameter settings")
}
if err := setParameterValuesToKubeObj(kubeObj, paramValues); err != nil {
return nil, nil, errors.WithMessage(err, "cannot set parameters value")
}
// convert structured kube obj into CUE (go ==marshal==> json ==decoder==> cue)
objRaw, err := kubeObj.MarshalJSON()
if err != nil {
return nil, nil, errors.Wrap(err, "cannot marshal kube object")
}
ins, err := json2cue.Decode(&cue.Runtime{}, "", objRaw)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot decode object into CUE")
}
cueRaw, err := format.Node(ins.Value().Syntax())
if err != nil {
return nil, nil, errors.Wrap(err, "cannot format CUE")
}
// NOTE a hack way to enable using CUE capabilities on KUBE schematic workload
wl.Template = fmt.Sprintf(`
output: {
%s
}`, string(cueRaw))
// re-use the way CUE module generates comp & acComp
comp, acComp, err := generateComponentFromCUEModule(c, wl, appName, revision, ns)
if err != nil {
return nil, nil, err
}
return comp, acComp, nil
}
// a helper map whose key is parameter name
type paramValueSettings map[string]paramValueSetting
type paramValueSetting struct {
Value interface{}
ValueType common.ParameterValueType
FieldPaths []string
}
func resolveKubeParameters(params []common.KubeParameter, settings map[string]interface{}) (paramValueSettings, error) {
supported := map[string]*common.KubeParameter{}
for _, p := range params {
supported[p.Name] = p.DeepCopy()
}
values := make(paramValueSettings)
for name, v := range settings {
// check unsupported parameter setting
if supported[name] == nil {
return nil, errors.Errorf("unsupported parameter %q", name)
}
// construct helper map
values[name] = paramValueSetting{
Value: v,
ValueType: supported[name].ValueType,
FieldPaths: supported[name].FieldPaths,
}
}
// check required parameter
for _, p := range params {
if p.Required != nil && *p.Required {
if _, ok := values[p.Name]; !ok {
return nil, errors.Errorf("require parameter %q", p.Name)
}
}
}
return values, nil
}
func setParameterValuesToKubeObj(obj *unstructured.Unstructured, values paramValueSettings) error {
paved := fieldpath.Pave(obj.Object)
for paramName, v := range values {
for _, f := range v.FieldPaths {
switch v.ValueType {
case common.StringType:
vString, ok := v.Value.(string)
if !ok {
return errors.Errorf(errInvalidValueType, v.ValueType)
}
if err := paved.SetString(f, vString); err != nil {
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
}
case common.NumberType:
switch v.Value.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
if err := paved.SetValue(f, v.Value); err != nil {
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
}
default:
return errors.Errorf(errInvalidValueType, v.ValueType)
}
case common.BooleanType:
vBoolean, ok := v.Value.(bool)
if !ok {
return errors.Errorf(errInvalidValueType, v.ValueType)
}
if err := paved.SetValue(f, vBoolean); err != nil {
return errors.Wrapf(err, "cannot set parameter %q to field %q", paramName, f)
}
}
}
}
return nil
}
func generateComponentFromHelmModule(c client.Client, wl *Workload, appName, revision, ns string) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
gv, err := schema.ParseGroupVersion(wl.DefinitionReference.APIVersion)
if err != nil {

View File

@@ -27,6 +27,7 @@ import (
"github.com/google/go-cmp/cmp"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -34,6 +35,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
@@ -669,3 +671,180 @@ var _ = Describe("Test appfile parser to parse helm module", func() {
})
})
var _ = Describe("Test appfile parser to parse kube module", func() {
var (
appName = "test-app"
compName = "test-comp"
)
var testTemplate = func() runtime.RawExtension {
yamlStr := `apiVersion: apps/v1
kind: Deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
ports:
- containerPort: 80 `
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
return runtime.RawExtension{Raw: b}
}
var expectWorkload = func() runtime.RawExtension {
yamlStr := `apiVersion: apps/v1
kind: Deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.0
ports:
- containerPort: 80 `
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
return runtime.RawExtension{Raw: b}
}
var testAppfile = func() *Appfile {
return &Appfile{
Name: appName,
Workloads: []*Workload{
{
Name: compName,
Type: "kube-worker",
CapabilityCategory: oamtypes.KubeCategory,
Params: map[string]interface{}{
"image": "nginx:1.14.0",
},
engine: definition.NewWorkloadAbstractEngine(compName, pd),
Traits: []*Trait{
{
Name: "scaler",
Params: map[string]interface{}{
"replicas": float64(10),
},
engine: definition.NewTraitAbstractEngine("scaler", pd),
Template: `
outputs: scaler: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ManualScalerTrait"
spec: {
replicaCount: parameter.replicas
}
}
parameter: {
//+short=r
replicas: *1 | int
}
`,
},
},
FullTemplate: &util.Template{
Kube: &common.Kube{
Template: testTemplate(),
Parameters: []common.KubeParameter{
{
Name: "image",
ValueType: common.StringType,
Required: pointer.BoolPtr(true),
FieldPaths: []string{"spec.template.spec.containers[0].image"},
},
},
},
},
DefinitionReference: common.WorkloadGVK{
APIVersion: "apps/v1",
Kind: "Deployment",
},
},
},
}
}
manuscaler := util.Object2RawExtension(&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "ManualScalerTrait",
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app.oam.dev/component": compName,
"app.oam.dev/name": appName,
"trait.oam.dev/type": "scaler",
"trait.oam.dev/resource": "scaler",
},
},
"spec": map[string]interface{}{"replicaCount": int64(10)},
},
})
It("Test application containing kube module", func() {
By("Generate ApplicationConfiguration and Components")
ac, components, err := NewApplicationParser(k8sClient, dm, pd).GenerateApplicationConfiguration(testAppfile(), "default")
Expect(err).To(BeNil())
expectAppConfig := &v1alpha2.ApplicationConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "ApplicationConfiguration",
APIVersion: "core.oam.dev/v1alpha2",
}, ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: "default",
Labels: map[string]string{oam.LabelAppName: appName},
},
Spec: v1alpha2.ApplicationConfigurationSpec{
Components: []v1alpha2.ApplicationConfigurationComponent{
{
ComponentName: compName,
Traits: []v1alpha2.ComponentTrait{
{
Trait: manuscaler,
},
},
},
},
},
}
expectComponent := &v1alpha2.Component{
TypeMeta: metav1.TypeMeta{
Kind: "Component",
APIVersion: "core.oam.dev/v1alpha2",
}, ObjectMeta: metav1.ObjectMeta{
Name: compName,
Namespace: "default",
Labels: map[string]string{oam.LabelAppName: appName},
},
Spec: v1alpha2.ComponentSpec{
Workload: expectWorkload(),
},
}
By("Verify expected ApplicationConfiguration")
diff := cmp.Diff(ac, expectAppConfig)
Expect(diff).Should(BeEmpty())
By("Verify expected Component")
diff = cmp.Diff(components[0], expectComponent)
Expect(diff).ShouldNot(BeEmpty())
})
It("Test missing set required parameter", func() {
appfile := testAppfile()
// remove parameter settings
appfile.Workloads[0].Params = nil
_, _, err := NewApplicationParser(k8sClient, dm, pd).GenerateApplicationConfiguration(appfile, "default")
expectError := errors.WithMessage(errors.New(`require parameter "image"`), "cannot resolve parameter settings")
diff := cmp.Diff(expectError, err, test.EquateErrors())
Expect(diff).Should(BeEmpty())
})
})

View File

@@ -86,12 +86,14 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var def utils.CapabilityComponentDefinition
def.Name = req.NamespacedName.Name
def.WorkloadType = workloadType
def.ComponentDefinition = componentDefinition
switch workloadType {
case util.ReferWorkload:
def.WorkloadDefName = componentDefinition.Spec.Workload.Type
case util.HELMDef:
def.Helm = componentDefinition.Spec.Schematic.HELM
def.ComponentDefinition = componentDefinition
case util.KubeDef:
def.Kube = componentDefinition.Spec.Schematic.KUBE
default:
}
err = def.StoreOpenAPISchema(ctx, r, req.Namespace, req.Name)

View File

@@ -48,6 +48,9 @@ func (h *handler) CreateWorkloadDefinition(ctx context.Context) (util.WorkloadTy
if h.cd.Spec.Schematic != nil && h.cd.Spec.Schematic.HELM != nil {
workloadType = util.HELMDef
}
if h.cd.Spec.Schematic != nil && h.cd.Spec.Schematic.KUBE != nil {
workloadType = util.KubeDef
}
wd := new(v1alpha2.WorkloadDefinition)
err := h.Get(ctx, client.ObjectKey{Namespace: h.cd.Namespace, Name: workloadName}, wd)

View File

@@ -25,6 +25,7 @@ import (
"cuelang.org/go/cue"
"github.com/getkin/kin-openapi/openapi3"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -62,6 +63,7 @@ type CapabilityComponentDefinition struct {
WorkloadDefName string `json:"workloadDefName"`
Helm *commontypes.Helm `json:"helm"`
Kube *commontypes.Kube `json:"kube"`
CapabilityBaseDefinition
}
@@ -105,6 +107,43 @@ func (def *CapabilityComponentDefinition) GetOpenAPISchema(ctx context.Context,
return getOpenAPISchema(*capability)
}
// GetKubeSchematicOpenAPISchema gets OpenAPI v3 schema based on kube schematic parameters
func (def *CapabilityComponentDefinition) GetKubeSchematicOpenAPISchema(params []commontypes.KubeParameter) ([]byte, error) {
required := []string{}
properties := map[string]*openapi3.Schema{}
for _, p := range params {
var tmp *openapi3.Schema
switch p.ValueType {
case commontypes.StringType:
tmp = openapi3.NewStringSchema()
case commontypes.NumberType:
tmp = openapi3.NewFloat64Schema()
case commontypes.BooleanType:
tmp = openapi3.NewBoolSchema()
default:
tmp = openapi3.NewStringSchema()
}
if p.Required != nil && *p.Required {
required = append(required, p.Name)
}
// save FieldPaths into description
tmp.Description = fmt.Sprintf("The value will be applied to fields: [%s].", strings.Join(p.FieldPaths, ","))
if p.Description != nil {
tmp.Description = fmt.Sprintf("%s\n %s", tmp.Description, *p.Description)
}
properties[p.Name] = tmp
}
s := openapi3.NewObjectSchema().WithProperties(properties)
if len(required) > 0 {
s.Required = required
}
b, err := s.MarshalJSON()
if err != nil {
return nil, errors.Wrap(err, "cannot marshal generated schema into json")
}
return b, nil
}
// StoreOpenAPISchema stores OpenAPI v3 schema in ConfigMap from WorkloadDefinition
func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name string) error {
var jsonSchema []byte
@@ -112,6 +151,8 @@ func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context
switch def.WorkloadType {
case util.HELMDef:
jsonSchema, err = helm.GetChartValuesJSONSchema(ctx, def.Helm)
case util.KubeDef:
jsonSchema, err = def.GetKubeSchematicOpenAPISchema(def.Kube.Parameters)
default:
jsonSchema, err = def.GetOpenAPISchema(ctx, k8sClient, namespace, name)
}

View File

@@ -106,6 +106,9 @@ const (
// ComponentDef describe a workload of Defined by ComponentDefinition
ComponentDef WorkloadType = "ComponentDef"
// KubeDef describe a workload refer to raw K8s resource
KubeDef WorkloadType = "KubeDef"
// HELMDef describe a workload refer to HELM
HELMDef WorkloadType = "HelmDef"

View File

@@ -42,6 +42,7 @@ type Template struct {
CapabilityCategory types.CapabilityCategory
Reference common.WorkloadGVK
Helm *common.Helm
Kube *common.Kube
// TODO: Add scope definition too
ComponentDefinition *v1beta1.ComponentDefinition
WorkloadDefinition *v1beta1.WorkloadDefinition
@@ -159,6 +160,10 @@ func NewTemplate(schematic *common.Schematic, status *common.Status, raw *runtim
tmp.CapabilityCategory = types.HelmCategory
return tmp, nil
}
if schematic.KUBE != nil {
tmp.Kube = schematic.KUBE
tmp.CapabilityCategory = types.KubeCategory
}
}
extension := map[string]interface{}{}

View File

@@ -155,7 +155,7 @@ var _ = Describe("AppConfig renders workloads", func() {
By("Verify workloads are created")
Eventually(func() bool {
reconcileAppConfigNow(ctx, ac)
requestReconcileNow(ctx, ac)
cw := &v1alpha2.ContainerizedWorkload{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: cwName, Namespace: namespace}, cw); err != nil {
return false

View File

@@ -312,7 +312,7 @@ var _ = Describe("Versioning mechanism of components", func() {
By("Check ContainerizedWorkload workload's image field has been changed to v2")
cwWlV2 := &v1alpha2.ContainerizedWorkload{}
Eventually(func() string {
reconcileAppConfigNow(ctx, &appConfigWithRevisionName)
requestReconcileNow(ctx, &appConfigWithRevisionName)
k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: componentName}, cwWlV2)
return cwWlV2.Spec.Containers[0].Image
}, time.Second*60, time.Microsecond*500).Should(Equal(imageV2))
@@ -358,7 +358,7 @@ var _ = Describe("Versioning mechanism of components", func() {
var w1 unstructured.Unstructured
Eventually(
func() error {
reconcileAppConfigNow(ctx, &appconfig)
requestReconcileNow(ctx, &appconfig)
w1.SetAPIVersion("example.com/v1")
w1.SetKind("Bar")
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: revisionNameV1}, &w1)
@@ -390,7 +390,7 @@ var _ = Describe("Versioning mechanism of components", func() {
var w2 unstructured.Unstructured
Eventually(
func() error {
reconcileAppConfigNow(ctx, &appconfig)
requestReconcileNow(ctx, &appconfig)
w2.SetAPIVersion("example.com/v1")
w2.SetKind("Bar")
return k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: revisionNameV2}, &w2)
@@ -477,7 +477,7 @@ var _ = Describe("Versioning mechanism of components", func() {
var w2 unstructured.Unstructured
Eventually(
func() string {
reconcileAppConfigNow(ctx, &appconfig)
requestReconcileNow(ctx, &appconfig)
w2.SetAPIVersion("example.com/v1")
w2.SetKind("Bar")
err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: componentName}, &w2)

View File

@@ -317,7 +317,7 @@ var _ = Describe("HealthScope", func() {
By("Verify health scope")
Eventually(
func() v1alpha2.ScopeHealthCondition {
reconcileAppConfigNow(ctx, &appConfig)
requestReconcileNow(ctx, &appConfig)
*healthScope = v1alpha2.HealthScope{}
k8sClient.Get(ctx, healthScopeObject, healthScope)
logf.Log.Info("Checking on health scope",

View File

@@ -144,19 +144,6 @@ var _ = Describe("Test application containing helm module", func() {
Expect(k8sClient.Patch(ctx, &scalerTd, client.Merge)).Should(Succeed())
})
// reconcileAppConfigNow will trigger an immediate reconciliation on AppConfig.
// Some test cases may fail for timeout to wait a scheduled reconciliation.
// This is a workaround to avoid long-time wait before next scheduled
// reconciliation.
reconcileAppContextNow := func(ctx context.Context, ac *v1alpha2.ApplicationContext) error {
u := ac.DeepCopy()
u.SetAnnotations(map[string]string{
"app.oam.dev/requestreconcile": time.Now().String(),
})
u.SetResourceVersion("")
return k8sClient.Patch(ctx, u, client.Merge)
}
It("Test deploy an application containing helm module", func() {
app = v1alpha2.Application{
ObjectMeta: metav1.ObjectMeta{
@@ -211,9 +198,7 @@ var _ = Describe("Test application containing helm module", func() {
By("Verify two traits are applied to the workload")
Eventually(func() bool {
if err := reconcileAppContextNow(ctx, ac); err != nil {
return false
}
requestReconcileNow(ctx, ac)
deploy := &appsv1.Deployment{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy); err != nil {
return false
@@ -282,9 +267,7 @@ var _ = Describe("Test application containing helm module", func() {
By("Verify the changes are applied to the workload")
Eventually(func() bool {
if err := reconcileAppContextNow(ctx, ac); err != nil {
return false
}
requestReconcileNow(ctx, ac)
deploy := &appsv1.Deployment{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy); err != nil {
return false

View File

@@ -0,0 +1,377 @@
/*
Copyright 2020 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"
"errors"
"fmt"
"time"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/ghodss/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam/util"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Test application containing kube module", func() {
ctx := context.Background()
var (
namespace = "kube-test-ns"
appName = "test-app"
compName = "test-comp"
cdName = "test-kube-worker"
wdName = "test-kube-worker-wd"
tdName = "test-virtualgroup"
)
var app v1beta1.Application
var ns corev1.Namespace
var testTemplate = func() runtime.RawExtension {
yamlStr := `apiVersion: apps/v1
kind: Deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
ports:
- containerPort: 80 `
b, _ := yaml.YAMLToJSON([]byte(yamlStr))
return runtime.RawExtension{Raw: b}
}
BeforeEach(func() {
ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
Eventually(
func() error {
return k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))
},
time.Second*120, time.Millisecond*500).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
By("make sure all the resources are removed")
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: namespace}, &corev1.Namespace{})
}, time.Second*120, time.Millisecond*500).Should(&util.NotFoundMatcher{})
By("create test namespace")
Eventually(
func() error {
return k8sClient.Create(ctx, &ns)
},
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
cd := v1beta1.ComponentDefinition{}
cd.SetName(cdName)
cd.SetNamespace(namespace)
cd.Spec.Workload.Definition = common.WorkloadGVK{APIVersion: "apps/v1", Kind: "Deployment"}
cd.Spec.Schematic = &common.Schematic{
KUBE: &common.Kube{
Template: testTemplate(),
Parameters: []common.KubeParameter{
{
Name: "image",
ValueType: common.StringType,
FieldPaths: []string{"spec.template.spec.containers[0].image"},
Required: pointer.BoolPtr(true),
Description: pointer.StringPtr("test description"),
},
},
},
}
Expect(k8sClient.Create(ctx, &cd)).Should(Succeed())
By("Install a patch trait used to test CUE module")
td := v1beta1.TraitDefinition{}
td.SetName(tdName)
td.SetNamespace(namespace)
td.Spec.AppliesToWorkloads = []string{"deployments.apps"}
td.Spec.Schematic = &common.Schematic{
CUE: &common.CUE{
Template: `patch: {
spec: template: {
metadata: labels: {
if parameter.type == "namespace" {
"app.namespace.virtual.group": parameter.group
}
if parameter.type == "cluster" {
"app.cluster.virtual.group": parameter.group
}
}
}
}
parameter: {
group: *"default" | string
type: *"namespace" | string
}`,
},
}
Expect(k8sClient.Create(ctx, &td)).Should(Succeed())
By("Add 'deployments.apps' to scaler's appliesToWorkloads")
scalerTd := v1beta1.TraitDefinition{}
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "scaler", Namespace: "vela-system"}, &scalerTd)).Should(Succeed())
scalerTd.Spec.AppliesToWorkloads = []string{"deployments.apps", "webservice", "worker"}
scalerTd.SetResourceVersion("")
Expect(k8sClient.Patch(ctx, &scalerTd, client.Merge)).Should(Succeed())
})
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.WorkloadDefinition{}, client.InNamespace(namespace))
k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace))
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
By("make sure all the resources are removed")
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: namespace}, &corev1.Namespace{})
}, time.Second*120, time.Millisecond*500).Should(&util.NotFoundMatcher{})
By("Remove 'deployments.apps' from scaler's appliesToWorkloads")
scalerTd := v1beta1.TraitDefinition{}
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "scaler", Namespace: "vela-system"}, &scalerTd)).Should(Succeed())
scalerTd.Spec.AppliesToWorkloads = []string{"webservice", "worker"}
scalerTd.SetResourceVersion("")
Expect(k8sClient.Patch(ctx, &scalerTd, client.Merge)).Should(Succeed())
})
It("Test deploy an application containing kube module", func() {
app = v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
{
Name: compName,
Type: cdName,
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "nginx:1.14.0",
}),
Traits: []v1beta1.ApplicationTrait{
{
Type: "scaler",
Properties: util.Object2RawExtension(map[string]interface{}{
"replicas": 2,
}),
},
{
Type: tdName,
Properties: util.Object2RawExtension(map[string]interface{}{
"group": "my-group",
"type": "cluster",
}),
},
},
},
},
},
}
By("Create application")
Expect(k8sClient.Create(ctx, &app)).Should(Succeed())
ac := &v1alpha2.ApplicationContext{}
acName := appName
By("Verify the ApplicationContext is created successfully")
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac)
}, 30*time.Second, time.Second).Should(Succeed())
By("Verify the workload(deployment) is created successfully")
deploy := &appsv1.Deployment{}
deployName := fmt.Sprintf("%s-v1", compName)
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy)
}, 30*time.Second, 3*time.Second).Should(Succeed())
By("Verify two traits are applied to the workload")
Eventually(func() bool {
requestReconcileNow(ctx, ac)
deploy := &appsv1.Deployment{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy); err != nil {
return false
}
By("Verify patch trait is applied")
templateLabels := deploy.Spec.Template.Labels
if templateLabels["app.cluster.virtual.group"] != "my-group" {
return false
}
By("Verify scaler trait is applied")
if *deploy.Spec.Replicas != 2 {
return false
}
By("Verify parameter is applied")
return deploy.Spec.Template.Spec.Containers[0].Image == "nginx:1.14.0"
}, 15*time.Second, 3*time.Second).Should(BeTrue())
By("Update the application")
app = v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
{
Name: compName,
Type: cdName,
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "nginx:1.14.1", // nginx:1.14.0 => nginx:1.14.1
}),
Traits: []v1beta1.ApplicationTrait{
{
Type: "scaler",
Properties: util.Object2RawExtension(map[string]interface{}{
"replicas": 3, // change 2 => 3
}),
},
{
Type: tdName,
Properties: util.Object2RawExtension(map[string]interface{}{
"group": "my-group-0", // change my-group => my-group-0
"type": "cluster",
}),
},
},
},
},
},
}
Expect(k8sClient.Patch(ctx, &app, client.Merge)).Should(Succeed())
By("Verify the ApplicationContext is updated")
deploy = &appsv1.Deployment{}
Eventually(func() bool {
ac = &v1alpha2.ApplicationContext{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac); err != nil {
return false
}
return ac.GetGeneration() == 2
}, 15*time.Second, 3*time.Second).Should(BeTrue())
By("Verify the changes are applied to the workload")
deployName = fmt.Sprintf("%s-v2", compName)
Eventually(func() bool {
requestReconcileNow(ctx, ac)
deploy := &appsv1.Deployment{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy); err != nil {
return false
}
By("Verify new patch trait is applied")
templateLabels := deploy.Spec.Template.Labels
if templateLabels["app.cluster.virtual.group"] != "my-group-0" {
return false
}
By("Verify new scaler trait is applied")
if *deploy.Spec.Replicas != 3 {
return false
}
By("Verify new parameter is applied")
return deploy.Spec.Template.Spec.Containers[0].Image == "nginx:1.14.1"
}, 60*time.Second, 10*time.Second).Should(BeTrue())
})
It("Test deploy an application containing kube module defined by workloadDefinition", func() {
workloaddef := v1beta1.WorkloadDefinition{}
workloaddef.SetName(wdName)
workloaddef.SetNamespace(namespace)
workloaddef.Spec.Reference = common.DefinitionReference{Name: "deployments.apps", Version: "v1"}
workloaddef.Spec.Schematic = &common.Schematic{
KUBE: &common.Kube{
Template: testTemplate(),
Parameters: []common.KubeParameter{
{
Name: "image",
ValueType: common.StringType,
FieldPaths: []string{"spec.template.spec.containers[0].image"},
Required: pointer.BoolPtr(true),
Description: pointer.StringPtr("test description"),
},
},
},
}
By("register workloadDefinition")
Expect(k8sClient.Create(ctx, &workloaddef)).Should(Succeed())
appTestName := "test-app-refer-to-workloaddef"
appTest := v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appTestName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
{
Name: compName,
Type: cdName,
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "nginx:1.14.0",
}),
},
},
},
}
By("Create application")
Expect(k8sClient.Create(ctx, &appTest)).Should(Succeed())
ac := &v1alpha2.ApplicationContext{}
acName := appTestName
By("Verify the AppConfig is created successfully")
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac)
}, 30*time.Second, time.Second).Should(Succeed())
By("Verify the workload(deployment) is created successfully")
deploy := &appsv1.Deployment{}
deployName := fmt.Sprintf("%s-v1", compName)
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy)
}, 15*time.Second, 3*time.Second).Should(Succeed())
})
It("Test store JSON schema of Kube parameter in ConfigMap", func() {
By("Get the ConfigMap")
cmName := fmt.Sprintf("schema-%s", cdName)
Eventually(func() error {
cm := &corev1.ConfigMap{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: cmName, Namespace: namespace}, cm); err != nil {
return err
}
if cm.Data["openapi-v3-json-schema"] == "" {
return errors.New("json schema is not found in the ConfigMap")
}
return nil
}, 60*time.Second, 5*time.Second).Should(Succeed())
})
})

View File

@@ -19,6 +19,7 @@ package controllers_test
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"testing"
"time"
@@ -294,15 +295,19 @@ var _ = AfterSuite(func() {
Expect(k8sClient.Delete(context.Background(), &crd)).Should(BeNil())
})
// reconcileAppConfigNow will trigger an immediate reconciliation on AppConfig.
// requestReconcileNow will trigger an immediate reconciliation on K8s object.
// Some test cases may fail for timeout to wait a scheduled reconciliation.
// This is a workaround to avoid long-time wait before next scheduled
// reconciliation.
func reconcileAppConfigNow(ctx context.Context, ac *v1alpha2.ApplicationConfiguration) error {
u := ac.DeepCopy()
u.SetAnnotations(map[string]string{
func requestReconcileNow(ctx context.Context, o runtime.Object) {
By(fmt.Sprintf("Requset reconcile %q now",
o.GetObjectKind().GroupVersionKind().String()))
oCopy := o.DeepCopyObject()
oMeta, ok := oCopy.(metav1.Object)
Expect(ok).Should(BeTrue())
oMeta.SetAnnotations(map[string]string{
"app.oam.dev/requestreconcile": time.Now().String(),
})
u.SetResourceVersion("")
return k8sClient.Patch(ctx, u, client.Merge)
oMeta.SetResourceVersion("")
Expect(k8sClient.Patch(ctx, oCopy, client.Merge)).Should(Succeed())
}