mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-23 06:14:30 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29ecc5c0df | ||
|
|
20c11f2b84 | ||
|
|
e9f7cf7c23 | ||
|
|
cebeff867a | ||
|
|
7902a19aae | ||
|
|
f98b8c7d8a | ||
|
|
ee8773e1cf | ||
|
|
8cf2f20846 | ||
|
|
2c6e8e7de7 | ||
|
|
dbfd6a1d10 | ||
|
|
ce8e652802 | ||
|
|
9968211163 | ||
|
|
c86776cca1 | ||
|
|
11a1b2fa72 | ||
|
|
cd036b87ae | ||
|
|
7e447c6532 | ||
|
|
b836f86484 | ||
|
|
d34372bf47 |
@@ -213,6 +213,8 @@ const (
|
||||
WorkflowStateFinished WorkflowState = "finished"
|
||||
// WorkflowStateExecuting means workflow is still running or waiting some steps.
|
||||
WorkflowStateExecuting WorkflowState = "executing"
|
||||
// WorkflowStateSkipping means it will skip this reconcile and let next reconcile to handle it.
|
||||
WorkflowStateSkipping WorkflowState = "skipping"
|
||||
)
|
||||
|
||||
// ApplicationComponentStatus record the health status of App component
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
@@ -138,3 +141,36 @@ func (app *Application) GetComponent(workloadType string) *common.ApplicationCom
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unstructured convert application to unstructured.Unstructured.
|
||||
func (app *Application) Unstructured() (*unstructured.Unstructured, error) {
|
||||
var obj = &unstructured.Unstructured{}
|
||||
app.SetGroupVersionKind(ApplicationKindVersionKind)
|
||||
bt, err := json.Marshal(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := obj.UnmarshalJSON(bt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if app.Status.Services == nil {
|
||||
if err := unstructured.SetNestedSlice(obj.Object, []interface{}{}, "status", "services"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if app.Status.AppliedResources == nil {
|
||||
if err := unstructured.SetNestedSlice(obj.Object, []interface{}{}, "status", "appliedResources"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if wfStatus := app.Status.Workflow; wfStatus != nil && wfStatus.Steps == nil {
|
||||
if err := unstructured.SetNestedSlice(obj.Object, []interface{}{}, "status", "workflow", "steps"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ spec:
|
||||
app: {{ template "kubevela.name" . }}-admission-create
|
||||
{{- include "kubevela.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: create
|
||||
image: {{ .Values.imageRegistry }}{{ .Values.admissionWebhooks.patch.image.repository }}:{{ .Values.admissionWebhooks.patch.image.tag }}
|
||||
|
||||
@@ -22,6 +22,10 @@ spec:
|
||||
app: {{ template "kubevela.name" . }}-admission-patch
|
||||
{{- include "kubevela.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: patch
|
||||
image: {{ .Values.imageRegistry }}{{ .Values.admissionWebhooks.patch.image.repository }}:{{ .Values.admissionWebhooks.patch.image.tag }}
|
||||
|
||||
@@ -198,6 +198,10 @@ spec:
|
||||
app: {{ template "kubevela.fullname" . }}-cluster-gateway-tls-secret-create
|
||||
{{- include "kubevela.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: create
|
||||
image: {{ .Values.imageRegistry }}{{ .Values.admissionWebhooks.patch.image.repository }}:{{ .Values.admissionWebhooks.patch.image.tag }}
|
||||
@@ -241,6 +245,10 @@ spec:
|
||||
app: {{ template "kubevela.fullname" . }}-cluster-gateway-tls-secret-patch
|
||||
{{- include "kubevela.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: patch
|
||||
image: {{ .Values.imageRegistry }}{{ .Values.multicluster.clusterGateway.image.repository }}:{{ .Values.multicluster.clusterGateway.image.tag }}
|
||||
|
||||
@@ -32,21 +32,30 @@ spec:
|
||||
kind: "Ingress"
|
||||
metadata: {
|
||||
name: context.name
|
||||
annotations: "kubernetes.io/ingress.class": parameter.class
|
||||
annotations: {
|
||||
if !parameter.classInSpec {
|
||||
"kubernetes.io/ingress.class": parameter.class
|
||||
}
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
if parameter.classInSpec {
|
||||
ingressClassName: parameter.class
|
||||
}
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
pathType: "ImplementationSpecific"
|
||||
backend: service: {
|
||||
name: context.name
|
||||
port: number: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
spec: rules: [{
|
||||
host: parameter.domain
|
||||
http: paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
pathType: "ImplementationSpecific"
|
||||
backend: service: {
|
||||
name: context.name
|
||||
port: number: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the domain you want to expose
|
||||
@@ -57,6 +66,9 @@ spec:
|
||||
|
||||
// +usage=Specify the class of ingress to use
|
||||
class: *"nginx" | string
|
||||
|
||||
// +usage=Set ingress class in '.spec.ingressClassName' instead of 'kubernetes.io/ingress.class' annotation.
|
||||
classInSpec: *false | bool
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
|
||||
@@ -11,6 +11,10 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
mountsArray: {
|
||||
pvc: *[
|
||||
for v in parameter.volumeMounts.pvc {
|
||||
@@ -152,6 +156,12 @@ spec:
|
||||
{
|
||||
containerPort: v.port
|
||||
protocol: v.protocol
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
}
|
||||
}}]
|
||||
}
|
||||
|
||||
@@ -262,6 +272,12 @@ spec:
|
||||
for v in parameter.ports if v.expose == true {
|
||||
port: v.port
|
||||
targetPort: v.port
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
}
|
||||
},
|
||||
]
|
||||
outputs: {
|
||||
@@ -304,6 +320,8 @@ spec:
|
||||
ports?: [...{
|
||||
// +usage=Number of port to expose on the pod's IP address
|
||||
port: int
|
||||
// +usage=Name of the port
|
||||
name?: string
|
||||
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
|
||||
protocol: *"TCP" | "UDP" | "SCTP"
|
||||
// +usage=Specify if the port should be exposed
|
||||
|
||||
@@ -32,21 +32,30 @@ spec:
|
||||
kind: "Ingress"
|
||||
metadata: {
|
||||
name: context.name
|
||||
annotations: "kubernetes.io/ingress.class": parameter.class
|
||||
annotations: {
|
||||
if !parameter.classInSpec {
|
||||
"kubernetes.io/ingress.class": parameter.class
|
||||
}
|
||||
}
|
||||
}
|
||||
spec: {
|
||||
if parameter.classInSpec {
|
||||
ingressClassName: parameter.class
|
||||
}
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
pathType: "ImplementationSpecific"
|
||||
backend: service: {
|
||||
name: context.name
|
||||
port: number: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
spec: rules: [{
|
||||
host: parameter.domain
|
||||
http: paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
pathType: "ImplementationSpecific"
|
||||
backend: service: {
|
||||
name: context.name
|
||||
port: number: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the domain you want to expose
|
||||
@@ -57,6 +66,9 @@ spec:
|
||||
|
||||
// +usage=Specify the class of ingress to use
|
||||
class: *"nginx" | string
|
||||
|
||||
// +usage=Set ingress class in '.spec.ingressClassName' instead of 'kubernetes.io/ingress.class' annotation.
|
||||
classInSpec: *false | bool
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
|
||||
@@ -11,6 +11,10 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
mountsArray: {
|
||||
pvc: *[
|
||||
for v in parameter.volumeMounts.pvc {
|
||||
@@ -152,6 +156,12 @@ spec:
|
||||
{
|
||||
containerPort: v.port
|
||||
protocol: v.protocol
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
}
|
||||
}}]
|
||||
}
|
||||
|
||||
@@ -262,6 +272,12 @@ spec:
|
||||
for v in parameter.ports if v.expose == true {
|
||||
port: v.port
|
||||
targetPort: v.port
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
}
|
||||
},
|
||||
]
|
||||
outputs: {
|
||||
@@ -304,6 +320,8 @@ spec:
|
||||
ports?: [...{
|
||||
// +usage=Number of port to expose on the pod's IP address
|
||||
port: int
|
||||
// +usage=Name of the port
|
||||
name?: string
|
||||
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
|
||||
protocol: *"TCP" | "UDP" | "SCTP"
|
||||
// +usage=Specify if the port should be exposed
|
||||
|
||||
@@ -129,6 +129,7 @@ func main() {
|
||||
flag.DurationVar(&retryPeriod, "leader-election-retry-period", 2*time.Second,
|
||||
"The duration the LeaderElector clients should wait between tries of actions")
|
||||
flag.BoolVar(&enableClusterGateway, "enable-cluster-gateway", false, "Enable cluster-gateway to use multicluster, disabled by default.")
|
||||
flag.BoolVar(&controllerArgs.EnableCompatibility, "enable-asi-compatibility", false, "enable compatibility for asi")
|
||||
standardcontroller.AddOptimizeFlags()
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -14,8 +14,12 @@ spec:
|
||||
ports:
|
||||
- port: 8000
|
||||
- port: 8001
|
||||
name: exposeport1
|
||||
protocol: UDP
|
||||
expose: true
|
||||
expose: true
|
||||
- port: 8002
|
||||
protocol: UDP
|
||||
expose: true
|
||||
volumeMounts:
|
||||
pvc:
|
||||
- name: my-mount
|
||||
|
||||
74
docs/examples/envbinding/envpatch-with-wild-match.yaml
Normal file
74
docs/examples/envbinding/envpatch-with-wild-match.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: example-app
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: podinfo
|
||||
type: webservice
|
||||
properties:
|
||||
image: stefanprodan/podinfo
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 1
|
||||
- name: hello-world
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 1
|
||||
- name: nginx
|
||||
type: worker
|
||||
properties:
|
||||
image: nginx
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 1
|
||||
policies:
|
||||
- name: example-multi-env-policy
|
||||
type: env-binding
|
||||
properties:
|
||||
envs:
|
||||
- name: test
|
||||
placement:
|
||||
clusterSelector:
|
||||
name: local
|
||||
namespaceSelector:
|
||||
name: test
|
||||
patch:
|
||||
components:
|
||||
- name: podinfo # patch to component named podinfo, no type check
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 2
|
||||
|
||||
- name: staging
|
||||
placement:
|
||||
clusterSelector:
|
||||
name: remote
|
||||
patch:
|
||||
components: # patch to all webservice components
|
||||
- type: webservice
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 3
|
||||
|
||||
- name: prod
|
||||
placement:
|
||||
clusterSelector:
|
||||
name: remote
|
||||
namespaceSelector:
|
||||
name: prod
|
||||
patch:
|
||||
components: # patch to all components
|
||||
- traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 3
|
||||
@@ -324,6 +324,8 @@ const (
|
||||
PayloadTypeACR = "acr"
|
||||
// PayloadTypeHarbor is the payload type harbor
|
||||
PayloadTypeHarbor = "harbor"
|
||||
// PayloadTypeJFrog is the payload type jfrog
|
||||
PayloadTypeJFrog = "jfrog"
|
||||
|
||||
// ComponentTypeWebservice is the component type webservice
|
||||
ComponentTypeWebservice = "webservice"
|
||||
@@ -336,6 +338,10 @@ const (
|
||||
const (
|
||||
// HarborEventTypePushArtifact is the event type PUSH_ARTIFACT
|
||||
HarborEventTypePushArtifact = "PUSH_ARTIFACT"
|
||||
// JFrogEventTypePush is push event type of jfrog webhook
|
||||
JFrogEventTypePush = "pushed"
|
||||
// JFrogDomainDocker is webhook domain of jfrog docker
|
||||
JFrogDomainDocker = "docker"
|
||||
)
|
||||
|
||||
// TableName return custom table name
|
||||
|
||||
@@ -464,6 +464,24 @@ type DockerHubRepository struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// HandleApplicationTriggerJFrogRequest application trigger JFrog webhook request
|
||||
type HandleApplicationTriggerJFrogRequest struct {
|
||||
Domain string `json:"domain"`
|
||||
EventType string `json:"event_type"`
|
||||
Data JFrogWebhookData `json:"data"`
|
||||
}
|
||||
|
||||
// JFrogWebhookData is the data of JFrog webhook request
|
||||
type JFrogWebhookData struct {
|
||||
URL string
|
||||
ImageName string `json:"image_name"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
RepoKey string `json:"repo_key"`
|
||||
Digest string `json:"sha256"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
// EnvBinding application env binding
|
||||
type EnvBinding struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
|
||||
@@ -18,6 +18,7 @@ package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
@@ -25,6 +26,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
errors2 "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -39,6 +42,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
@@ -155,7 +159,9 @@ func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registr
|
||||
if addon == nil {
|
||||
return nil, bcode.ErrAddonNotExist
|
||||
}
|
||||
addon.UISchema = renderDefaultUISchema(addon.APISchema)
|
||||
|
||||
addon.UISchema = renderAddonCustomUISchema(ctx, u.kubeClient, name, renderDefaultUISchema(addon.APISchema))
|
||||
|
||||
a, err := AddonImpl2AddonRes(addon)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -455,6 +461,29 @@ func convertAppStateToAddonPhase(state common2.ApplicationPhase) apis.AddonPhase
|
||||
}
|
||||
}
|
||||
|
||||
func renderAddonCustomUISchema(ctx context.Context, cli client.Client, addonName string, defaultSchema []*utils.UIParameter) []*utils.UIParameter {
|
||||
var cm v1.ConfigMap
|
||||
if err := cli.Get(ctx, k8stypes.NamespacedName{
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Name: fmt.Sprintf("addon-uischema-%s", addonName),
|
||||
}, &cm); err != nil {
|
||||
if !errors2.IsNotFound(err) {
|
||||
log.Logger.Errorf("find uischema configmap from cluster failure %s", err.Error())
|
||||
}
|
||||
return defaultSchema
|
||||
}
|
||||
data, ok := cm.Data[types.UISchema]
|
||||
if !ok {
|
||||
return defaultSchema
|
||||
}
|
||||
schema := []*utils.UIParameter{}
|
||||
if err := json.Unmarshal([]byte(data), &schema); err != nil {
|
||||
log.Logger.Errorf("unmarshal ui schema failure %s", err.Error())
|
||||
return defaultSchema
|
||||
}
|
||||
return patchSchema(defaultSchema, schema)
|
||||
}
|
||||
|
||||
// ConvertAddonRegistryModel2AddonRegistryMeta will convert from model to AddonRegistry
|
||||
func ConvertAddonRegistryModel2AddonRegistryMeta(r pkgaddon.Registry) apis.AddonRegistry {
|
||||
return apis.AddonRegistry{
|
||||
|
||||
106
pkg/apiserver/rest/usecase/addon_test.go
Normal file
106
pkg/apiserver/rest/usecase/addon_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
||||
)
|
||||
|
||||
var _ = Describe("addon usecase test", func() {
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: types.DefaultKubeVelaNS}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("Test render customize ui-schema", func() {
|
||||
schemaData, err := ioutil.ReadFile("testdata/addon-uischema-test.yaml")
|
||||
|
||||
addonName := "test"
|
||||
Expect(err).Should(BeNil())
|
||||
jsonData, err := yaml.YAMLToJSON(schemaData)
|
||||
Expect(err).Should(BeNil())
|
||||
cm := v1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "ConfigMap"},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: types.DefaultKubeVelaNS, Name: fmt.Sprintf("addon-uischema-%s", addonName)},
|
||||
Data: map[string]string{
|
||||
types.UISchema: string(jsonData),
|
||||
}}
|
||||
|
||||
Expect(k8sClient.Create(ctx, &cm)).Should(BeNil())
|
||||
defaultSchema := []*utils.UIParameter{
|
||||
{
|
||||
JSONKey: "version",
|
||||
Sort: 3,
|
||||
},
|
||||
{
|
||||
JSONKey: "domain",
|
||||
Sort: 8,
|
||||
},
|
||||
}
|
||||
res := renderAddonCustomUISchema(ctx, k8sClient, addonName, defaultSchema)
|
||||
Expect(len(res)).Should(BeEquivalentTo(2))
|
||||
for _, re := range res {
|
||||
if re.JSONKey == "version" {
|
||||
Expect(re.Validate.DefaultValue.(string)).Should(BeEquivalentTo("1.2.0-rc1"))
|
||||
Expect(re.Sort).Should(BeEquivalentTo(1))
|
||||
}
|
||||
if re.JSONKey == "domain" {
|
||||
Expect(re.Sort).Should(BeEquivalentTo(9))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("Test render without ui-schema", func() {
|
||||
addonName := "test-without-schema"
|
||||
defaultSchema := []*utils.UIParameter{
|
||||
{
|
||||
JSONKey: "version",
|
||||
Sort: 3,
|
||||
},
|
||||
{
|
||||
JSONKey: "domain",
|
||||
Sort: 8,
|
||||
},
|
||||
}
|
||||
res := renderAddonCustomUISchema(ctx, k8sClient, addonName, defaultSchema)
|
||||
Expect(len(res)).Should(BeEquivalentTo(2))
|
||||
for _, re := range res {
|
||||
if re.JSONKey == "version" {
|
||||
Expect(re.Validate).Should(BeNil())
|
||||
Expect(re.Sort).Should(BeEquivalentTo(3))
|
||||
}
|
||||
if re.JSONKey == "domain" {
|
||||
Expect(re.Sort).Should(BeEquivalentTo(8))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
18
pkg/apiserver/rest/usecase/testdata/addon-uischema-test.yaml
vendored
Normal file
18
pkg/apiserver/rest/usecase/testdata/addon-uischema-test.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
- jsonKey: version
|
||||
validate:
|
||||
defaultValue: 1.2.0-rc1
|
||||
sort: 1
|
||||
- jsonKey: dbType
|
||||
label: DBType
|
||||
validate:
|
||||
defaultValue: kubeapi
|
||||
sort: 3
|
||||
- jsonKey: dbURL
|
||||
label: DBURL
|
||||
sort: 5
|
||||
- jsonKey: database
|
||||
sort: 7
|
||||
validate:
|
||||
defaultValue: kubevela
|
||||
- jsonKey: domain
|
||||
sort: 9
|
||||
@@ -62,6 +62,7 @@ func registerHandlers() {
|
||||
new(acrHandlerImpl).install()
|
||||
new(dockerHubHandlerImpl).install()
|
||||
new(harborHandlerImpl).install()
|
||||
new(jfrogHandlerImpl).install()
|
||||
}
|
||||
|
||||
type webhookHandler interface {
|
||||
@@ -160,6 +161,11 @@ func (c *webhookUsecaseImpl) HandleApplicationWebhook(ctx context.Context, token
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case model.PayloadTypeJFrog:
|
||||
handler, err = c.newJFrogHandler(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, bcode.ErrInvalidWebhookPayloadType
|
||||
}
|
||||
@@ -423,3 +429,74 @@ func (c *harborHandlerImpl) handle(ctx context.Context, webhookTrigger *model.Ap
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type jfrogHandlerImpl struct {
|
||||
req apisv1.HandleApplicationTriggerJFrogRequest
|
||||
w *webhookUsecaseImpl
|
||||
}
|
||||
|
||||
func (c *webhookUsecaseImpl) newJFrogHandler(req *restful.Request) (webhookHandler, error) {
|
||||
var jfrogReq apisv1.HandleApplicationTriggerJFrogRequest
|
||||
if err := req.ReadEntity(&jfrogReq); err != nil {
|
||||
return nil, bcode.ErrInvalidWebhookPayloadBody
|
||||
}
|
||||
if jfrogReq.Domain != model.JFrogDomainDocker || jfrogReq.EventType != model.JFrogEventTypePush {
|
||||
return nil, bcode.ErrInvalidWebhookPayloadBody
|
||||
}
|
||||
// jfrog should use request header to give URL, it is not exist in request body
|
||||
jfrogReq.Data.URL = req.HeaderParameter("X-JFrogURL")
|
||||
return &jfrogHandlerImpl{
|
||||
req: jfrogReq,
|
||||
w: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (j *jfrogHandlerImpl) handle(ctx context.Context, webhookTrigger *model.ApplicationTrigger, app *model.Application) (interface{}, error) {
|
||||
jfrogReq := j.req
|
||||
comp := &model.ApplicationComponent{
|
||||
AppPrimaryKey: webhookTrigger.AppPrimaryKey,
|
||||
}
|
||||
comps, err := j.w.ds.List(ctx, comp, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(comps) == 0 {
|
||||
return nil, bcode.ErrApplicationComponetNotExist
|
||||
}
|
||||
|
||||
// use the first component as the target component
|
||||
component := comps[0].(*model.ApplicationComponent)
|
||||
image := fmt.Sprintf("%s/%s:%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName, jfrogReq.Data.Tag)
|
||||
if jfrogReq.Data.URL != "" {
|
||||
image = fmt.Sprintf("%s/%s", jfrogReq.Data.URL, image)
|
||||
}
|
||||
if err := j.w.patchComponentProperties(ctx, component, &runtime.RawExtension{
|
||||
Raw: []byte(fmt.Sprintf(`{"image": "%s"}`, image)),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return j.w.applicationUsecase.Deploy(ctx, app, apisv1.ApplicationDeployRequest{
|
||||
WorkflowName: webhookTrigger.WorkflowName,
|
||||
Note: "triggered by webhook jfrog",
|
||||
TriggerType: apisv1.TriggerTypeWebhook,
|
||||
Force: true,
|
||||
ImageInfo: &model.ImageInfo{
|
||||
Type: model.PayloadTypeHarbor,
|
||||
Resource: &model.ImageResource{
|
||||
Digest: jfrogReq.Data.Digest,
|
||||
Tag: jfrogReq.Data.Tag,
|
||||
URL: image,
|
||||
},
|
||||
Repository: &model.ImageRepository{
|
||||
Name: jfrogReq.Data.ImageName,
|
||||
Namespace: jfrogReq.Data.RepoKey,
|
||||
FullName: fmt.Sprintf("%s/%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (j *jfrogHandlerImpl) install() {
|
||||
WebhookHandlers = append(WebhookHandlers, model.PayloadTypeJFrog)
|
||||
}
|
||||
|
||||
@@ -256,5 +256,45 @@ var _ = Describe("Test application usecase function", func() {
|
||||
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect((*comp.Properties)["image"]).Should(Equal("docker.io/test-namespace/test-repo:test-tag"))
|
||||
|
||||
By("Test HandleApplicationWebhook function with jfrog payload without header of X-JFrogURL")
|
||||
jfrogTrigger, err := appUsecase.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
|
||||
Name: "test-jfrog",
|
||||
PayloadType: "jfrog",
|
||||
Type: "webhook",
|
||||
ComponentName: "component-name-webhook",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
jfrogBody := apisv1.HandleApplicationTriggerJFrogRequest{
|
||||
Domain: "docker",
|
||||
EventType: "pushed",
|
||||
Data: apisv1.JFrogWebhookData{
|
||||
ImageName: "test-image",
|
||||
RepoKey: "test-repo",
|
||||
Digest: "test-digest",
|
||||
Tag: "test-tag",
|
||||
},
|
||||
}
|
||||
body, err = json.Marshal(jfrogBody)
|
||||
Expect(err).Should(BeNil())
|
||||
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
|
||||
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
|
||||
Expect(err).Should(BeNil())
|
||||
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), jfrogTrigger.Token, restful.NewRequest(httpreq))
|
||||
Expect(err).Should(BeNil())
|
||||
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect((*comp.Properties)["image"]).Should(Equal("test-repo/test-image:test-tag"))
|
||||
|
||||
By("Test HandleApplicationWebhook function with jfrog payload with header of X-JFrogURL")
|
||||
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
|
||||
Expect(err).Should(BeNil())
|
||||
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
|
||||
httpreq.Header.Add("X-JFrogURL", "test-addr")
|
||||
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), jfrogTrigger.Token, restful.NewRequest(httpreq))
|
||||
Expect(err).Should(BeNil())
|
||||
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect((*comp.Properties)["image"]).Should(Equal("test-addr/test-repo/test-image:test-tag"))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ func (p *Parser) ValidateCUESchematicAppfile(a *Appfile) error {
|
||||
}
|
||||
pCtx, err := newValidationProcessContext(wl, a.Name, a.AppRevisionName, a.Namespace)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "cannot create validationg process context")
|
||||
return errors.WithMessagef(err, "cannot create the validation process context of app=%s in namespace=%s", a.Name, a.Namespace)
|
||||
}
|
||||
for _, tr := range wl.Traits {
|
||||
if tr.CapabilityCategory != types.CUECategory {
|
||||
|
||||
@@ -80,4 +80,7 @@ type Args struct {
|
||||
|
||||
// OAMSpecVer is the oam spec version controller want to setup
|
||||
OAMSpecVer string
|
||||
|
||||
// EnableCompatibility indicates that will change some functions of controller to adapt to multiple platforms, such as asi.
|
||||
EnableCompatibility bool
|
||||
}
|
||||
|
||||
@@ -81,12 +81,17 @@ var (
|
||||
// Reconciler reconciles an Application object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
Recorder event.Recorder
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
Recorder event.Recorder
|
||||
options
|
||||
}
|
||||
|
||||
type options struct {
|
||||
appRevisionLimit int
|
||||
concurrentReconciles int
|
||||
disableStatusUpdate bool
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=core.oam.dev,resources=applications,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -121,6 +126,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
if annotations := app.GetAnnotations(); annotations == nil || annotations[oam.AnnotationKubeVelaVersion] == "" {
|
||||
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationKubeVelaVersion, version.VelaVersion)
|
||||
}
|
||||
logCtx.AddTag("publish_version", app.GetAnnotations()[oam.AnnotationKubeVelaVersion])
|
||||
|
||||
appParser := appfile.NewApplicationParser(r.Client, r.dm, r.pd)
|
||||
handler, err := NewAppHandler(logCtx, r, app, appParser)
|
||||
if err != nil {
|
||||
@@ -238,6 +245,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
if status := app.Status.Workflow; status != nil && status.Terminated {
|
||||
return r.result(nil).ret()
|
||||
}
|
||||
case common.WorkflowStateSkipping:
|
||||
logCtx.Info("Skip this reconcile")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
var phase = common.ApplicationRunning
|
||||
@@ -286,7 +296,7 @@ func (r *Reconciler) gcResourceTrackers(logCtx monitorContext.Context, handler *
|
||||
return r.endWithNegativeCondition(logCtx, handler.app, condition.ReconcileError(err), phase)
|
||||
}
|
||||
if !finished {
|
||||
logCtx.Info("GarbageCollecting resourcetrackers")
|
||||
logCtx.Info("GarbageCollecting resourcetrackers unfinished")
|
||||
cond := condition.Deleting()
|
||||
if len(waiting) > 0 {
|
||||
cond.Message = fmt.Sprintf("Waiting for %s to delete. (At least %d resources are deleting.)", waiting[0].DisplayName(), len(waiting))
|
||||
@@ -382,13 +392,31 @@ func (r *Reconciler) endWithNegativeCondition(ctx context.Context, app *v1beta1.
|
||||
func (r *Reconciler) patchStatus(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error {
|
||||
app.Status.Phase = phase
|
||||
updateObservedGeneration(app)
|
||||
return r.Status().Patch(ctx, app, client.Merge)
|
||||
if err := r.Status().Patch(ctx, app, client.Merge); err != nil {
|
||||
// set to -1 to re-run workflow if status is failed to patch
|
||||
workflow.StepStatusCache.Store(fmt.Sprintf("%s-%s", app.Name, app.Namespace), -1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) updateStatus(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error {
|
||||
app.Status.Phase = phase
|
||||
updateObservedGeneration(app)
|
||||
return r.Status().Update(ctx, app)
|
||||
|
||||
if !r.disableStatusUpdate {
|
||||
return r.Status().Update(ctx, app)
|
||||
}
|
||||
obj, err := app.Unstructured()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.Status().Update(ctx, obj); err != nil {
|
||||
// set to -1 to re-run workflow if status is failed to update
|
||||
workflow.StepStatusCache.Store(fmt.Sprintf("%s-%s", app.Name, app.Namespace), -1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) doWorkflowFinish(app *v1beta1.Application, wf workflow.Workflow) error {
|
||||
@@ -501,13 +529,12 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// Setup adds a controller that reconciles AppRollout.
|
||||
func Setup(mgr ctrl.Manager, args core.Args) error {
|
||||
reconciler := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: event.NewAPIRecorder(mgr.GetEventRecorderFor("Application")),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
appRevisionLimit: args.AppRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: event.NewAPIRecorder(mgr.GetEventRecorderFor("Application")),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return reconciler.SetupWithManager(mgr)
|
||||
}
|
||||
@@ -557,3 +584,11 @@ func timeReconcile(app *v1beta1.Application) func() {
|
||||
metrics.ApplicationReconcileTimeHistogram.WithLabelValues(beginPhase, string(app.Status.Phase)).Observe(v)
|
||||
}
|
||||
}
|
||||
|
||||
func parseOptions(args core.Args) options {
|
||||
return options{
|
||||
disableStatusUpdate: args.EnableCompatibility,
|
||||
appRevisionLimit: args.AppRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ func ComputeAppRevisionHash(appRevision *v1beta1.ApplicationRevision) (string, e
|
||||
}
|
||||
appRevisionHash.ComponentDefinitionHash[key] = hash
|
||||
}
|
||||
for key, td := range appRevision.Spec.TraitDefinitions {
|
||||
for key, td := range filterSkipAffectAppRevTraitDefinitions(appRevision.Spec.TraitDefinitions) {
|
||||
hash, err := utils.ComputeSpecHash(&td.Spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -376,7 +376,11 @@ func (h *AppHandler) currentAppRevIsNew(ctx context.Context) (bool, bool, error)
|
||||
|
||||
// diff the latest revision first
|
||||
if h.app.Status.LatestRevision.RevisionHash == h.currentRevHash && DeepEqualRevision(h.latestAppRev, h.currentAppRev) {
|
||||
appSpec := h.currentAppRev.Spec.Application.Spec
|
||||
traitDef := h.currentAppRev.Spec.TraitDefinitions
|
||||
h.currentAppRev = h.latestAppRev.DeepCopy()
|
||||
h.currentAppRev.Spec.Application.Spec = appSpec
|
||||
h.currentAppRev.Spec.TraitDefinitions = traitDef
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
@@ -409,7 +413,9 @@ func DeepEqualRevision(old, new *v1beta1.ApplicationRevision) bool {
|
||||
if len(old.Spec.WorkloadDefinitions) != len(new.Spec.WorkloadDefinitions) {
|
||||
return false
|
||||
}
|
||||
if len(old.Spec.TraitDefinitions) != len(new.Spec.TraitDefinitions) {
|
||||
oldTraitDefinitions := filterSkipAffectAppRevTraitDefinitions(old.Spec.TraitDefinitions)
|
||||
newTraitDefinitions := filterSkipAffectAppRevTraitDefinitions(new.Spec.TraitDefinitions)
|
||||
if len(oldTraitDefinitions) != len(newTraitDefinitions) {
|
||||
return false
|
||||
}
|
||||
if len(old.Spec.ComponentDefinitions) != len(new.Spec.ComponentDefinitions) {
|
||||
@@ -428,8 +434,8 @@ func DeepEqualRevision(old, new *v1beta1.ApplicationRevision) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for key, td := range new.Spec.TraitDefinitions {
|
||||
if !apiequality.Semantic.DeepEqual(old.Spec.TraitDefinitions[key].Spec, td.Spec) {
|
||||
for key, td := range newTraitDefinitions {
|
||||
if !apiequality.Semantic.DeepEqual(oldTraitDefinitions[key].Spec, td.Spec) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -820,6 +826,17 @@ func filterSkipAffectAppRevTrait(appSpec v1beta1.ApplicationSpec, tds map[string
|
||||
return *res
|
||||
}
|
||||
|
||||
// before computing hash or deepEqual, filterSkipAffectAppRevTraitDefinitions filter can ignore `SkipAffectRevision` trait definition from appRev
|
||||
func filterSkipAffectAppRevTraitDefinitions(tds map[string]v1beta1.TraitDefinition) map[string]v1beta1.TraitDefinition {
|
||||
res := make(map[string]v1beta1.TraitDefinition)
|
||||
for key, td := range tds {
|
||||
if !td.Spec.SkipRevisionAffect {
|
||||
res[key] = td
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type historiesByRevision []v1beta1.ApplicationRevision
|
||||
|
||||
func (h historiesByRevision) Len() int { return len(h) }
|
||||
@@ -837,7 +854,7 @@ func cleanUpWorkflowComponentRevision(ctx context.Context, h *AppHandler) error
|
||||
}
|
||||
// collect component revision in use
|
||||
compRevisionInUse := map[string]map[string]struct{}{}
|
||||
for _, resource := range h.app.Status.AppliedResources {
|
||||
for i, resource := range h.app.Status.AppliedResources {
|
||||
compName := resource.Name
|
||||
ns := resource.Namespace
|
||||
r := &unstructured.Unstructured{}
|
||||
@@ -846,7 +863,7 @@ func cleanUpWorkflowComponentRevision(ctx context.Context, h *AppHandler) error
|
||||
err := h.r.Get(_ctx, ktypes.NamespacedName{Name: compName, Namespace: ns}, r)
|
||||
notFound := apierrors.IsNotFound(err)
|
||||
if err != nil && !notFound {
|
||||
return err
|
||||
return errors.WithMessagef(err, "get applied resource index=%d", i)
|
||||
}
|
||||
if compRevisionInUse[compName] == nil {
|
||||
compRevisionInUse[compName] = map[string]struct{}{}
|
||||
|
||||
@@ -151,7 +151,6 @@ var _ = Describe("test generate revision ", func() {
|
||||
appRevision1.Spec.ComponentDefinitions[cd.Name] = cd
|
||||
appRevision1.Spec.WorkloadDefinitions[wd.Name] = wd
|
||||
appRevision1.Spec.TraitDefinitions[td.Name] = td
|
||||
appRevision1.Spec.TraitDefinitions[rolloutTd.Name] = rolloutTd
|
||||
appRevision1.Spec.ScopeDefinitions[sd.Name] = sd
|
||||
|
||||
appRevision2 = *appRevision1.DeepCopy()
|
||||
@@ -168,6 +167,10 @@ var _ = Describe("test generate revision ", func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), &ns)).Should(Succeed())
|
||||
})
|
||||
|
||||
verifyDeepEqualRevision := func() {
|
||||
Expect(DeepEqualRevision(&appRevision1, &appRevision2)).Should(BeTrue())
|
||||
}
|
||||
|
||||
verifyEqual := func() {
|
||||
appHash1, err := ComputeAppRevisionHash(&appRevision1)
|
||||
Expect(err).Should(Succeed())
|
||||
@@ -228,7 +231,7 @@ var _ = Describe("test generate revision ", func() {
|
||||
verifyNotEqual()
|
||||
})
|
||||
|
||||
It("Test appliction contain a SkipAppRevision tait will have same hash", func() {
|
||||
It("Test appliction contain a SkipAppRevision tait will have same hash and revision will equal", func() {
|
||||
rolloutTrait := common.ApplicationTrait{
|
||||
Type: "rollout",
|
||||
Properties: &runtime.RawExtension{
|
||||
@@ -236,7 +239,10 @@ var _ = Describe("test generate revision ", func() {
|
||||
},
|
||||
}
|
||||
appRevision2.Spec.Application.Spec.Components[0].Traits = append(appRevision2.Spec.Application.Spec.Components[0].Traits, rolloutTrait)
|
||||
// appRevision will have no traitDefinition of rollout
|
||||
appRevision2.Spec.TraitDefinitions[rolloutTd.Name] = rolloutTd
|
||||
verifyEqual()
|
||||
verifyDeepEqualRevision()
|
||||
})
|
||||
|
||||
It("Test apply success for none rollout case", func() {
|
||||
|
||||
@@ -137,13 +137,14 @@ var _ = BeforeSuite(func(done Done) {
|
||||
appParser = appfile.NewApplicationParser(k8sClient, dm, pd)
|
||||
|
||||
reconciler = &Reconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: testScheme,
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
Recorder: event.NewAPIRecorder(recorder),
|
||||
appRevisionLimit: appRevisionLimit,
|
||||
Client: k8sClient,
|
||||
Scheme: testScheme,
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
Recorder: event.NewAPIRecorder(recorder),
|
||||
}
|
||||
|
||||
reconciler.appRevisionLimit = appRevisionLimit
|
||||
// setup the controller manager since we need the component handler to run in the background
|
||||
mgr, err = ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: testScheme,
|
||||
|
||||
@@ -205,12 +205,17 @@ func GetTerraformConfigurationFromRemote(name, remoteURL, remotePath string) (st
|
||||
return "", err
|
||||
}
|
||||
|
||||
tfPath := filepath.Join(tmpPath, remotePath, "main.tf")
|
||||
tfPath := filepath.Join(tmpPath, remotePath, "variables.tf")
|
||||
if _, err := os.Stat(tfPath); err != nil {
|
||||
tfPath = filepath.Join(tmpPath, remotePath, "main.tf")
|
||||
if _, err := os.Stat(tfPath); err != nil {
|
||||
return "", errors.Wrap(err, "failed to find main.tf or variables.tf in Terraform configurations of the remote repository")
|
||||
}
|
||||
}
|
||||
conf, err := ioutil.ReadFile(filepath.Clean(tfPath))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", errors.Wrap(err, "failed to read Terraform configuration")
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(tmpPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -382,20 +382,28 @@ func TestGetTerraformConfigurationFromRemote(t *testing.T) {
|
||||
// panic: permission denied
|
||||
type want struct {
|
||||
config string
|
||||
err error
|
||||
errMsg string
|
||||
}
|
||||
|
||||
type args struct {
|
||||
name string
|
||||
url string
|
||||
path string
|
||||
data []byte
|
||||
variableFile string
|
||||
// mockWorkingPath will create `/tmp/terraform`
|
||||
mockWorkingPath bool
|
||||
}
|
||||
cases := map[string]struct {
|
||||
name string
|
||||
url string
|
||||
path string
|
||||
data []byte
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"valid": {
|
||||
name: "valid",
|
||||
url: "https://github.com/kubevela-contrib/terraform-modules.git",
|
||||
path: "",
|
||||
data: []byte(`
|
||||
args: args{
|
||||
name: "valid",
|
||||
url: "https://github.com/kubevela-contrib/terraform-modules.git",
|
||||
path: "",
|
||||
data: []byte(`
|
||||
variable "aaa" {
|
||||
type = list(object({
|
||||
type = string
|
||||
@@ -404,6 +412,8 @@ variable "aaa" {
|
||||
}))
|
||||
default = []
|
||||
}`),
|
||||
variableFile: "main.tf",
|
||||
},
|
||||
want: want{
|
||||
config: `
|
||||
variable "aaa" {
|
||||
@@ -414,28 +424,51 @@ variable "aaa" {
|
||||
}))
|
||||
default = []
|
||||
}`,
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
"working path exists": {
|
||||
args: args{
|
||||
variableFile: "main.tf",
|
||||
mockWorkingPath: true,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "failed to remove the directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.args.mockWorkingPath {
|
||||
err := os.MkdirAll("./tmp/terraform", 0755)
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll("./tmp/terraform")
|
||||
patch1 := ApplyFunc(os.Remove, func(_ string) error {
|
||||
return errors.New("failed")
|
||||
})
|
||||
defer patch1.Reset()
|
||||
patch2 := ApplyFunc(os.Open, func(_ string) (*os.File, error) {
|
||||
return nil, errors.New("failed")
|
||||
})
|
||||
defer patch2.Reset()
|
||||
}
|
||||
|
||||
patch := ApplyFunc(git.PlainCloneContext, func(ctx context.Context, path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) {
|
||||
tmpPath := filepath.Join("./tmp/terraform", tc.name)
|
||||
tmpPath := filepath.Join("./tmp/terraform", tc.args.name)
|
||||
err := os.MkdirAll(tmpPath, os.ModePerm)
|
||||
assert.NilError(t, err)
|
||||
err = ioutil.WriteFile(filepath.Clean(filepath.Join(tmpPath, "main.tf")), tc.data, 0644)
|
||||
err = ioutil.WriteFile(filepath.Clean(filepath.Join(tmpPath, tc.args.variableFile)), tc.args.data, 0644)
|
||||
assert.NilError(t, err)
|
||||
return nil, nil
|
||||
})
|
||||
defer patch.Reset()
|
||||
|
||||
conf, err := GetTerraformConfigurationFromRemote(tc.name, tc.url, tc.path)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nGetTerraformConfigurationFromRemote(...): -want error, +got error:\n%s", name, diff)
|
||||
}
|
||||
if tc.want.err == nil {
|
||||
conf, err := GetTerraformConfigurationFromRemote(tc.args.name, tc.args.url, tc.args.path)
|
||||
if tc.want.errMsg != "" {
|
||||
if err != nil && !strings.Contains(err.Error(), tc.want.errMsg) {
|
||||
t.Errorf("\n%s\nGetTerraformConfigurationFromRemote(...): -want error %v, +got error:%s", name, err, tc.want.errMsg)
|
||||
}
|
||||
} else {
|
||||
assert.Equal(t, tc.want.config, conf)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -22,17 +22,34 @@ import (
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/build"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
)
|
||||
|
||||
// GetParameters get parameter from cue template
|
||||
func GetParameters(templateStr string) ([]types.Parameter, error) {
|
||||
r := cue.Runtime{}
|
||||
template, err := r.Compile("", templateStr+BaseTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func GetParameters(templateStr string, pd *packages.PackageDiscover) ([]types.Parameter, error) {
|
||||
var template *cue.Instance
|
||||
var err error
|
||||
if pd != nil {
|
||||
bi := build.NewContext().NewInstance("", nil)
|
||||
err := bi.AddFile("-", templateStr+BaseTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template, err = pd.ImportPackagesAndBuildInstance(bi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
r := cue.Runtime{}
|
||||
template, err = r.Compile("", templateStr+BaseTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
tempStruct, err := template.Value().Struct()
|
||||
if err != nil {
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
func TestGetParameter(t *testing.T) {
|
||||
data, _ := os.ReadFile("testdata/workloads/metrics.cue")
|
||||
params, err := GetParameters(string(data))
|
||||
params, err := GetParameters(string(data), nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, params, []types.Parameter{
|
||||
{Name: "format", Required: false, Default: "prometheus", Usage: "format of the metrics, " +
|
||||
@@ -38,7 +38,7 @@ func TestGetParameter(t *testing.T) {
|
||||
{Name: "selector", Required: false, Usage: "the label selector for the pods, default is the workload labels", Type: cue.StructKind},
|
||||
})
|
||||
data, _ = os.ReadFile("testdata/workloads/deployment.cue")
|
||||
params, err = GetParameters(string(data))
|
||||
params, err = GetParameters(string(data), nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []types.Parameter{
|
||||
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
|
||||
@@ -50,7 +50,7 @@ func TestGetParameter(t *testing.T) {
|
||||
params)
|
||||
|
||||
data, _ = os.ReadFile("testdata/workloads/test-param.cue")
|
||||
params, err = GetParameters(string(data))
|
||||
params, err = GetParameters(string(data), nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []types.Parameter{
|
||||
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
|
||||
@@ -61,13 +61,13 @@ func TestGetParameter(t *testing.T) {
|
||||
{Name: "fval", Default: 64.3, Type: cue.FloatKind},
|
||||
{Name: "nval", Default: float64(0), Required: true, Type: cue.NumberKind}}, params)
|
||||
data, _ = os.ReadFile("testdata/workloads/empty.cue")
|
||||
params, err = GetParameters(string(data))
|
||||
params, err = GetParameters(string(data), nil)
|
||||
assert.NoError(t, err)
|
||||
var exp []types.Parameter
|
||||
assert.Equal(t, exp, params)
|
||||
|
||||
data, _ = os.ReadFile("testdata/workloads/webservice.cue") // test cue parameter with "// +ignore" annotation
|
||||
params, err = GetParameters(string(data)) // Only test for func RetrieveComments
|
||||
params, err = GetParameters(string(data), nil) // Only test for func RetrieveComments
|
||||
assert.NoError(t, err)
|
||||
var flag bool
|
||||
for _, para := range params {
|
||||
|
||||
@@ -146,8 +146,17 @@ func PatchApplication(base *v1beta1.Application, patch *v1alpha1.EnvPatch, selec
|
||||
var errs errors2.ErrorList
|
||||
var err error
|
||||
for _, comp := range patch.Components {
|
||||
if baseComp, exists := compMaps[comp.Name]; exists {
|
||||
if baseComp.Type != comp.Type {
|
||||
if comp.Name == "" {
|
||||
for compName, baseComp := range compMaps {
|
||||
if comp.Type == "" || comp.Type == baseComp.Type {
|
||||
compMaps[compName], err = MergeComponent(baseComp, comp.DeepCopy())
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to merge component %s", compName))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if baseComp, exists := compMaps[comp.Name]; exists {
|
||||
if baseComp.Type != comp.Type && comp.Type != "" {
|
||||
compMaps[comp.Name] = comp.ToApplicationComponent()
|
||||
} else {
|
||||
compMaps[comp.Name], err = MergeComponent(baseComp, comp.DeepCopy())
|
||||
|
||||
@@ -294,6 +294,240 @@ func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"patch-all-comp": {
|
||||
baseApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "express-server",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a.example.com",
|
||||
}),
|
||||
}, {
|
||||
Type: "ingress-1-20",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a-1-20.example.com",
|
||||
}),
|
||||
}},
|
||||
}, {
|
||||
Name: "express-worker",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
}},
|
||||
},
|
||||
},
|
||||
envName: "prod",
|
||||
envPatch: v1alpha1.EnvPatch{
|
||||
Components: []v1alpha1.EnvComponentPatch{{
|
||||
Name: "",
|
||||
Type: "",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []v1alpha1.EnvTraitPatch{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
expectedApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "express-server",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}, {
|
||||
Type: "ingress-1-20",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a-1-20.example.com",
|
||||
}),
|
||||
}},
|
||||
}, {
|
||||
Name: "express-worker",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"patch-type-comp": {
|
||||
baseApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "express-server",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a.example.com",
|
||||
}),
|
||||
}, {
|
||||
Type: "ingress-1-20",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a-1-20.example.com",
|
||||
}),
|
||||
}},
|
||||
}, {
|
||||
Name: "express-worker",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
}},
|
||||
},
|
||||
},
|
||||
envName: "prod",
|
||||
envPatch: v1alpha1.EnvPatch{
|
||||
Components: []v1alpha1.EnvComponentPatch{{
|
||||
Name: "",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []v1alpha1.EnvTraitPatch{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
expectedApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "express-server",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}, {
|
||||
Type: "ingress-1-20",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a-1-20.example.com",
|
||||
}),
|
||||
}},
|
||||
}, {
|
||||
Name: "express-worker",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"patch-without-type-specified": {
|
||||
baseApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "express-server",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a.example.com",
|
||||
}),
|
||||
}, {
|
||||
Type: "ingress-1-20",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a-1-20.example.com",
|
||||
}),
|
||||
}},
|
||||
}, {
|
||||
Name: "express-worker",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
}},
|
||||
},
|
||||
},
|
||||
envName: "prod",
|
||||
envPatch: v1alpha1.EnvPatch{
|
||||
Components: []v1alpha1.EnvComponentPatch{{
|
||||
Name: "express-worker",
|
||||
Type: "",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []v1alpha1.EnvTraitPatch{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
expectedApp: &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "express-server",
|
||||
Type: "webservice",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "crccheck/hello-world",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a.example.com",
|
||||
}),
|
||||
}, {
|
||||
Type: "ingress-1-20",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "a-1-20.example.com",
|
||||
}),
|
||||
}},
|
||||
}, {
|
||||
Name: "express-worker",
|
||||
Type: "worker",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"image": "busybox",
|
||||
}),
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "ingress",
|
||||
Properties: util.Object2RawExtension(map[string]interface{}{
|
||||
"domain": "b.example.com",
|
||||
}),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
}
|
||||
|
||||
#Component: {
|
||||
name: string
|
||||
type: string
|
||||
name?: string
|
||||
type?: string
|
||||
properties?: {...}
|
||||
traits?: [...{
|
||||
type: string
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/ast"
|
||||
"cuelang.org/go/cue/build"
|
||||
"cuelang.org/go/cue/format"
|
||||
"cuelang.org/go/encoding/openapi"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -62,6 +63,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
@@ -146,11 +148,26 @@ func HTTPGet(ctx context.Context, url string) ([]byte, error) {
|
||||
}
|
||||
|
||||
// GetCUEParameterValue converts definitions to cue format
|
||||
func GetCUEParameterValue(cueStr string) (cue.Value, error) {
|
||||
r := cue.Runtime{}
|
||||
template, err := r.Compile("", cueStr+velacue.BaseTemplate)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
func GetCUEParameterValue(cueStr string, pd *packages.PackageDiscover) (cue.Value, error) {
|
||||
var template *cue.Instance
|
||||
var err error
|
||||
if pd != nil {
|
||||
bi := build.NewContext().NewInstance("", nil)
|
||||
err := bi.AddFile("-", cueStr+velacue.BaseTemplate)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
|
||||
template, err = pd.ImportPackagesAndBuildInstance(bi)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
} else {
|
||||
r := cue.Runtime{}
|
||||
template, err = r.Compile("", cueStr+velacue.BaseTemplate)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
}
|
||||
tempStruct, err := template.Value().Struct()
|
||||
if err != nil {
|
||||
|
||||
@@ -127,7 +127,7 @@ output: {
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, err := GetCUEParameterValue(tc.cueStr)
|
||||
_, err := GetCUEParameterValue(tc.cueStr, nil)
|
||||
if tc.want.err != nil {
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nGenOpenAPIFromFile(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
@@ -162,7 +162,7 @@ name
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, err := GetCUEParameterValue(tc.cueStr)
|
||||
_, err := GetCUEParameterValue(tc.cueStr, nil)
|
||||
if diff := cmp.Diff(tc.want.errMsg, err.Error(), test.EquateConditions()); diff != "" {
|
||||
t.Errorf("\n%s\nGenOpenAPIFromFile(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
|
||||
@@ -85,11 +85,24 @@ func (c ViewContext) GetMutableValue(paths ...string) string {
|
||||
func (c ViewContext) SetMutableValue(data string, paths ...string) {
|
||||
}
|
||||
|
||||
// IncreaseMutableCountValue increase mutable count in workflow context.
|
||||
func (c ViewContext) IncreaseMutableCountValue(paths ...string) int {
|
||||
// IncreaseCountValueInMemory increase count in workflow context memory store.
|
||||
func (c ViewContext) IncreaseCountValueInMemory(paths ...string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetValueInMemory set data in workflow context memory store.
|
||||
func (c ViewContext) SetValueInMemory(data interface{}, paths ...string) {
|
||||
}
|
||||
|
||||
// GetValueInMemory get data in workflow context memory store.
|
||||
func (c ViewContext) GetValueInMemory(paths ...string) (interface{}, bool) {
|
||||
return "", true
|
||||
}
|
||||
|
||||
// DeleteValueInMemory delete data in workflow context memory store.
|
||||
func (c ViewContext) DeleteValueInMemory(paths ...string) {
|
||||
}
|
||||
|
||||
// DeleteMutableValue delete mutable data in workflow context.
|
||||
func (c ViewContext) DeleteMutableValue(paths ...string) {
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
@@ -48,13 +48,18 @@ const (
|
||||
AnnotationStartTimestamp = "vela.io/startTime"
|
||||
)
|
||||
|
||||
var (
|
||||
workflowMemoryCache sync.Map
|
||||
)
|
||||
|
||||
// WorkflowContext is workflow context.
|
||||
type WorkflowContext struct {
|
||||
cli client.Client
|
||||
store *corev1.ConfigMap
|
||||
components map[string]*ComponentManifest
|
||||
vars *value.Value
|
||||
modified bool
|
||||
cli client.Client
|
||||
store *corev1.ConfigMap
|
||||
memoryStore *sync.Map
|
||||
components map[string]*ComponentManifest
|
||||
vars *value.Value
|
||||
modified bool
|
||||
}
|
||||
|
||||
// GetComponent Get ComponentManifest from workflow context.
|
||||
@@ -105,7 +110,7 @@ func (wf *WorkflowContext) SetVar(v *value.Value, paths ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStore get configmap of workflow context.
|
||||
// GetStore get store of workflow context.
|
||||
func (wf *WorkflowContext) GetStore() *corev1.ConfigMap {
|
||||
return wf.store
|
||||
}
|
||||
@@ -121,23 +126,6 @@ func (wf *WorkflowContext) SetMutableValue(data string, paths ...string) {
|
||||
wf.modified = true
|
||||
}
|
||||
|
||||
// IncreaseMutableCountValue increase mutable count in workflow context.
|
||||
func (wf *WorkflowContext) IncreaseMutableCountValue(paths ...string) int {
|
||||
c := wf.GetMutableValue(paths...)
|
||||
if c == "" {
|
||||
wf.SetMutableValue("0", paths...)
|
||||
return 0
|
||||
}
|
||||
count, err := strconv.Atoi(c)
|
||||
if err != nil {
|
||||
wf.SetMutableValue("0", paths...)
|
||||
return 0
|
||||
}
|
||||
count++
|
||||
wf.SetMutableValue(strconv.Itoa(count), paths...)
|
||||
return count
|
||||
}
|
||||
|
||||
// DeleteMutableValue delete mutable data in workflow context.
|
||||
func (wf *WorkflowContext) DeleteMutableValue(paths ...string) {
|
||||
key := strings.Join(paths, ".")
|
||||
@@ -147,6 +135,39 @@ func (wf *WorkflowContext) DeleteMutableValue(paths ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// IncreaseCountValueInMemory increase count in workflow context memory store.
|
||||
func (wf *WorkflowContext) IncreaseCountValueInMemory(paths ...string) int {
|
||||
key := strings.Join(paths, ".")
|
||||
c, ok := wf.memoryStore.Load(key)
|
||||
if !ok {
|
||||
wf.memoryStore.Store(key, 0)
|
||||
return 0
|
||||
}
|
||||
count, ok := c.(int)
|
||||
if !ok {
|
||||
wf.memoryStore.Store(key, 0)
|
||||
return 0
|
||||
}
|
||||
count++
|
||||
wf.memoryStore.Store(key, count)
|
||||
return count
|
||||
}
|
||||
|
||||
// SetValueInMemory set data in workflow context memory store.
|
||||
func (wf *WorkflowContext) SetValueInMemory(data interface{}, paths ...string) {
|
||||
wf.memoryStore.Store(strings.Join(paths, "."), data)
|
||||
}
|
||||
|
||||
// GetValueInMemory get data in workflow context memory store.
|
||||
func (wf *WorkflowContext) GetValueInMemory(paths ...string) (interface{}, bool) {
|
||||
return wf.memoryStore.Load(strings.Join(paths, "."))
|
||||
}
|
||||
|
||||
// DeleteValueInMemory delete data in workflow context memory store.
|
||||
func (wf *WorkflowContext) DeleteValueInMemory(paths ...string) {
|
||||
wf.memoryStore.Delete(strings.Join(paths, "."))
|
||||
}
|
||||
|
||||
// MakeParameter make 'value' with interface{}
|
||||
func (wf *WorkflowContext) MakeParameter(parameter interface{}) (*value.Value, error) {
|
||||
var s = "{}"
|
||||
@@ -317,6 +338,11 @@ func NewContext(cli client.Client, ns, app string, appUID types.UID) (Context, e
|
||||
return wfCtx, wfCtx.Commit()
|
||||
}
|
||||
|
||||
// CleanupMemoryStore cleans up memory store.
|
||||
func CleanupMemoryStore(app, ns string) {
|
||||
workflowMemoryCache.Delete(fmt.Sprintf("%s-%s", app, ns))
|
||||
}
|
||||
|
||||
func newContext(cli client.Client, ns, app string, appUID types.UID) (*WorkflowContext, error) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
@@ -347,11 +373,13 @@ func newContext(cli client.Client, ns, app string, appUID types.UID) (*WorkflowC
|
||||
store.Annotations = map[string]string{
|
||||
AnnotationStartTimestamp: time.Now().String(),
|
||||
}
|
||||
memCache := getMemoryStore(fmt.Sprintf("%s-%s", app, ns))
|
||||
wfCtx := &WorkflowContext{
|
||||
cli: cli,
|
||||
store: &store,
|
||||
components: map[string]*ComponentManifest{},
|
||||
modified: true,
|
||||
cli: cli,
|
||||
store: &store,
|
||||
memoryStore: memCache,
|
||||
components: map[string]*ComponentManifest{},
|
||||
modified: true,
|
||||
}
|
||||
var err error
|
||||
wfCtx.vars, err = value.NewValue("", nil, "")
|
||||
@@ -359,6 +387,20 @@ func newContext(cli client.Client, ns, app string, appUID types.UID) (*WorkflowC
|
||||
return wfCtx, err
|
||||
}
|
||||
|
||||
func getMemoryStore(key string) *sync.Map {
|
||||
memCache := &sync.Map{}
|
||||
mc, ok := workflowMemoryCache.Load(key)
|
||||
if !ok {
|
||||
workflowMemoryCache.Store(key, memCache)
|
||||
} else {
|
||||
memCache, ok = mc.(*sync.Map)
|
||||
if !ok {
|
||||
workflowMemoryCache.Store(key, memCache)
|
||||
}
|
||||
}
|
||||
return memCache
|
||||
}
|
||||
|
||||
// LoadContext load workflow context from store.
|
||||
func LoadContext(cli client.Client, ns, app string) (Context, error) {
|
||||
var store corev1.ConfigMap
|
||||
@@ -372,9 +414,11 @@ func LoadContext(cli client.Client, ns, app string) (Context, error) {
|
||||
}, &store); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memCache := getMemoryStore(fmt.Sprintf("%s-%s", app, ns))
|
||||
ctx := &WorkflowContext{
|
||||
cli: cli,
|
||||
store: &store,
|
||||
cli: cli,
|
||||
store: &store,
|
||||
memoryStore: memCache,
|
||||
}
|
||||
if err := ctx.LoadFromConfigMap(store); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -275,14 +275,33 @@ func TestMutableValue(t *testing.T) {
|
||||
wfCtx.DeleteMutableValue("test", "key")
|
||||
v = wfCtx.GetMutableValue("test", "key")
|
||||
r.Equal(v, "")
|
||||
}
|
||||
|
||||
wfCtx.SetMutableValue("value", "test", "key")
|
||||
count := wfCtx.IncreaseMutableCountValue("test", "key")
|
||||
func TestMemoryValue(t *testing.T) {
|
||||
cli := newCliForTest(t, nil)
|
||||
r := require.New(t)
|
||||
|
||||
wfCtx, err := NewContext(cli, "default", "app-v1", "testuid")
|
||||
r.NoError(err)
|
||||
err = wfCtx.Commit()
|
||||
r.NoError(err)
|
||||
|
||||
wfCtx.SetValueInMemory("value", "test", "key")
|
||||
v, ok := wfCtx.GetValueInMemory("test", "key")
|
||||
r.Equal(ok, true)
|
||||
r.Equal(v.(string), "value")
|
||||
|
||||
wfCtx.DeleteValueInMemory("test", "key")
|
||||
_, ok = wfCtx.GetValueInMemory("test", "key")
|
||||
r.Equal(ok, false)
|
||||
|
||||
wfCtx.SetValueInMemory("value", "test", "key")
|
||||
count := wfCtx.IncreaseCountValueInMemory("test", "key")
|
||||
r.Equal(count, 0)
|
||||
count = wfCtx.IncreaseMutableCountValue("notfound", "key")
|
||||
count = wfCtx.IncreaseCountValueInMemory("notfound", "key")
|
||||
r.Equal(count, 0)
|
||||
wfCtx.SetMutableValue("10", "number", "key")
|
||||
count = wfCtx.IncreaseMutableCountValue("number", "key")
|
||||
wfCtx.SetValueInMemory(10, "number", "key")
|
||||
count = wfCtx.IncreaseCountValueInMemory("number", "key")
|
||||
r.Equal(count, 11)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,11 @@ type Context interface {
|
||||
GetStore() *corev1.ConfigMap
|
||||
GetMutableValue(path ...string) string
|
||||
SetMutableValue(data string, path ...string)
|
||||
IncreaseMutableCountValue(paths ...string) int
|
||||
DeleteMutableValue(paths ...string)
|
||||
IncreaseCountValueInMemory(paths ...string) int
|
||||
SetValueInMemory(data interface{}, paths ...string)
|
||||
GetValueInMemory(paths ...string) (interface{}, bool)
|
||||
DeleteValueInMemory(paths ...string)
|
||||
Commit() error
|
||||
MakeParameter(parameter interface{}) (*value.Value, error)
|
||||
StoreRef() *corev1.ObjectReference
|
||||
|
||||
@@ -33,9 +33,6 @@ type Workflow interface {
|
||||
// Trace record workflow state in controllerRevision.
|
||||
Trace() error
|
||||
|
||||
// CleanupCountersInContext cleans up the temporary counters in workflow context.
|
||||
CleanupCountersInContext(ctx monitorContext.Context)
|
||||
|
||||
// GetBackoffWaitTime returns the wait time for next retry.
|
||||
GetBackoffWaitTime() time.Duration
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ func (exec *executor) err(ctx wfContext.Context, err error, reason string) {
|
||||
}
|
||||
|
||||
func (exec *executor) checkErrorTimes(ctx wfContext.Context) {
|
||||
times := ctx.IncreaseMutableCountValue(wfTypes.ContextPrefixFailedTimes, exec.wfStatus.ID)
|
||||
times := ctx.IncreaseCountValueInMemory(wfTypes.ContextPrefixFailedTimes, exec.wfStatus.ID)
|
||||
if times >= MaxErrorTimes {
|
||||
exec.wait = false
|
||||
exec.failedAfterRetries = true
|
||||
|
||||
@@ -243,6 +243,7 @@ close({
|
||||
r.Equal(operation.Waiting, true)
|
||||
r.Equal(status.Phase, common.WorkflowStepPhaseFailed)
|
||||
case "failed-after-retries":
|
||||
wfContext.CleanupMemoryStore("app-v1", "default")
|
||||
newCtx := newWorkflowContextForTest(t)
|
||||
for i := 0; i < MaxErrorTimes; i++ {
|
||||
status, operation, err = run.Run(newCtx, &types.TaskRunOptions{})
|
||||
|
||||
@@ -20,8 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -45,6 +44,8 @@ import (
|
||||
var (
|
||||
// DisableRecorder optimize workflow by disable recorder
|
||||
DisableRecorder = false
|
||||
// StepStatusCache cache the step status
|
||||
StepStatusCache sync.Map
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -123,11 +124,16 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
|
||||
}
|
||||
}
|
||||
w.app.Status.Conditions = reservedConditions
|
||||
StepStatusCache.Delete(fmt.Sprintf("%s-%s", w.app.Name, w.app.Namespace))
|
||||
wfContext.CleanupMemoryStore(w.app.Name, w.app.Namespace)
|
||||
return common.WorkflowStateInitializing, nil
|
||||
}
|
||||
|
||||
wfStatus := w.app.Status.Workflow
|
||||
cacheKey := fmt.Sprintf("%s-%s", w.app.Name, w.app.Namespace)
|
||||
|
||||
if wfStatus.Finished {
|
||||
StepStatusCache.Delete(cacheKey)
|
||||
return common.WorkflowStateFinished, nil
|
||||
}
|
||||
if wfStatus.Terminated {
|
||||
@@ -148,7 +154,13 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
|
||||
return common.WorkflowStateExecuting, err
|
||||
}
|
||||
w.wfCtx = wfCtx
|
||||
w.checkDuplicateID(ctx)
|
||||
|
||||
if cacheValue, ok := StepStatusCache.Load(cacheKey); ok {
|
||||
// handle cache resource
|
||||
if len(wfStatus.Steps) < cacheValue.(int) {
|
||||
return common.WorkflowStateSkipping, nil
|
||||
}
|
||||
}
|
||||
|
||||
e := &engine{
|
||||
status: wfStatus,
|
||||
@@ -161,17 +173,19 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
|
||||
err = e.run(taskRunners)
|
||||
if err != nil {
|
||||
ctx.Error(err, "run steps")
|
||||
StepStatusCache.Store(cacheKey, len(wfStatus.Steps))
|
||||
wfStatus.Message = string(common.WorkflowStateExecuting)
|
||||
return common.WorkflowStateExecuting, err
|
||||
}
|
||||
|
||||
e.checkWorkflowStatusMessage(wfStatus)
|
||||
StepStatusCache.Store(cacheKey, len(wfStatus.Steps))
|
||||
if wfStatus.Terminated {
|
||||
w.CleanupCountersInContext(ctx)
|
||||
wfContext.CleanupMemoryStore(e.app.Name, e.app.Namespace)
|
||||
return common.WorkflowStateTerminated, nil
|
||||
}
|
||||
if wfStatus.Suspend {
|
||||
w.CleanupCountersInContext(ctx)
|
||||
wfContext.CleanupMemoryStore(e.app.Name, e.app.Namespace)
|
||||
return common.WorkflowStateSuspended, nil
|
||||
}
|
||||
if w.allDone(taskRunners) {
|
||||
@@ -194,31 +208,13 @@ func (w *workflow) Trace() error {
|
||||
return recorder.With(w.cli, w.app).Save("", data).Limit(10).Error()
|
||||
}
|
||||
|
||||
func (w *workflow) CleanupCountersInContext(ctx monitorContext.Context) {
|
||||
ctxCM := w.wfCtx.GetStore()
|
||||
|
||||
for k := range ctxCM.Data {
|
||||
if strings.HasPrefix(k, wfTypes.ContextPrefixFailedTimes) ||
|
||||
strings.HasPrefix(k, wfTypes.ContextPrefixBackoffTimes) ||
|
||||
strings.HasPrefix(k, wfTypes.ContextKeyLastExecuteTime) ||
|
||||
strings.HasPrefix(k, wfTypes.ContextKeyNextExecuteTime) {
|
||||
delete(ctxCM.Data, k)
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.wfCtx.Commit(); err != nil {
|
||||
ctx.Error(err, "failed to commit workflow context", "application", w.app.Name, "config map", ctxCM.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (w *workflow) GetBackoffWaitTime() time.Duration {
|
||||
nextTime := w.wfCtx.GetMutableValue(wfTypes.ContextKeyNextExecuteTime)
|
||||
if nextTime == "" {
|
||||
nextTime, ok := w.wfCtx.GetValueInMemory(wfTypes.ContextKeyNextExecuteTime)
|
||||
if !ok {
|
||||
return time.Second
|
||||
}
|
||||
unix, err := strconv.ParseInt(nextTime, 10, 64)
|
||||
if err != nil {
|
||||
unix, ok := nextTime.(int64)
|
||||
if !ok {
|
||||
return time.Second
|
||||
}
|
||||
next := time.Unix(unix, 0)
|
||||
@@ -285,32 +281,15 @@ func (w *workflow) setMetadataToContext(wfCtx wfContext.Context) error {
|
||||
return wfCtx.SetVar(metadata, wfTypes.ContextKeyMetadata)
|
||||
}
|
||||
|
||||
func (w *workflow) checkDuplicateID(ctx monitorContext.Context) {
|
||||
if len(w.app.Status.Workflow.Steps) > 0 {
|
||||
return
|
||||
}
|
||||
ctxCM := w.wfCtx.GetStore()
|
||||
found := false
|
||||
for k := range ctxCM.Data {
|
||||
if strings.HasPrefix(k, wfTypes.ContextPrefixBackoffTimes) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
w.CleanupCountersInContext(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func getBackoffWaitTime(wfCtx wfContext.Context) int {
|
||||
ctxCM := wfCtx.GetStore()
|
||||
func (e *engine) getBackoffWaitTime() int {
|
||||
// the default value of min times reaches the max workflow backoff wait time
|
||||
minTimes := 15
|
||||
found := false
|
||||
for k, v := range ctxCM.Data {
|
||||
if strings.HasPrefix(k, wfTypes.ContextPrefixBackoffTimes) {
|
||||
for _, step := range e.status.Steps {
|
||||
if v, ok := e.wfCtx.GetValueInMemory(wfTypes.ContextPrefixBackoffTimes, step.ID); ok {
|
||||
found = true
|
||||
times, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
times, ok := v.(int)
|
||||
if !ok {
|
||||
times = 0
|
||||
}
|
||||
if times < minTimes {
|
||||
@@ -333,19 +312,19 @@ func getBackoffWaitTime(wfCtx wfContext.Context) int {
|
||||
}
|
||||
|
||||
func (e *engine) setNextExecuteTime() {
|
||||
interval := getBackoffWaitTime(e.wfCtx)
|
||||
lastExecuteTime := e.wfCtx.GetMutableValue(wfTypes.ContextKeyLastExecuteTime)
|
||||
if lastExecuteTime == "" {
|
||||
interval := e.getBackoffWaitTime()
|
||||
lastExecuteTime, ok := e.wfCtx.GetValueInMemory(wfTypes.ContextKeyLastExecuteTime)
|
||||
if !ok {
|
||||
e.monitorCtx.Error(fmt.Errorf("failed to get last execute time"), "application", e.app.Name)
|
||||
}
|
||||
|
||||
last, err := strconv.ParseInt(lastExecuteTime, 10, 64)
|
||||
if err != nil {
|
||||
e.monitorCtx.Error(err, "failed to parse last execute time", "lastExecuteTime", lastExecuteTime)
|
||||
last, ok := lastExecuteTime.(int64)
|
||||
if !ok {
|
||||
e.monitorCtx.Error(fmt.Errorf("failed to parse last execute time to int64"), "lastExecuteTime", lastExecuteTime)
|
||||
}
|
||||
|
||||
next := last + int64(interval)
|
||||
e.wfCtx.SetMutableValue(strconv.FormatInt(next, 10), wfTypes.ContextKeyNextExecuteTime)
|
||||
e.wfCtx.SetValueInMemory(next, wfTypes.ContextKeyNextExecuteTime)
|
||||
if err := e.wfCtx.Commit(); err != nil {
|
||||
e.monitorCtx.Error(err, "failed to commit next execute time", "nextExecuteTime", next)
|
||||
}
|
||||
@@ -376,7 +355,7 @@ func (e *engine) runAsDAG(taskRunners []wfTypes.TaskRunner) error {
|
||||
}
|
||||
todoTasks = append(todoTasks, tRunner)
|
||||
} else {
|
||||
wfCtx.DeleteMutableValue(wfTypes.ContextPrefixBackoffTimes, stepID)
|
||||
wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, stepID)
|
||||
}
|
||||
}
|
||||
if done {
|
||||
@@ -461,7 +440,7 @@ func (e *engine) steps(taskRunners []wfTypes.TaskRunner) error {
|
||||
e.failedAfterRetries = e.failedAfterRetries || operation.FailedAfterRetries
|
||||
e.waiting = e.waiting || operation.Waiting
|
||||
if status.Phase != common.WorkflowStepPhaseSucceeded {
|
||||
wfCtx.IncreaseMutableCountValue(wfTypes.ContextPrefixBackoffTimes, status.ID)
|
||||
wfCtx.IncreaseCountValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID)
|
||||
if err := wfCtx.Commit(); err != nil {
|
||||
return errors.WithMessage(err, "commit workflow context")
|
||||
}
|
||||
@@ -471,7 +450,7 @@ func (e *engine) steps(taskRunners []wfTypes.TaskRunner) error {
|
||||
e.checkFailedAfterRetries()
|
||||
return nil
|
||||
}
|
||||
wfCtx.DeleteMutableValue(wfTypes.ContextPrefixBackoffTimes, status.ID)
|
||||
wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID)
|
||||
if err := wfCtx.Commit(); err != nil {
|
||||
return errors.WithMessage(err, "commit workflow context")
|
||||
}
|
||||
@@ -511,7 +490,7 @@ func (e *engine) updateStepStatus(status common.WorkflowStepStatus) {
|
||||
now = metav1.NewTime(time.Now())
|
||||
)
|
||||
|
||||
e.wfCtx.SetMutableValue(strconv.FormatInt(now.Unix(), 10), wfTypes.ContextKeyLastExecuteTime)
|
||||
e.wfCtx.SetValueInMemory(now.Unix(), wfTypes.ContextKeyLastExecuteTime)
|
||||
status.LastExecuteTime = now
|
||||
for i := range e.status.Steps {
|
||||
if e.status.Steps[i].Name == status.Name {
|
||||
|
||||
@@ -142,7 +142,6 @@ var _ = Describe("Test Workflow", func() {
|
||||
Phase: common.WorkflowStepPhaseSucceeded,
|
||||
}},
|
||||
})).Should(BeEquivalentTo(""))
|
||||
|
||||
})
|
||||
|
||||
It("Workflow test for failed after retries", func() {
|
||||
@@ -237,7 +236,6 @@ var _ = Describe("Test Workflow", func() {
|
||||
Phase: common.WorkflowStepPhaseSucceeded,
|
||||
}},
|
||||
})).Should(BeEquivalentTo(""))
|
||||
|
||||
})
|
||||
|
||||
It("Test get backoff time and clean", func() {
|
||||
@@ -251,40 +249,48 @@ var _ = Describe("Test Workflow", func() {
|
||||
wf := NewWorkflow(app, k8sClient, common.WorkflowModeDAG)
|
||||
_, err := wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
e := &engine{
|
||||
status: app.Status.Workflow,
|
||||
wfCtx: wfCtx,
|
||||
}
|
||||
interval := e.getBackoffWaitTime()
|
||||
Expect(interval).Should(BeEquivalentTo(minWorkflowBackoffWaitTime))
|
||||
|
||||
By("Test get backoff time")
|
||||
for i := 0; i < 5; i++ {
|
||||
for i := 0; i < 4; i++ {
|
||||
_, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
interval := getBackoffWaitTime(wfCtx)
|
||||
interval := e.getBackoffWaitTime()
|
||||
Expect(interval).Should(BeEquivalentTo(minWorkflowBackoffWaitTime))
|
||||
}
|
||||
|
||||
for i := 0; i < 9; i++ {
|
||||
_, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
interval := getBackoffWaitTime(wfCtx)
|
||||
interval := e.getBackoffWaitTime()
|
||||
Expect(interval).Should(BeEquivalentTo(int(0.05 * math.Pow(2, float64(i+5)))))
|
||||
}
|
||||
|
||||
_, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
interval := getBackoffWaitTime(wfCtx)
|
||||
interval = e.getBackoffWaitTime()
|
||||
Expect(interval).Should(BeEquivalentTo(maxWorkflowBackoffWaitTime))
|
||||
|
||||
By("Test get backoff time after clean")
|
||||
wf.CleanupCountersInContext(ctx)
|
||||
wfContext.CleanupMemoryStore(app.Name, app.Namespace)
|
||||
_, err = wf.ExecuteSteps(ctx, revision, runners)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
wfCtx, err = wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
interval = getBackoffWaitTime(wfCtx)
|
||||
e = &engine{
|
||||
status: app.Status.Workflow,
|
||||
wfCtx: wfCtx,
|
||||
}
|
||||
interval = e.getBackoffWaitTime()
|
||||
Expect(interval).Should(BeEquivalentTo(minWorkflowBackoffWaitTime))
|
||||
})
|
||||
|
||||
|
||||
@@ -234,11 +234,11 @@ func parseToMap(args []string) (map[string]interface{}, error) {
|
||||
res := map[string]interface{}{}
|
||||
for _, pair := range args {
|
||||
line := strings.Split(pair, "=")
|
||||
if len(line) != 2 {
|
||||
if len(line) < 2 {
|
||||
return nil, fmt.Errorf("parameter format should be foo=bar, %s not match", pair)
|
||||
}
|
||||
k := strings.TrimSpace(line[0])
|
||||
v := strings.TrimSpace(line[1])
|
||||
v := strings.TrimSpace(strings.Join(line[1:], "="))
|
||||
if k != "" && v != "" {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
60
references/cli/addon_test.go
Normal file
60
references/cli/addon_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestParseMap(t *testing.T) {
|
||||
testcase := []struct {
|
||||
args []string
|
||||
res map[string]interface{}
|
||||
nilError bool
|
||||
}{
|
||||
{
|
||||
args: []string{"key1=value1"},
|
||||
res: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
},
|
||||
nilError: true,
|
||||
},
|
||||
{
|
||||
args: []string{"dbUrl=mongodb=mgset-58800212"},
|
||||
res: map[string]interface{}{
|
||||
"dbUrl": "mongodb=mgset-58800212",
|
||||
},
|
||||
nilError: true,
|
||||
},
|
||||
{
|
||||
args: []string{"errorparameter"},
|
||||
res: nil,
|
||||
nilError: false,
|
||||
},
|
||||
}
|
||||
for _, s := range testcase {
|
||||
r, err := parseToMap(s.args)
|
||||
assert.DeepEqual(t, s.res, r)
|
||||
if s.nilError {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
assert.Error(t, err, "parameter format should be foo=bar, errorparameter not match")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@ const (
|
||||
FlagProvider = "provider"
|
||||
// FlagGit command flag to specify which git repository the configuration(HCL) is stored in
|
||||
FlagGit = "git"
|
||||
// FlagLocal command flag to specify the local path of Terraform module or resource HCL file
|
||||
FlagLocal = "local"
|
||||
// FlagPath command flag to specify which path the configuration(HCL) is stored in the Git repository
|
||||
FlagPath = "path"
|
||||
// FlagNamespace command flag to specify which namespace to use
|
||||
|
||||
@@ -244,6 +244,6 @@ func PrintInstalledCompDef(c common2.Args, io cmdutil.IOStreams, filter filterFu
|
||||
}
|
||||
table.AddRow(capa.Name, capa.CrdName)
|
||||
}
|
||||
io.Infof(table.String())
|
||||
io.Info(table.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/encoding/gocode/gocodec"
|
||||
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -167,8 +169,10 @@ func NewDefinitionInitCommand(c common.Args) *cobra.Command {
|
||||
"> vela def init my-def -i --output ./my-def.cue\n" +
|
||||
"# Command below initiate a ComponentDefinition named my-webservice with the template parsed from ./template.yaml.\n" +
|
||||
"> vela def init my-webservice -i --template-yaml ./template.yaml\n" +
|
||||
"# Command below initiate a typed ComponentDefinition named vswitch from Alibaba Cloud.\n" +
|
||||
"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch",
|
||||
"# Initiate a Terraform ComponentDefinition named vswitch from Github for Alibaba Cloud.\n" +
|
||||
"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch\n" +
|
||||
"# Initiate a Terraform ComponentDefinition named redis from local file for AWS.\n" +
|
||||
"> vela def init redis --type component --provider aws --desc \"Terraform configuration for AWS Redis\" --local redis.tf",
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var defStr string
|
||||
@@ -280,6 +284,7 @@ func NewDefinitionInitCommand(c common.Args) *cobra.Command {
|
||||
cmd.Flags().BoolP(FlagInteractive, "i", false, "Specify whether use interactive process to help generate definitions.")
|
||||
cmd.Flags().StringP(FlagProvider, "p", "", "Specify which provider the cloud resource definition belongs to. Only `alibaba`, `aws`, `azure` are supported.")
|
||||
cmd.Flags().StringP(FlagGit, "", "", "Specify which git repository the configuration(HCL) is stored in. Valid when --provider/-p is set.")
|
||||
cmd.Flags().StringP(FlagLocal, "", "", "Specify the local path of the configuration(HCL) file. Valid when --provider/-p is set.")
|
||||
cmd.Flags().StringP(FlagPath, "", "", "Specify which path the configuration(HCL) is stored in the Git repository. Valid when --git is set.")
|
||||
return cmd
|
||||
}
|
||||
@@ -290,18 +295,42 @@ func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, p
|
||||
}
|
||||
|
||||
switch provider {
|
||||
case "aws", "azure", "alibaba":
|
||||
case "aws", "azure", "alibaba", "tencent":
|
||||
var terraform *commontype.Terraform
|
||||
|
||||
git, err := cmd.Flags().GetString(FlagGit)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to get `%s`", FlagGit)
|
||||
}
|
||||
if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") {
|
||||
return "", errors.Errorf("invalid git url: %s", git)
|
||||
local, err := cmd.Flags().GetString(FlagLocal)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to get `%s`", FlagLocal)
|
||||
}
|
||||
if git != "" && local != "" {
|
||||
return "", errors.New("only one of --git and --local can be set")
|
||||
}
|
||||
gitPath, err := cmd.Flags().GetString(FlagPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to get `%s`", FlagPath)
|
||||
}
|
||||
if git != "" {
|
||||
if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") {
|
||||
return "", errors.Errorf("invalid git url: %s", git)
|
||||
}
|
||||
terraform = &commontype.Terraform{
|
||||
Configuration: git,
|
||||
Type: "remote",
|
||||
Path: gitPath,
|
||||
}
|
||||
} else if local != "" {
|
||||
hcl, err := ioutil.ReadFile(filepath.Clean(local))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to read Terraform configuration from file %s", local)
|
||||
}
|
||||
terraform = &commontype.Terraform{
|
||||
Configuration: string(hcl),
|
||||
}
|
||||
}
|
||||
def := v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
@@ -325,14 +354,16 @@ func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, p
|
||||
},
|
||||
},
|
||||
Schematic: &commontype.Schematic{
|
||||
Terraform: &commontype.Terraform{
|
||||
Configuration: git,
|
||||
Type: "remote",
|
||||
Path: gitPath,
|
||||
},
|
||||
Terraform: terraform,
|
||||
},
|
||||
},
|
||||
}
|
||||
if provider != "alibaba" {
|
||||
def.Spec.Schematic.Terraform.ProviderReference = &crossplane.Reference{
|
||||
Name: provider,
|
||||
Namespace: "default",
|
||||
}
|
||||
}
|
||||
var out bytes.Buffer
|
||||
err = json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{Yaml: true}).Encode(&def, &out)
|
||||
if err != nil {
|
||||
|
||||
@@ -204,10 +204,14 @@ func TestNewDefinitionInitCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewDefinitionInitCommand4Terraform(t *testing.T) {
|
||||
const defFileName = "alibaba-vswitch.yaml"
|
||||
const (
|
||||
defVswitchFileName = "alibaba-vswitch.yaml"
|
||||
defRedisFileName = "tencent-redis.yaml"
|
||||
)
|
||||
testcases := []struct {
|
||||
name string
|
||||
args []string
|
||||
output string
|
||||
errMsg string
|
||||
want string
|
||||
}{
|
||||
@@ -216,8 +220,95 @@ func TestNewDefinitionInitCommand4Terraform(t *testing.T) {
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "xxx", "--git", "https://github.com/kubevela-contrib/terraform-modules.git", "--path", "alibaba/vswitch"},
|
||||
},
|
||||
{
|
||||
name: "print in a file",
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "xxx", "--git", "https://github.com/kubevela-contrib/terraform-modules.git", "--path", "alibaba/vswitch", "--output", defFileName},
|
||||
name: "normal from local",
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "tencent", "--desc", "xxx", "--local", "test-data/redis.tf", "--output", defRedisFileName},
|
||||
output: defRedisFileName,
|
||||
want: `apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: xxx
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
type: terraform
|
||||
name: tencent-vswitch
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
terraform:
|
||||
configuration: |
|
||||
terraform {
|
||||
required_providers {
|
||||
tencentcloud = {
|
||||
source = "tencentcloudstack/tencentcloud"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "tencentcloud_redis_instance" "main" {
|
||||
type_id = 8
|
||||
availability_zone = var.availability_zone
|
||||
name = var.instance_name
|
||||
password = var.user_password
|
||||
mem_size = var.mem_size
|
||||
port = var.port
|
||||
}
|
||||
|
||||
output "DB_IP" {
|
||||
value = tencentcloud_redis_instance.main.ip
|
||||
}
|
||||
|
||||
output "DB_PASSWORD" {
|
||||
value = var.user_password
|
||||
}
|
||||
|
||||
output "DB_PORT" {
|
||||
value = var.port
|
||||
}
|
||||
|
||||
variable "availability_zone" {
|
||||
description = "The available zone ID of an instance to be created."
|
||||
type = string
|
||||
default = "ap-chengdu-1"
|
||||
}
|
||||
|
||||
variable "instance_name" {
|
||||
description = "redis instance name"
|
||||
type = string
|
||||
default = "sample"
|
||||
}
|
||||
|
||||
variable "user_password" {
|
||||
description = "redis instance password"
|
||||
type = string
|
||||
default = "IEfewjf2342rfwfwYYfaked"
|
||||
}
|
||||
|
||||
variable "mem_size" {
|
||||
description = "redis instance memory size"
|
||||
type = number
|
||||
default = 1024
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
description = "The port used to access a redis instance."
|
||||
type = number
|
||||
default = 6379
|
||||
}
|
||||
providerRef:
|
||||
name: tencent
|
||||
namespace: default
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: terraform.core.oam.dev/v1beta1
|
||||
kind: Configuration
|
||||
status: {}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "print in a file",
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "xxx", "--git", "https://github.com/kubevela-contrib/terraform-modules.git", "--path", "alibaba/vswitch", "--output", defVswitchFileName},
|
||||
output: defVswitchFileName,
|
||||
want: `apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
@@ -255,6 +346,16 @@ status: {}`,
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "test", "--git", "xxx"},
|
||||
errMsg: "invalid git url",
|
||||
},
|
||||
{
|
||||
name: "git and local could be set at the same time",
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "test", "--git", "xxx", "--local", "yyy"},
|
||||
errMsg: "only one of --git and --local can be set",
|
||||
},
|
||||
{
|
||||
name: "local file doesn't exist",
|
||||
args: []string{"vswitch", "-t", "component", "--provider", "tencent", "--desc", "xxx", "--local", "test-data/redis2.tf"},
|
||||
errMsg: "failed to read Terraform configuration from file",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
@@ -267,8 +368,8 @@ status: {}`,
|
||||
if err != nil && !strings.Contains(err.Error(), tc.errMsg) {
|
||||
t.Fatalf("unexpected error when executing init command: %v", err)
|
||||
} else if tc.want != "" {
|
||||
data, err := os.ReadFile(defFileName)
|
||||
defer os.Remove(defFileName)
|
||||
data, err := os.ReadFile(tc.output)
|
||||
defer os.Remove(tc.output)
|
||||
assert.Nil(t, err)
|
||||
if !strings.Contains(string(data), tc.want) {
|
||||
t.Fatalf("unexpected output: %s", string(data))
|
||||
|
||||
@@ -762,14 +762,14 @@ func ParseCapability(mapper discoverymapper.DiscoveryMapper, data []byte) (types
|
||||
}
|
||||
workloadDefinitionRef = ref.Name
|
||||
}
|
||||
return plugins.HandleDefinition(cd.Name, workloadDefinitionRef, cd.Annotations, cd.Labels, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic)
|
||||
return plugins.HandleDefinition(cd.Name, workloadDefinitionRef, cd.Annotations, cd.Labels, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic, nil)
|
||||
case "TraitDefinition":
|
||||
var td v1beta1.TraitDefinition
|
||||
err = yaml.Unmarshal(data, &td)
|
||||
if err != nil {
|
||||
return types.Capability{}, err
|
||||
}
|
||||
return plugins.HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Labels, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic)
|
||||
return plugins.HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Labels, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic, nil)
|
||||
case "ScopeDefinition":
|
||||
// TODO(wonderflow): support scope definition here.
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
@@ -62,8 +63,8 @@ var webSite bool
|
||||
func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show the reference doc for a component type or trait.",
|
||||
Long: "Show the reference doc for component or trait types.",
|
||||
Short: "Show the reference doc for a component, trait or workflow.",
|
||||
Long: "Show the reference doc for component, trait or workflow types.",
|
||||
Example: `show webservice`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
@@ -126,7 +127,7 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
|
||||
}
|
||||
}
|
||||
if !capabilityIsValid {
|
||||
return fmt.Errorf("%s is not a valid component type or trait", capabilityName)
|
||||
return fmt.Errorf("%s is not a valid component, trait or workflow", capabilityName)
|
||||
}
|
||||
|
||||
cli, err := c.GetClient()
|
||||
@@ -139,7 +140,15 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
|
||||
},
|
||||
}
|
||||
|
||||
if err := ref.CreateMarkdown(ctx, capabilities, docsPath, plugins.ReferenceSourcePath); err != nil {
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pd, err := packages.NewPackageDiscover(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ref.CreateMarkdown(ctx, capabilities, docsPath, plugins.ReferenceSourcePath, pd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -171,6 +180,8 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
|
||||
case types.TypeScope:
|
||||
case types.TypeComponentDefinition:
|
||||
capabilityPath = plugins.ComponentDefinitionTypePath
|
||||
case types.TypeWorkflowStep:
|
||||
capabilityPath = plugins.WorkflowStepPath
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %v", capabilityType)
|
||||
}
|
||||
@@ -216,7 +227,7 @@ func launch(server *http.Server, errChan chan<- error) {
|
||||
|
||||
func generateSideBar(capabilities []types.Capability, docsPath string) error {
|
||||
sideBar := filepath.Join(docsPath, SideBar)
|
||||
components, traits := getComponentsAndTraits(capabilities)
|
||||
components, traits, workflowsteps := getDefinitions(capabilities)
|
||||
f, err := os.Create(sideBar)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -237,6 +248,14 @@ func generateSideBar(capabilities []types.Capability, docsPath string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := f.WriteString("- Workflow Steps\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range workflowsteps {
|
||||
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, plugins.WorkflowStepPath, t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -303,12 +322,12 @@ func generateREADME(capabilities []types.Capability, docsPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteString("# KubeVela Reference Docs for Component Types and Traits\n" +
|
||||
"Click the navigation bar on the left or the links below to look into the detailed referennce of a Workload type or a Trait.\n"); err != nil {
|
||||
if _, err := f.WriteString("# KubeVela Reference Docs for Component Types, Traits and WorkflowSteps\n" +
|
||||
"Click the navigation bar on the left or the links below to look into the detailed reference of a Workload type, Trait or Workflow Step.\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workloads, traits := getComponentsAndTraits(capabilities)
|
||||
workloads, traits, workflowsteps := getDefinitions(capabilities)
|
||||
|
||||
if _, err := f.WriteString("## Component Types\n"); err != nil {
|
||||
return err
|
||||
@@ -328,28 +347,47 @@ func generateREADME(capabilities []types.Capability, docsPath string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := f.WriteString("## Workflow Steps\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range workflowsteps {
|
||||
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, plugins.WorkflowStepPath, t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getComponentsAndTraits(capabilities []types.Capability) ([]string, []string) {
|
||||
var components, traits []string
|
||||
func getDefinitions(capabilities []types.Capability) ([]string, []string, []string) {
|
||||
var components, traits, workflowSteps []string
|
||||
for _, c := range capabilities {
|
||||
switch c.Type {
|
||||
case types.TypeComponentDefinition:
|
||||
components = append(components, c.Name)
|
||||
case types.TypeTrait:
|
||||
traits = append(traits, c.Name)
|
||||
case types.TypeWorkflowStep:
|
||||
workflowSteps = append(workflowSteps, c.Name)
|
||||
case types.TypeScope:
|
||||
case types.TypeWorkload:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return components, traits
|
||||
return components, traits, workflowSteps
|
||||
}
|
||||
|
||||
// ShowReferenceConsole will show capability reference in console
|
||||
func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns string) error {
|
||||
capability, err := plugins.GetCapabilityByName(ctx, c, capabilityName, ns)
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pd, err := packages.NewPackageDiscover(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
capability, err := plugins.GetCapabilityByName(ctx, c, capabilityName, ns, pd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -377,7 +415,7 @@ func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.
|
||||
return err
|
||||
}
|
||||
case types.CUECategory:
|
||||
propertyConsole, err = ref.GenerateCUETemplateProperties(capability)
|
||||
propertyConsole, err = ref.GenerateCUETemplateProperties(capability, pd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func TestGetWorkloadAndTraits(t *testing.T) {
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
gotWorkloads, gotTraits := getComponentsAndTraits(tc.capabilities)
|
||||
gotWorkloads, gotTraits, _ := getDefinitions(tc.capabilities)
|
||||
assert.Equal(t, tc.want, want{workloads: gotWorkloads, traits: gotTraits})
|
||||
})
|
||||
}
|
||||
|
||||
58
references/cli/test-data/redis.tf
Normal file
58
references/cli/test-data/redis.tf
Normal file
@@ -0,0 +1,58 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
tencentcloud = {
|
||||
source = "tencentcloudstack/tencentcloud"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "tencentcloud_redis_instance" "main" {
|
||||
type_id = 8
|
||||
availability_zone = var.availability_zone
|
||||
name = var.instance_name
|
||||
password = var.user_password
|
||||
mem_size = var.mem_size
|
||||
port = var.port
|
||||
}
|
||||
|
||||
output "DB_IP" {
|
||||
value = tencentcloud_redis_instance.main.ip
|
||||
}
|
||||
|
||||
output "DB_PASSWORD" {
|
||||
value = var.user_password
|
||||
}
|
||||
|
||||
output "DB_PORT" {
|
||||
value = var.port
|
||||
}
|
||||
|
||||
variable "availability_zone" {
|
||||
description = "The available zone ID of an instance to be created."
|
||||
type = string
|
||||
default = "ap-chengdu-1"
|
||||
}
|
||||
|
||||
variable "instance_name" {
|
||||
description = "redis instance name"
|
||||
type = string
|
||||
default = "sample"
|
||||
}
|
||||
|
||||
variable "user_password" {
|
||||
description = "redis instance password"
|
||||
type = string
|
||||
default = "IEfewjf2342rfwfwYYfaked"
|
||||
}
|
||||
|
||||
variable "mem_size" {
|
||||
description = "redis instance memory size"
|
||||
type = number
|
||||
default = 1024
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
description = "The port used to access a redis instance."
|
||||
type = number
|
||||
default = 6379
|
||||
}
|
||||
@@ -245,7 +245,7 @@ func PrintInstalledTraitDef(c common2.Args, io cmdutil.IOStreams, filter filterF
|
||||
}
|
||||
table.AddRow(capa.Name, capa.AppliesTo)
|
||||
}
|
||||
io.Infof(table.String())
|
||||
io.Info(table.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,12 @@ func NewUpCommand(c common2.Args, order string, ioStream cmdutil.IOStreams) *cob
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
|
||||
}
|
||||
|
||||
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
|
||||
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
|
||||
if namespace != "" && namespace != types.DefaultAppNamespace {
|
||||
app.SetNamespace(namespace)
|
||||
}
|
||||
err = common.ApplyApplication(app, ioStream, kubecli)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -17,12 +17,21 @@ limitations under the License.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/common"
|
||||
)
|
||||
|
||||
@@ -34,3 +43,105 @@ func TestUp(t *testing.T) {
|
||||
assert.Contains(t, msg, "App has been deployed")
|
||||
assert.Contains(t, msg, fmt.Sprintf("App status: vela status %s", app.Name))
|
||||
}
|
||||
|
||||
func TestUpOverrideNamespace(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
application string
|
||||
applicationName string
|
||||
namespace string
|
||||
expectedNamespace string
|
||||
}{
|
||||
"use default namespace if not set": {
|
||||
application: `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: first-vela-app
|
||||
spec:
|
||||
components: []
|
||||
`,
|
||||
applicationName: "first-vela-app",
|
||||
namespace: "",
|
||||
expectedNamespace: types.DefaultAppNamespace,
|
||||
},
|
||||
"override namespace": {
|
||||
application: `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: first-vela-app
|
||||
spec:
|
||||
components: []
|
||||
`,
|
||||
applicationName: "first-vela-app",
|
||||
namespace: "overridden-namespace",
|
||||
expectedNamespace: "overridden-namespace",
|
||||
},
|
||||
"use application namespace": {
|
||||
application: `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: first-vela-app
|
||||
namespace: vela-apps
|
||||
spec:
|
||||
components: []
|
||||
`,
|
||||
applicationName: "first-vela-app",
|
||||
namespace: "",
|
||||
expectedNamespace: "vela-apps",
|
||||
},
|
||||
"override application namespace": {
|
||||
application: `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: first-vela-app
|
||||
namespace: vela-apps
|
||||
spec:
|
||||
components: []
|
||||
`,
|
||||
applicationName: "first-vela-app",
|
||||
namespace: "overridden-namespace",
|
||||
expectedNamespace: "overridden-namespace",
|
||||
},
|
||||
}
|
||||
|
||||
for name, c := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
args := initArgs()
|
||||
kc, err := args.GetClient()
|
||||
require.NoError(t, err)
|
||||
|
||||
af, err := os.CreateTemp(os.TempDir(), "up-override-namespace-*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = af.Close()
|
||||
_ = os.Remove(af.Name())
|
||||
}()
|
||||
_, err = af.WriteString(c.application)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure namespace
|
||||
require.NoError(t, kc.Create(context.TODO(), &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: c.expectedNamespace},
|
||||
}))
|
||||
|
||||
var buf bytes.Buffer
|
||||
cmd := NewUpCommand(args, "", util.IOStreams{In: os.Stdin, Out: &buf, ErrOut: &buf})
|
||||
if c.namespace != "" {
|
||||
require.NoError(t, cmd.Flags().Set(FlagNamespace, c.namespace))
|
||||
}
|
||||
require.NoError(t, cmd.Flags().Set("file", af.Name()))
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
var app v1beta1.Application
|
||||
require.NoError(t, kc.Get(context.TODO(), client.ObjectKey{
|
||||
Name: c.applicationName,
|
||||
Namespace: c.expectedNamespace,
|
||||
}, &app))
|
||||
require.Equal(t, c.expectedNamespace, app.Namespace)
|
||||
require.Equal(t, c.applicationName, app.Name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
@@ -212,9 +213,9 @@ func validateCapabilities(tmp *types.Capability, dm discoverymapper.DiscoveryMap
|
||||
|
||||
// HandleDefinition will handle definition to capability
|
||||
func HandleDefinition(name, crdName string, annotation, labels map[string]string, extension *runtime.RawExtension, tp types.CapType,
|
||||
applyTo []string, schematic *commontypes.Schematic) (types.Capability, error) {
|
||||
applyTo []string, schematic *commontypes.Schematic, pd *packages.PackageDiscover) (types.Capability, error) {
|
||||
var tmp types.Capability
|
||||
tmp, err := HandleTemplate(extension, schematic, name)
|
||||
tmp, err := HandleTemplate(extension, schematic, name, pd)
|
||||
if err != nil {
|
||||
return types.Capability{}, err
|
||||
}
|
||||
@@ -242,7 +243,7 @@ func GetDescription(annotation map[string]string) string {
|
||||
}
|
||||
|
||||
// HandleTemplate will handle definition template to capability
|
||||
func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string) (types.Capability, error) {
|
||||
func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string, pd *packages.PackageDiscover) (types.Capability, error) {
|
||||
tmp, err := appfile.ConvertTemplateJSON2Object(name, in, schematic)
|
||||
if err != nil {
|
||||
return types.Capability{}, err
|
||||
@@ -282,7 +283,7 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic,
|
||||
}
|
||||
return types.Capability{}, errors.New("template not exist in definition")
|
||||
}
|
||||
tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate)
|
||||
tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate, pd)
|
||||
if err != nil {
|
||||
return types.Capability{}, err
|
||||
}
|
||||
@@ -291,7 +292,7 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic,
|
||||
}
|
||||
|
||||
// GetCapabilityByName gets capability by definition name
|
||||
func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName string, ns string) (*types.Capability, error) {
|
||||
func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName string, ns string, pd *packages.PackageDiscover) (*types.Capability, error) {
|
||||
var (
|
||||
foundCapability bool
|
||||
capability *types.Capability
|
||||
@@ -357,6 +358,25 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri
|
||||
}
|
||||
return capability, nil
|
||||
}
|
||||
|
||||
var wfStepDef v1beta1.WorkflowStepDefinition
|
||||
err = newClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: capabilityName}, &wfStepDef)
|
||||
if err == nil {
|
||||
foundCapability = true
|
||||
} else if kerrors.IsNotFound(err) {
|
||||
err = newClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: capabilityName}, &wfStepDef)
|
||||
if err == nil {
|
||||
foundCapability = true
|
||||
}
|
||||
}
|
||||
if foundCapability {
|
||||
capability, err = GetCapabilityByWorkflowStepDefinitionObject(wfStepDef, pd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return capability, nil
|
||||
}
|
||||
|
||||
if ns == types.DefaultKubeVelaNS {
|
||||
return nil, fmt.Errorf("could not find %s in namespace %s", capabilityName, ns)
|
||||
}
|
||||
@@ -366,7 +386,7 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri
|
||||
// GetCapabilityByComponentDefinitionObject gets capability by ComponentDefinition object
|
||||
func GetCapabilityByComponentDefinitionObject(componentDef v1beta1.ComponentDefinition, referenceName string) (*types.Capability, error) {
|
||||
capability, err := HandleDefinition(componentDef.Name, referenceName, componentDef.Annotations, componentDef.Labels,
|
||||
componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic)
|
||||
componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to handle ComponentDefinition")
|
||||
}
|
||||
@@ -381,10 +401,25 @@ func GetCapabilityByTraitDefinitionObject(traitDef v1beta1.TraitDefinition) (*ty
|
||||
err error
|
||||
)
|
||||
capability, err = HandleDefinition(traitDef.Name, traitDef.Spec.Reference.Name, traitDef.Annotations, traitDef.Labels,
|
||||
traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic)
|
||||
traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to handle TraitDefinition")
|
||||
}
|
||||
capability.Namespace = traitDef.Namespace
|
||||
return &capability, nil
|
||||
}
|
||||
|
||||
// GetCapabilityByWorkflowStepDefinitionObject gets capability by WorkflowStepDefinition object
|
||||
func GetCapabilityByWorkflowStepDefinitionObject(wfStepDef v1beta1.WorkflowStepDefinition, pd *packages.PackageDiscover) (*types.Capability, error) {
|
||||
var (
|
||||
capability types.Capability
|
||||
err error
|
||||
)
|
||||
capability, err = HandleDefinition(wfStepDef.Name, wfStepDef.Spec.Reference.Name, wfStepDef.Annotations, wfStepDef.Labels,
|
||||
nil, types.TypeWorkflowStep, nil, wfStepDef.Spec.Schematic, pd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to handle WorkflowStepDefinition")
|
||||
}
|
||||
capability.Namespace = wfStepDef.Namespace
|
||||
return &capability, nil
|
||||
}
|
||||
|
||||
@@ -227,15 +227,15 @@ var _ = Describe("test GetCapabilityByName", func() {
|
||||
|
||||
It("get capability", func() {
|
||||
Context("ComponentDefinition is in the current namespace", func() {
|
||||
_, err := GetCapabilityByName(ctx, c, component1, ns)
|
||||
_, err := GetCapabilityByName(ctx, c, component1, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
Context("ComponentDefinition is in the default namespace", func() {
|
||||
_, err := GetCapabilityByName(ctx, c, component2, ns)
|
||||
_, err := GetCapabilityByName(ctx, c, component2, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
Context("ComponentDefinition is in the default namespace", func() {
|
||||
cap, err := GetCapabilityByName(ctx, c, component3, ns)
|
||||
cap, err := GetCapabilityByName(ctx, c, component3, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
jsontmp, err := json.Marshal(cap.KubeParameter)
|
||||
Expect(err).Should(BeNil())
|
||||
@@ -245,20 +245,20 @@ var _ = Describe("test GetCapabilityByName", func() {
|
||||
Expect(string(jsontmp)).Should(ContainSubstring("the specific container port num which can accept external request."))
|
||||
})
|
||||
Context("ComponentDefinition's workload type is AutoDetectWorkloadDefinition", func() {
|
||||
_, err := GetCapabilityByName(ctx, c, component4, ns)
|
||||
_, err := GetCapabilityByName(ctx, c, component4, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
Context("TraitDefinition is in the current namespace", func() {
|
||||
_, err := GetCapabilityByName(ctx, c, trait1, ns)
|
||||
_, err := GetCapabilityByName(ctx, c, trait1, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
Context("TraitDefinition is in the default namespace", func() {
|
||||
_, err := GetCapabilityByName(ctx, c, trait2, ns)
|
||||
_, err := GetCapabilityByName(ctx, c, trait2, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
Context("TraitDefinition is in the default namespace", func() {
|
||||
cap, err := GetCapabilityByName(ctx, c, trait3, ns)
|
||||
cap, err := GetCapabilityByName(ctx, c, trait3, ns, nil)
|
||||
Expect(err).Should(BeNil())
|
||||
jsontmp, err := json.Marshal(cap.KubeParameter)
|
||||
Expect(err).Should(BeNil())
|
||||
@@ -267,7 +267,7 @@ var _ = Describe("test GetCapabilityByName", func() {
|
||||
})
|
||||
|
||||
Context("capability cloud not be found", func() {
|
||||
_, err := GetCapabilityByName(ctx, c, "a-component-definition-not-existed", ns)
|
||||
_, err := GetCapabilityByName(ctx, c, "a-component-definition-not-existed", ns, nil)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -44,6 +44,14 @@ var Definitions = map[string]map[Language]string{
|
||||
Zh: "阿里云",
|
||||
En: "Alibaba Cloud",
|
||||
},
|
||||
"AWS": {
|
||||
Zh: "AWS",
|
||||
En: "AWS",
|
||||
},
|
||||
"Azure": {
|
||||
Zh: "Azure",
|
||||
En: "Azure",
|
||||
},
|
||||
"Name": {
|
||||
Zh: "名称",
|
||||
En: "Name",
|
||||
@@ -76,4 +84,52 @@ var Definitions = map[string]map[Language]string{
|
||||
Zh: "用于部署阿里云 ACK 集群的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud ACK cluster",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_Serverless_Kubernetes_(ASK)": {
|
||||
Zh: "用于部署阿里云 Serverless Kubernetes (ASK) 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud Serverless Kubernetes (ASK)",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_Elastic_IP": {
|
||||
Zh: "用于部署阿里云弹性 IP 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud Elastic IP",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_OSS_object": {
|
||||
Zh: "用于部署阿里云 OSS 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud OSS",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_RDS_object": {
|
||||
Zh: "用于部署阿里云 RDS 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud RDS",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_Redis": {
|
||||
Zh: "用于部署阿里云 Redis 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud Redis",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_SLS_Project": {
|
||||
Zh: "用于部署阿里云 SLS Project 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud SLS Project",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_SLS_Store": {
|
||||
Zh: "用于部署阿里云 SLS Store 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud SLS Store",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_VPC": {
|
||||
Zh: "用于部署阿里云 VPC 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud VPC",
|
||||
},
|
||||
"Terraform_configuration_for_Alibaba_Cloud_VSwitch": {
|
||||
Zh: "用于部署阿里云 VSwitch 的组件说明",
|
||||
En: "Terraform configuration for Alibaba Cloud VSwitch",
|
||||
},
|
||||
"Terraform_configuration_for_AWS_S3": {
|
||||
Zh: "用于部署 AWS S3 的组件说明",
|
||||
En: "Terraform configuration for AWS S3",
|
||||
},
|
||||
"Terraform_configuration_for_Azure_Database_Mariadb": {
|
||||
Zh: "用于部署 Azure mariadb 数据库的组件说明",
|
||||
En: "Terraform configuration for Azure Database Mariadb",
|
||||
},
|
||||
"Terraform_configuration_for_Azure_Blob_Storage_Account": {
|
||||
Zh: "用于部署 Azure Blob Storage 账号的的组件说明",
|
||||
En: "Terraform configuration for Azure Blob Storage Account",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ variable "acl" {
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := tc.ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, ReferenceSourcePath)
|
||||
got := tc.ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, ReferenceSourcePath, nil)
|
||||
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nCreateMakrdown(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
@@ -554,6 +554,20 @@ func TestMakeReadableTitle(t *testing.T) {
|
||||
},
|
||||
want: "阿里云 DEF-GHI",
|
||||
},
|
||||
{
|
||||
args: args{
|
||||
title: "aws-jk",
|
||||
ref: refZh,
|
||||
},
|
||||
want: "AWS JK",
|
||||
},
|
||||
{
|
||||
args: args{
|
||||
title: "azure-jk",
|
||||
ref: refZh,
|
||||
},
|
||||
want: "Azure JK",
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/terraform"
|
||||
)
|
||||
@@ -55,6 +56,8 @@ const (
|
||||
WorkloadTypePath = "workload-types"
|
||||
// TraitPath is the URL path for trait typed capability
|
||||
TraitPath = "traits"
|
||||
// WorkflowStepPath is the URL path for workflow step typed capability
|
||||
WorkflowStepPath = "workflowsteps"
|
||||
)
|
||||
|
||||
// Int64Type is int64 type
|
||||
@@ -359,6 +362,156 @@ spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: ack-conn
|
||||
namespace: vela-system
|
||||
`,
|
||||
"alibaba-eip": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: provision-cloud-resource-eip
|
||||
spec:
|
||||
components:
|
||||
- name: sample-eip
|
||||
type: alibaba-eip
|
||||
properties:
|
||||
writeConnectionSecretToRef:
|
||||
name: eip-conn
|
||||
`,
|
||||
|
||||
"alibaba-oss": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: oss-cloud-source
|
||||
spec:
|
||||
components:
|
||||
- name: sample-oss
|
||||
type: alibaba-oss
|
||||
properties:
|
||||
bucket: vela-website
|
||||
acl: private
|
||||
writeConnectionSecretToRef:
|
||||
name: oss-conn
|
||||
`,
|
||||
|
||||
"alibaba-redis": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: redis-cloud-source
|
||||
spec:
|
||||
components:
|
||||
- name: sample-redis
|
||||
type: alibaba-redis
|
||||
properties:
|
||||
instance_name: oam-redis
|
||||
account_name: oam
|
||||
password: Xyfff83jfewGGfaked
|
||||
writeConnectionSecretToRef:
|
||||
name: redis-conn
|
||||
`,
|
||||
|
||||
"aws-s3": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: s3-cloud-source
|
||||
spec:
|
||||
components:
|
||||
- name: sample-s3
|
||||
type: aws-s3
|
||||
properties:
|
||||
bucket: vela-website-20211019
|
||||
acl: private
|
||||
|
||||
writeConnectionSecretToRef:
|
||||
name: s3-conn
|
||||
`,
|
||||
|
||||
"azure-database-mariadb": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: mariadb-backend
|
||||
spec:
|
||||
components:
|
||||
- name: mariadb-backend
|
||||
type: azure-database-mariadb
|
||||
properties:
|
||||
resource_group: "kubevela-group"
|
||||
location: "West Europe"
|
||||
server_name: "kubevela"
|
||||
db_name: "backend"
|
||||
username: "acctestun"
|
||||
password: "H@Sh1CoR3!Faked"
|
||||
writeConnectionSecretToRef:
|
||||
name: azure-db-conn
|
||||
namespace: vela-system
|
||||
`,
|
||||
|
||||
"azure-storage-account": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: storage-account-dev
|
||||
spec:
|
||||
components:
|
||||
- name: storage-account-dev
|
||||
type: azure-storage-account
|
||||
properties:
|
||||
create_rsg: false
|
||||
resource_group_name: "weursgappdev01"
|
||||
location: "West Europe"
|
||||
name: "appdev01"
|
||||
tags: |
|
||||
{
|
||||
ApplicationName = "Application01"
|
||||
Terraform = "Yes"
|
||||
}
|
||||
static_website: |
|
||||
[{
|
||||
index_document = "index.html"
|
||||
error_404_document = "index.html"
|
||||
}]
|
||||
|
||||
writeConnectionSecretToRef:
|
||||
name: storage-account-dev
|
||||
namespace: vela-system
|
||||
`,
|
||||
|
||||
"alibaba-sls-project": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-sls-project-sample
|
||||
spec:
|
||||
components:
|
||||
- name: sample-sls-project
|
||||
type: alibaba-sls-project
|
||||
properties:
|
||||
name: kubevela-1112
|
||||
description: "Managed by KubeVela"
|
||||
|
||||
writeConnectionSecretToRef:
|
||||
name: sls-project-conn
|
||||
`,
|
||||
|
||||
"alibaba-sls-store": `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-sls-store-sample
|
||||
spec:
|
||||
components:
|
||||
- name: sample-sls-store
|
||||
type: alibaba-sls-store
|
||||
properties:
|
||||
store_name: kubevela-1111
|
||||
store_retention_period: 30
|
||||
store_shard_count: 2
|
||||
store_max_split_shard_count: 2
|
||||
|
||||
writeConnectionSecretToRef:
|
||||
name: sls-store-conn
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -406,6 +559,14 @@ func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, c commo
|
||||
caps []types.Capability
|
||||
err error
|
||||
)
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pd, err := packages.NewPackageDiscover(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref.DefinitionName == "" {
|
||||
caps, err = LoadAllInstalledCapability("default", c)
|
||||
@@ -413,18 +574,18 @@ func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, c commo
|
||||
return fmt.Errorf("failed to get all capabilityes: %w", err)
|
||||
}
|
||||
} else {
|
||||
cap, err := GetCapabilityByName(ctx, c, ref.DefinitionName, namespace)
|
||||
cap, err := GetCapabilityByName(ctx, c, ref.DefinitionName, namespace, pd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get capability capability %s: %w", ref.DefinitionName, err)
|
||||
}
|
||||
caps = []types.Capability{*cap}
|
||||
}
|
||||
|
||||
return ref.CreateMarkdown(ctx, caps, baseRefPath, ReferenceSourcePath)
|
||||
return ref.CreateMarkdown(ctx, caps, baseRefPath, ReferenceSourcePath, pd)
|
||||
}
|
||||
|
||||
// CreateMarkdown creates markdown based on capabilities
|
||||
func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath, referenceSourcePath string) error {
|
||||
func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath, referenceSourcePath string, pd *packages.PackageDiscover) error {
|
||||
setDisplayFormat("markdown")
|
||||
for i, c := range caps {
|
||||
var (
|
||||
@@ -460,7 +621,7 @@ func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.C
|
||||
capNameInTitle := ref.makeReadableTitle(capName)
|
||||
switch c.Category {
|
||||
case types.CUECategory:
|
||||
cueValue, err := common.GetCUEParameterValue(c.CueTemplate)
|
||||
cueValue, err := common.GetCUEParameterValue(c.CueTemplate, pd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err)
|
||||
}
|
||||
@@ -521,16 +682,26 @@ func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.C
|
||||
}
|
||||
|
||||
func (ref *MarkdownReference) makeReadableTitle(title string) string {
|
||||
const alibabaCloud = "alibaba-"
|
||||
if ref.I18N == "" {
|
||||
ref.I18N = En
|
||||
}
|
||||
var AlibabaCloudTitle = Definitions["AlibabaCloud"][ref.I18N]
|
||||
if strings.HasPrefix(title, alibabaCloud) {
|
||||
cloudResource := strings.Replace(title, alibabaCloud, "", 1)
|
||||
return fmt.Sprintf("%s %s", AlibabaCloudTitle, strings.ToUpper(cloudResource))
|
||||
if !strings.Contains(title, "-") {
|
||||
return strings.Title(title)
|
||||
}
|
||||
return strings.Title(title)
|
||||
var name string
|
||||
provider := strings.Split(title, "-")[0]
|
||||
switch provider {
|
||||
case "alibaba":
|
||||
name = "AlibabaCloud"
|
||||
case "aws":
|
||||
name = "AWS"
|
||||
case "azure":
|
||||
name = "Azure"
|
||||
default:
|
||||
return strings.Title(title)
|
||||
}
|
||||
cloudResource := strings.Replace(title, provider+"-", "", 1)
|
||||
return fmt.Sprintf("%s %s", Definitions[name][ref.I18N], strings.ToUpper(cloudResource))
|
||||
}
|
||||
|
||||
// prepareParameter prepares the table content for each property
|
||||
@@ -661,7 +832,7 @@ func (ref *ParseReference) parseParameters(paraValue cue.Value, paramKey string,
|
||||
param.Type = val.IncompleteKind()
|
||||
switch val.IncompleteKind() {
|
||||
case cue.StructKind:
|
||||
if subField, _ := val.Struct(); subField.Len() == 0 { // err cannot be not nil,so ignore it
|
||||
if subField, err := val.Struct(); err == nil && subField.Len() == 0 { // err cannot be not nil,so ignore it
|
||||
if mapValue, ok := val.Elem(); ok {
|
||||
// In the future we could recursive call to surpport complex map-value(struct or list)
|
||||
param.PrintableType = fmt.Sprintf("map[string]%s", mapValue.IncompleteKind().String())
|
||||
@@ -772,11 +943,11 @@ func (ref *MarkdownReference) generateConflictWithAndMore(capabilityName string,
|
||||
}
|
||||
|
||||
// GenerateCUETemplateProperties get all properties of a capability
|
||||
func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Capability) ([]ConsoleReference, error) {
|
||||
func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Capability, pd *packages.PackageDiscover) ([]ConsoleReference, error) {
|
||||
setDisplayFormat("console")
|
||||
capName := capability.Name
|
||||
|
||||
cueValue, err := common.GetCUEParameterValue(capability.CueTemplate)
|
||||
cueValue, err := common.GetCUEParameterValue(capability.CueTemplate, pd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", capName, err)
|
||||
}
|
||||
@@ -878,7 +1049,7 @@ func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.C
|
||||
refParam.Name = v.Name
|
||||
refParam.PrintableType = v.Type
|
||||
refParam.Usage = v.Description
|
||||
refParam.Required = true
|
||||
refParam.Required = v.Required
|
||||
refParameterList = append(refParameterList, refParam)
|
||||
}
|
||||
refParameterList = append(refParameterList, writeConnectionSecretToRefReferenceParameter)
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
webservice: {
|
||||
type: "component"
|
||||
annotations: {}
|
||||
@@ -186,6 +190,12 @@ template: {
|
||||
{
|
||||
containerPort: v.port
|
||||
protocol: v.protocol
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
}
|
||||
}}]
|
||||
}
|
||||
|
||||
@@ -297,6 +307,12 @@ template: {
|
||||
for v in parameter.ports if v.expose == true {
|
||||
port: v.port
|
||||
targetPort: v.port
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -341,6 +357,8 @@ template: {
|
||||
ports?: [...{
|
||||
// +usage=Number of port to expose on the pod's IP address
|
||||
port: int
|
||||
// +usage=Name of the port
|
||||
name?: string
|
||||
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
|
||||
protocol: *"TCP" | "UDP" | "SCTP"
|
||||
// +usage=Specify if the port should be exposed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"gateway": {
|
||||
gateway: {
|
||||
type: "trait"
|
||||
annotations: {}
|
||||
labels: {}
|
||||
@@ -49,22 +49,29 @@ template: {
|
||||
metadata: {
|
||||
name: context.name
|
||||
annotations: {
|
||||
"kubernetes.io/ingress.class": parameter.class
|
||||
if !parameter.classInSpec {
|
||||
"kubernetes.io/ingress.class": parameter.class
|
||||
}
|
||||
}
|
||||
}
|
||||
spec: rules: [{
|
||||
host: parameter.domain
|
||||
http: paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
pathType: "ImplementationSpecific"
|
||||
backend: service: {
|
||||
name: context.name
|
||||
port: number: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
spec: {
|
||||
if parameter.classInSpec {
|
||||
ingressClassName: parameter.class
|
||||
}
|
||||
rules: [{
|
||||
host: parameter.domain
|
||||
http: paths: [
|
||||
for k, v in parameter.http {
|
||||
path: k
|
||||
pathType: "ImplementationSpecific"
|
||||
backend: service: {
|
||||
name: context.name
|
||||
port: number: v
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
@@ -76,5 +83,8 @@ template: {
|
||||
|
||||
// +usage=Specify the class of ingress to use
|
||||
class: *"nginx" | string
|
||||
|
||||
// +usage=Set ingress class in '.spec.ingressClassName' instead of 'kubernetes.io/ingress.class' annotation.
|
||||
classInSpec: *false | bool
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user