mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-23 14:23:54 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbed2b5cb3 | ||
|
|
8be75545bc | ||
|
|
d748096f7c | ||
|
|
d4ab93c232 | ||
|
|
37a656a292 | ||
|
|
3c94ac1bc1 | ||
|
|
8499dffcd7 | ||
|
|
30a6e85023 | ||
|
|
11530e2720 | ||
|
|
f53466bc7d | ||
|
|
e8ea8ec48f | ||
|
|
6c4c9bdf7e | ||
|
|
e7930a2da0 | ||
|
|
45e1de19dc | ||
|
|
d910bb7928 | ||
|
|
a580c9a44c | ||
|
|
8f5eaefd89 | ||
|
|
7c3a35ae87 | ||
|
|
ea0003f7cb | ||
|
|
3728857c82 | ||
|
|
8e6c49cb37 | ||
|
|
4b31274bda | ||
|
|
429e62d11b | ||
|
|
841a18189a | ||
|
|
59bd066c05 | ||
|
|
efa9dedb85 | ||
|
|
fdffde4dfd | ||
|
|
d08aa7d12c | ||
|
|
f9755a405f | ||
|
|
d751d95bac | ||
|
|
e86eec07e0 | ||
|
|
4abb5c6ced | ||
|
|
32d9a9ec94 | ||
|
|
f6f9ef4ded | ||
|
|
166c93d548 | ||
|
|
8f767068bf | ||
|
|
8d9e2a71e7 | ||
|
|
58b3bca537 |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
- name: Install StaticCheck
|
||||
run: GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck
|
||||
run: GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@v0.3.0
|
||||
|
||||
- name: Static Check
|
||||
run: staticcheck ./...
|
||||
|
||||
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUCKET: ${{ secrets.CLI_OSS_BUCKET }}
|
||||
ENDPOINT: ${{ secrets.CLI_OSS_ENDPOINT }}
|
||||
ACCESS_KEY: ${{ secrets.CLI_OSS_ACCESS_KEY }}
|
||||
ACCESS_KEY_SECRET: ${{ secrets.CLI_OSS_ACCESS_KEY_SECRET }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -104,6 +108,23 @@ jobs:
|
||||
name: sha256sums
|
||||
path: ./_bin/sha256-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.txt
|
||||
retention-days: 1
|
||||
- name: clear the asset
|
||||
run: |
|
||||
rm -rf ./_bin/vela/${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}
|
||||
mv ./_bin/vela/vela-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz ./_bin/vela/vela-${{ env.VELA_VERSION }}-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz
|
||||
mv ./_bin/vela/vela-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.zip ./_bin/vela/vela-${{ env.VELA_VERSION }}-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.zip
|
||||
- name: Install ossutil
|
||||
run: wget http://gosspublic.alicdn.com/ossutil/1.7.0/ossutil64 && chmod +x ossutil64 && mv ossutil64 ossutil
|
||||
- name: Configure Alibaba Cloud OSSUTIL
|
||||
run: ./ossutil --config-file .ossutilconfig config -i ${ACCESS_KEY} -k ${ACCESS_KEY_SECRET} -e ${ENDPOINT} -c .ossutilconfig
|
||||
- name: sync local to cloud
|
||||
run: ./ossutil --config-file .ossutilconfig sync ./_bin/vela oss://$BUCKET/binary/vela/${{ env.VELA_VERSION }}
|
||||
|
||||
- name: sync the latest version file
|
||||
run: |
|
||||
echo ${{ env.VELA_VERSION }} > ./latest_version
|
||||
./ossutil --config-file .ossutilconfig cp -u ./latest_version oss://$BUCKET/binary/vela/latest_version
|
||||
|
||||
|
||||
upload-plugin-homebrew:
|
||||
needs: build
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/interfaces"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
)
|
||||
@@ -121,7 +122,11 @@ func (in ManagedResource) NamespacedName() types.NamespacedName {
|
||||
// ResourceKey computes the key for managed resource, resources with the same key points to the same resource
|
||||
func (in ManagedResource) ResourceKey() string {
|
||||
gv, kind := in.GroupVersionKind().ToAPIVersionAndKind()
|
||||
return strings.Join([]string{gv, kind, in.Cluster, in.Namespace, in.Name}, "/")
|
||||
cluster := in.Cluster
|
||||
if cluster == "" {
|
||||
cluster = velatypes.ClusterLocalName
|
||||
}
|
||||
return strings.Join([]string{gv, kind, cluster, in.Namespace, in.Name}, "/")
|
||||
}
|
||||
|
||||
// ComponentKey computes the key for the component which managed resource belongs to
|
||||
|
||||
@@ -22,6 +22,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// ClusterLocalName the name for the hub cluster
|
||||
ClusterLocalName = "local"
|
||||
|
||||
// CredentialTypeInternal identifies the virtual cluster from internal kubevela system
|
||||
CredentialTypeInternal v1alpha1.CredentialType = "Internal"
|
||||
// CredentialTypeOCMManagedCluster identifies the virtual cluster from ocm
|
||||
|
||||
@@ -73,8 +73,8 @@ const (
|
||||
LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster"
|
||||
// LabelConfigIdentifier is the label for config identifier
|
||||
LabelConfigIdentifier = "config.oam.dev/identifier"
|
||||
// LabelConfigDescription is the label for config description
|
||||
LabelConfigDescription = "config.oam.dev/description"
|
||||
// AnnotationConfigDescription is the annotation for config description
|
||||
AnnotationConfigDescription = "config.oam.dev/description"
|
||||
// AnnotationConfigAlias is the annotation for config alias
|
||||
AnnotationConfigAlias = "config.oam.dev/alias"
|
||||
)
|
||||
@@ -148,3 +148,15 @@ const (
|
||||
// HelmRepository is the config type for Helm chart repository
|
||||
HelmRepository = "config-helm-repository"
|
||||
)
|
||||
|
||||
const (
|
||||
// TerraformComponentPrefix is the prefix of component type of terraform-xxx
|
||||
TerraformComponentPrefix = "terraform-"
|
||||
|
||||
// ProviderAppPrefix is the prefix of the application to create a Terraform Provider
|
||||
ProviderAppPrefix = "config-terraform-provider"
|
||||
// ProviderNamespace is the namespace of Terraform Cloud Provider
|
||||
ProviderNamespace = "default"
|
||||
// VelaCoreConfig is to mark application, config and its secret or Terraform provider lelong to a KubeVela config
|
||||
VelaCoreConfig = "velacore-config"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -106,7 +106,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the commands for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
|
||||
custom.definition.oam.dev/type.config.oam.dev: image-registry
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
@@ -35,20 +36,23 @@ spec:
|
||||
"config.oam.dev/sub-type": "auth"
|
||||
}
|
||||
}
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
stringData: {
|
||||
if parameter.auth != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
if parameter.auth != _|_ {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: ".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
})
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the container image for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
if _baseEnv != _|_ {
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar.value}}
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar}}
|
||||
// +patchStrategy=replace
|
||||
env: [ for envVar in _baseEnv if _delKeys[envVar.name] == _|_ && !_params.replace {
|
||||
name: envVar.name
|
||||
@@ -54,11 +54,15 @@ spec:
|
||||
value: _params.env[envVar.name]
|
||||
}
|
||||
if _params.env[envVar.name] == _|_ {
|
||||
value: envVar.value
|
||||
if envVar.value != _|_ {
|
||||
value: envVar.value
|
||||
}
|
||||
if envVar.valueFrom != _|_ {
|
||||
valueFrom: envVar.valueFrom
|
||||
}
|
||||
}
|
||||
}] + [ for k, v in _params.env if _delKeys[k] == _|_ && (_params.replace || _baseEnvMap[k] == _|_) {
|
||||
name: k
|
||||
value: v
|
||||
v
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -92,7 +96,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the environment variables for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -132,10 +132,9 @@ spec:
|
||||
parameter.labels
|
||||
}
|
||||
if parameter.addRevisionLabel {
|
||||
"app.oam.dev/appRevision": context.appRevision
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
"app.oam.dev/component": context.name
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
annotations: parameter.annotations
|
||||
@@ -333,7 +332,7 @@ spec:
|
||||
exposeType: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
|
||||
// +ignore
|
||||
// +usage=If addRevisionLabel is true, the appRevision label will be added to the underlying pods
|
||||
// +usage=If addRevisionLabel is true, the revision label will be added to the underlying pods
|
||||
addRevisionLabel: *false | bool
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
@@ -455,7 +454,7 @@ spec:
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -106,7 +106,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the commands for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
|
||||
custom.definition.oam.dev/type.config.oam.dev: image-registry
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
@@ -35,20 +36,23 @@ spec:
|
||||
"config.oam.dev/sub-type": "auth"
|
||||
}
|
||||
}
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
stringData: {
|
||||
if parameter.auth != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
if parameter.auth != _|_ {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: ".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
})
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
|
||||
@@ -69,7 +69,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the container image for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
if _baseEnv != _|_ {
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar.value}}
|
||||
_baseEnvMap: {for envVar in _baseEnv {"\(envVar.name)": envVar}}
|
||||
// +patchStrategy=replace
|
||||
env: [ for envVar in _baseEnv if _delKeys[envVar.name] == _|_ && !_params.replace {
|
||||
name: envVar.name
|
||||
@@ -54,11 +54,15 @@ spec:
|
||||
value: _params.env[envVar.name]
|
||||
}
|
||||
if _params.env[envVar.name] == _|_ {
|
||||
value: envVar.value
|
||||
if envVar.value != _|_ {
|
||||
value: envVar.value
|
||||
}
|
||||
if envVar.valueFrom != _|_ {
|
||||
valueFrom: envVar.valueFrom
|
||||
}
|
||||
}
|
||||
}] + [ for k, v in _params.env if _delKeys[k] == _|_ && (_params.replace || _baseEnvMap[k] == _|_) {
|
||||
name: k
|
||||
value: v
|
||||
v
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -92,7 +96,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the environment variables for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -132,10 +132,9 @@ spec:
|
||||
parameter.labels
|
||||
}
|
||||
if parameter.addRevisionLabel {
|
||||
"app.oam.dev/appRevision": context.appRevision
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
"app.oam.dev/component": context.name
|
||||
"app.oam.dev/revision": context.revision
|
||||
}
|
||||
if parameter.annotations != _|_ {
|
||||
annotations: parameter.annotations
|
||||
@@ -333,7 +332,7 @@ spec:
|
||||
exposeType: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
|
||||
// +ignore
|
||||
// +usage=If addRevisionLabel is true, the appRevision label will be added to the underlying pods
|
||||
// +usage=If addRevisionLabel is true, the revision label will be added to the underlying pods
|
||||
addRevisionLabel: *false | bool
|
||||
|
||||
// +usage=Commands to run in the container
|
||||
@@ -455,7 +454,7 @@ spec:
|
||||
readinessProbe?: #HealthProbe
|
||||
|
||||
// +usage=Specify the hostAliases to add
|
||||
hostAliases: [...{
|
||||
hostAliases?: [...{
|
||||
ip: string
|
||||
hostnames: [...string]
|
||||
}]
|
||||
|
||||
23
e2e/addon/mock/testdata/mock-addon/metadata.yaml
vendored
Normal file
23
e2e/addon/mock/testdata/mock-addon/metadata.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: mock-addon
|
||||
version: 1.0.0
|
||||
description: Extended workload to do continuous and progressive delivery
|
||||
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
|
||||
url: https://fluxcd.io
|
||||
|
||||
tags:
|
||||
- extended_workload
|
||||
- gitops
|
||||
- only_example
|
||||
|
||||
deployTo:
|
||||
control_plane: true
|
||||
runtime_cluster: false
|
||||
|
||||
dependencies: []
|
||||
#- name: addon_name
|
||||
|
||||
# set invisible means this won't be list and will be enabled when depended on
|
||||
# for example, terraform-alibaba depends on terraform which is invisible,
|
||||
# when terraform-alibaba is enabled, terraform will be enabled automatically
|
||||
# default: false
|
||||
invisible: false
|
||||
14
e2e/addon/mock/testdata/mock-addon/template.yaml
vendored
Normal file
14
e2e/addon/mock/testdata/mock-addon/template.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: mock-addon
|
||||
namespace: vela-system
|
||||
spec:
|
||||
components:
|
||||
- name: ns-example-system
|
||||
type: raw
|
||||
properties:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: mock-system
|
||||
@@ -129,7 +129,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).Should(ContainSubstring(showTdResult))
|
||||
})
|
||||
PIt("Test show componentDefinition use Helm Charts as Workload", func() {
|
||||
It("Test show componentDefinition use Helm Charts as Workload", func() {
|
||||
Eventually(func() string {
|
||||
cdName := "test-webapp-chart"
|
||||
output, _ := e2e.Exec(fmt.Sprintf("kubectl-vela show %s -n default", cdName))
|
||||
|
||||
14
go.mod
14
go.mod
@@ -63,11 +63,11 @@ require (
|
||||
github.com/wonderflow/cert-manager-api v1.0.3
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.18.1
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/tools v0.1.6 // indirect
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
@@ -97,6 +97,8 @@ require (
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
require github.com/robfig/cron/v3 v3.0.1
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.81.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
@@ -106,7 +108,7 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
@@ -249,8 +251,8 @@ require (
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
|
||||
27
go.sum
27
go.sum
@@ -110,8 +110,9 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
@@ -1437,6 +1438,8 @@ github.com/rancher/wrangler v0.4.0/go.mod h1:1cR91WLhZgkZ+U4fV9nVuXqKurWbgXcIReU
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -1654,7 +1657,7 @@ github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||
@@ -1796,8 +1799,9 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1841,8 +1845,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1907,9 +1912,10 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1922,8 +1928,9 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -2046,7 +2053,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
@@ -2202,8 +2209,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6 h1:SIasE1FVIQOWz2GEAHFOmoW7xchJcqlucjSULTL0Ag4=
|
||||
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -25,7 +25,7 @@ ifeq (, $(shell which staticcheck))
|
||||
@{ \
|
||||
set -e ;\
|
||||
echo 'installing honnef.co/go/tools/cmd/staticcheck ' ;\
|
||||
GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck ;\
|
||||
GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@v0.3.0 ;\
|
||||
}
|
||||
STATICCHECK=$(GOBIN)/staticcheck
|
||||
else
|
||||
@@ -56,7 +56,7 @@ else
|
||||
CUE=$(shell which cue)
|
||||
endif
|
||||
|
||||
KUSTOMIZE_VERSION ?= 3.8.2
|
||||
KUSTOMIZE_VERSION ?= 4.5.4
|
||||
|
||||
.PHONY: kustomize
|
||||
kustomize:
|
||||
|
||||
@@ -49,6 +49,7 @@ e2e-apiserver-test:
|
||||
pkill vela_addon_mock_server || true
|
||||
go run ./e2e/addon/mock/vela_addon_mock_server.go &
|
||||
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_apiserver_test.out ./test/e2e-apiserver-test
|
||||
sleep 15
|
||||
@$(OK) tests pass
|
||||
|
||||
.PHONY: e2e-test
|
||||
|
||||
@@ -195,6 +195,10 @@ func GetPatternFromItem(it Item, r AsyncReader, rootPath string) string {
|
||||
if strings.HasPrefix(relativePath, strings.Join([]string{rootPath, p.Value}, "/")) {
|
||||
return p.Value
|
||||
}
|
||||
if strings.HasPrefix(relativePath, filepath.Join(rootPath, p.Value)) {
|
||||
// for enable addon by load dir, compatible with linux or windows os
|
||||
return p.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -357,7 +361,7 @@ func readResFile(a *InstallPackage, reader AsyncReader, readPath string) error {
|
||||
if filename == "parameter.cue" {
|
||||
return nil
|
||||
}
|
||||
file := ElementFile{Data: b, Name: path.Base(readPath)}
|
||||
file := ElementFile{Data: b, Name: filepath.Base(readPath)}
|
||||
switch filepath.Ext(filename) {
|
||||
case ".cue":
|
||||
a.CUETemplates = append(a.CUETemplates, file)
|
||||
@@ -375,7 +379,7 @@ func readDefSchemaFile(a *InstallPackage, reader AsyncReader, readPath string) e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.DefSchemas = append(a.DefSchemas, ElementFile{Data: b, Name: path.Base(readPath)})
|
||||
a.DefSchemas = append(a.DefSchemas, ElementFile{Data: b, Name: filepath.Base(readPath)})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -386,7 +390,7 @@ func readDefFile(a *UIData, reader AsyncReader, readPath string) error {
|
||||
return err
|
||||
}
|
||||
filename := path.Base(readPath)
|
||||
file := ElementFile{Data: b, Name: path.Base(readPath)}
|
||||
file := ElementFile{Data: b, Name: filepath.Base(readPath)}
|
||||
switch filepath.Ext(filename) {
|
||||
case ".cue":
|
||||
a.CUEDefinitions = append(a.CUEDefinitions, file)
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
|
||||
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -362,6 +364,21 @@ var _ = Describe("func addon update ", func() {
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("test enable addon in local dir", func() {
|
||||
BeforeEach(func() {
|
||||
app := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-example"}}
|
||||
Expect(k8sClient.Delete(ctx, &app)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{}))
|
||||
})
|
||||
|
||||
It("test enable addon by local dir", func() {
|
||||
ctx := context.Background()
|
||||
err := EnableAddonByLocalDir(ctx, "example", "./testdata/example", k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, map[string]interface{}{"example": "test"})
|
||||
Expect(err).Should(BeNil())
|
||||
app := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-example"}, &app)).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
const (
|
||||
appYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
|
||||
@@ -19,12 +19,12 @@ package addon
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
)
|
||||
|
||||
// We have three addon layer here
|
||||
@@ -119,28 +119,27 @@ func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error
|
||||
// ListUIData will always list UIData from cache first, if not exist, read from source.
|
||||
func (u *Cache) ListUIData(r Registry) ([]*UIData, error) {
|
||||
var err error
|
||||
listAddons := u.listCachedUIData(r.Name)
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
var listAddons []*UIData
|
||||
if !IsVersionRegistry(r) {
|
||||
addonMeta, err := u.ListAddonMeta(r)
|
||||
listAddons = u.listCachedUIData(r.Name)
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
listAddons, err = u.listUIDataAndCache(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
|
||||
}
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
listAddons, err = versionedRegistry.ListAddon()
|
||||
listAddons = u.listVersionRegistryCachedUIData(r.Name)
|
||||
if listAddons != nil {
|
||||
return listAddons, nil
|
||||
}
|
||||
listAddons, err = u.listVersionRegistryUIDataAndCache(r)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, listAddons)
|
||||
|
||||
return listAddons, nil
|
||||
}
|
||||
|
||||
@@ -175,6 +174,27 @@ func (u *Cache) listCachedUIData(name string) []*UIData {
|
||||
return d
|
||||
}
|
||||
|
||||
// listVersionRegistryCachedUIData will get cached addons from specified VersionRegistry in cache
|
||||
func (u *Cache) listVersionRegistryCachedUIData(name string) []*UIData {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
d, ok := u.versionedUIData[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var uiDatas []*UIData
|
||||
for version, uiData := range d {
|
||||
if !strings.Contains(version, "-latest") {
|
||||
uiDatas = append(uiDatas, uiData)
|
||||
}
|
||||
}
|
||||
|
||||
return uiDatas
|
||||
}
|
||||
|
||||
// getCachedAddonMeta will get cached registry meta from specified registry in cache
|
||||
func (u *Cache) getCachedAddonMeta(name string) map[string]SourceMeta {
|
||||
if u == nil {
|
||||
@@ -260,35 +280,51 @@ func (u *Cache) discoverAndRefreshRegistry() {
|
||||
|
||||
for _, r := range registries {
|
||||
if !IsVersionRegistry(r) {
|
||||
registryMeta, err := r.ListAddonMeta()
|
||||
_, err = u.listUIDataAndCache(r)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonMeta2Cache(r.Name, registryMeta)
|
||||
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, uiData)
|
||||
} else {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
uiDatas, err := versionedRegistry.ListAddon()
|
||||
_, err = u.listVersionRegistryUIDataAndCache(r)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
for _, addon := range uiDatas {
|
||||
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addon from registry %s, addon %s version %s for cache updating, %v", addon.Name, r.Name, err)
|
||||
continue
|
||||
}
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
|
||||
// we also no version key, if use get addonUIData without version will return this vale as latest data.
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Cache) listUIDataAndCache(r Registry) ([]*UIData, error) {
|
||||
registryMeta, err := r.ListAddonMeta()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
u.putAddonMeta2Cache(r.Name, registryMeta)
|
||||
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
u.putAddonUIData2Cache(r.Name, uiData)
|
||||
return uiData, nil
|
||||
}
|
||||
|
||||
func (u *Cache) listVersionRegistryUIDataAndCache(r Registry) ([]*UIData, error) {
|
||||
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
|
||||
uiDatas, err := versionedRegistry.ListAddon()
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, addon := range uiDatas {
|
||||
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to get addon from versioned registry %s, addon %s version %s for cache updating, %v", r.Name, addon.Name, addon.Version, err)
|
||||
continue
|
||||
}
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
|
||||
// we also no version key, if use get addonUIData without version will return this vale as latest data.
|
||||
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
|
||||
}
|
||||
return uiDatas, nil
|
||||
}
|
||||
|
||||
@@ -31,3 +31,63 @@ func TestPutVersionedUIData2cache(t *testing.T) {
|
||||
assert.NotEmpty(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"])
|
||||
assert.Equal(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"].Name, "fluxcd")
|
||||
}
|
||||
|
||||
func TestPutAddonUIData2Cache(t *testing.T) {
|
||||
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
|
||||
addons := []*UIData{&uiData}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonUIData2Cache(name, addons)
|
||||
assert.NotEmpty(t, u.uiData)
|
||||
assert.Equal(t, u.uiData[name], addons)
|
||||
}
|
||||
|
||||
func TestListCachedUIData(t *testing.T) {
|
||||
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
|
||||
addons := []*UIData{&uiData}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonUIData2Cache(name, addons)
|
||||
|
||||
assert.Equal(t, u.listCachedUIData(name), addons)
|
||||
}
|
||||
|
||||
func TestPutAddonMeta2Cache(t *testing.T) {
|
||||
addonMeta := map[string]SourceMeta{
|
||||
"fluxcd": {
|
||||
Name: "fluxcd",
|
||||
Items: []Item{
|
||||
&OSSItem{
|
||||
tp: FileType,
|
||||
path: "fluxcd/definitions/helm-release.yaml",
|
||||
name: "helm-release.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonMeta2Cache(name, addonMeta)
|
||||
assert.NotEmpty(t, u.registryMeta)
|
||||
assert.Equal(t, u.registryMeta[name], addonMeta)
|
||||
}
|
||||
|
||||
func TestGetCachedAddonMeta(t *testing.T) {
|
||||
addonMeta := map[string]SourceMeta{
|
||||
"fluxcd": {
|
||||
Name: "fluxcd",
|
||||
Items: []Item{
|
||||
&OSSItem{
|
||||
tp: FileType,
|
||||
path: "fluxcd/definitions/helm-release.yaml",
|
||||
name: "helm-release.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
name := "helm-repo"
|
||||
u := NewCache(nil)
|
||||
u.putAddonMeta2Cache(name, addonMeta)
|
||||
|
||||
assert.Equal(t, u.getCachedAddonMeta(name), addonMeta)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -92,7 +93,11 @@ func DisableAddon(ctx context.Context, cli client.Client, name string, config *r
|
||||
|
||||
// EnableAddonByLocalDir enable an addon from local dir
|
||||
func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, dc *discovery.DiscoveryClient, applicator apply.Applicator, config *rest.Config, args map[string]interface{}) error {
|
||||
r := localReader{dir: dir, name: name}
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := localReader{dir: absDir, name: name}
|
||||
metas, err := r.ListAddonMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -37,8 +37,10 @@ func (l localReader) ListAddonMeta() (map[string]SourceMeta, error) {
|
||||
}
|
||||
|
||||
func (l localReader) ReadFile(path string) (string, error) {
|
||||
file := strings.TrimPrefix(path, l.name+"/")
|
||||
b, err := ioutil.ReadFile(filepath.Clean(filepath.Join(l.dir, file)))
|
||||
path = strings.TrimPrefix(path, l.name+"/")
|
||||
// for windows
|
||||
path = strings.TrimPrefix(path, l.name+"\\")
|
||||
b, err := ioutil.ReadFile(filepath.Clean(filepath.Join(l.dir, path)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -46,7 +48,8 @@ func (l localReader) ReadFile(path string) (string, error) {
|
||||
}
|
||||
|
||||
func (l localReader) RelativePath(item Item) string {
|
||||
return filepath.Join(l.name, strings.TrimPrefix(item.GetPath()+"/", l.dir))
|
||||
file := strings.TrimPrefix(item.GetPath(), filepath.Clean(l.dir))
|
||||
return filepath.Join(l.name, file)
|
||||
}
|
||||
|
||||
func recursiveFetchFiles(path string, metas *SourceMeta) error {
|
||||
@@ -60,7 +63,7 @@ func recursiveFetchFiles(path string, metas *SourceMeta) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
metas.Items = append(metas.Items, OSSItem{tp: "file", path: fmt.Sprintf("%s/%s", path, file.Name()), name: file.Name()})
|
||||
metas.Items = append(metas.Items, OSSItem{tp: "file", path: filepath.Join(path, file.Name()), name: file.Name()})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -68,6 +68,43 @@ type HelmSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
}
|
||||
|
||||
// SafeCopier is an interface to copy Struct without sensitive fields, such as Token, Username, Password
|
||||
type SafeCopier interface {
|
||||
SafeCopy() interface{}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
func (g *GitAddonSource) SafeCopy() *GitAddonSource {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
return &GitAddonSource{
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
func (g *GiteeAddonSource) SafeCopy() *GiteeAddonSource {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
return &GiteeAddonSource{
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Username, Password
|
||||
func (h *HelmSource) SafeCopy() *HelmSource {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
return &HelmSource{
|
||||
URL: h.URL,
|
||||
}
|
||||
}
|
||||
|
||||
// Item is a partial interface for github.RepositoryContent
|
||||
type Item interface {
|
||||
// GetType return "dir" or "file"
|
||||
|
||||
@@ -115,3 +115,30 @@ func TestConvert2OssItem(t *testing.T) {
|
||||
assert.Equal(t, expectItemCase, addonMetas)
|
||||
|
||||
}
|
||||
|
||||
func TestSafeCopy(t *testing.T) {
|
||||
var git *GitAddonSource
|
||||
sgit := git.SafeCopy()
|
||||
assert.Nil(t, sgit)
|
||||
git = &GitAddonSource{URL: "http://github.com/kubevela", Path: "addons", Token: "123456"}
|
||||
sgit = git.SafeCopy()
|
||||
assert.Empty(t, sgit.Token)
|
||||
assert.Equal(t, "http://github.com/kubevela", sgit.URL)
|
||||
assert.Equal(t, "addons", sgit.Path)
|
||||
|
||||
var gitee *GiteeAddonSource
|
||||
sgitee := gitee.SafeCopy()
|
||||
assert.Nil(t, sgitee)
|
||||
gitee = &GiteeAddonSource{URL: "http://gitee.com/kubevela", Path: "addons", Token: "123456"}
|
||||
sgitee = gitee.SafeCopy()
|
||||
assert.Empty(t, sgitee.Token)
|
||||
assert.Equal(t, "http://gitee.com/kubevela", sgitee.URL)
|
||||
assert.Equal(t, "addons", sgitee.Path)
|
||||
|
||||
var helm *HelmSource
|
||||
shelm := helm.SafeCopy()
|
||||
assert.Nil(t, shelm)
|
||||
helm = &HelmSource{URL: "https://hub.vela.com/chartrepo/addons"}
|
||||
shelm = helm.SafeCopy()
|
||||
assert.Equal(t, "https://hub.vela.com/chartrepo/addons", shelm.URL)
|
||||
}
|
||||
|
||||
100
pkg/apiserver/collect/suit_test.go
Normal file
100
pkg/apiserver/collect/suit_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
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 collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/kubeapi"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestCalculateJob(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Caclculate systemInfo cronJob")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
By("bootstrapping test environment")
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
CRDDirectoryPaths: []string{"../../../charts/vela-core/crds"},
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
By("new kube client success")
|
||||
clients.SetKubeClient(k8sClient)
|
||||
Expect(err).Should(BeNil())
|
||||
close(done)
|
||||
}, 240)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
func NewDatastore(cfg datastore.Config) (ds datastore.DataStore, err error) {
|
||||
switch cfg.Type {
|
||||
case "mongodb":
|
||||
ds, err = mongodb.New(context.Background(), cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
|
||||
}
|
||||
case "kubeapi":
|
||||
ds, err = kubeapi.New(context.Background(), cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create mongodb datastore instance failure %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("not support datastore type %s", cfg.Type)
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
331
pkg/apiserver/collect/system_info_collect.go
Normal file
331
pkg/apiserver/collect/system_info_collect.go
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
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 collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
client2 "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
// TopKFrequent top frequency component or trait definition
|
||||
var TopKFrequent = 5
|
||||
|
||||
// CrontabSpec the cron spec of job running
|
||||
var CrontabSpec = "0 0 * * *"
|
||||
|
||||
// maximum tires is 5, initial duration is 1 minute
|
||||
var waitBackOff = wait.Backoff{
|
||||
Steps: 5,
|
||||
Duration: 1 * time.Minute,
|
||||
Factor: 5.0,
|
||||
Jitter: 0.1,
|
||||
}
|
||||
|
||||
// InfoCalculateCronJob is the cronJob to calculate the system info store in db
|
||||
type InfoCalculateCronJob struct {
|
||||
ds datastore.DataStore
|
||||
}
|
||||
|
||||
// StartCalculatingInfoCronJob will start the system info calculating job.
|
||||
func StartCalculatingInfoCronJob(ds datastore.DataStore) {
|
||||
i := InfoCalculateCronJob{
|
||||
ds: ds,
|
||||
}
|
||||
|
||||
// run calculate job in 0:00 of every day
|
||||
i.start(CrontabSpec)
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) start(cronSpec string) {
|
||||
c := cron.New(cron.WithChain(
|
||||
// don't let job panic crash whole api-server process
|
||||
cron.Recover(cron.DefaultLogger),
|
||||
))
|
||||
|
||||
// ignore the entityId and error, the cron spec is defined by hard code, mustn't generate error
|
||||
_, _ = c.AddFunc(cronSpec, func() {
|
||||
|
||||
// ExponentialBackoff retry this job
|
||||
err := retry.OnError(waitBackOff, func(err error) bool {
|
||||
// always retry
|
||||
return true
|
||||
}, func() error {
|
||||
if err := i.run(); err != nil {
|
||||
log.Logger.Errorf("Failed to calculate systemInfo, will try again after several minute error %v", err)
|
||||
return err
|
||||
}
|
||||
log.Logger.Info("Successfully to calculate systemInfo")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Logger.Errorf("After 5 tries the calculating cronJob failed: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
c.Start()
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) run() error {
|
||||
ctx := context.Background()
|
||||
systemInfo := model.SystemInfo{}
|
||||
e, err := i.ds.List(ctx, &systemInfo, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if no systemInfo means velaux have not have not send get request,so skip calculate job
|
||||
if len(e) == 0 {
|
||||
return nil
|
||||
}
|
||||
info, ok := e[0].(*model.SystemInfo)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// if disable collection skip calculate job
|
||||
if !info.EnableCollection {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := i.calculateAndUpdate(ctx, *info); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateAndUpdate(ctx context.Context, systemInfo model.SystemInfo) error {
|
||||
|
||||
appCount, topKComp, topKTrait, topWorkflowStep, topKPolicy, err := i.calculateAppInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enabledAddon, err := i.calculateAddonInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterCount, err := i.calculateClusterInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statisticInfo := model.StatisticInfo{
|
||||
AppCount: genCountInfo(appCount),
|
||||
TopKCompDef: topKComp,
|
||||
TopKTraitDef: topKTrait,
|
||||
TopKWorkflowStepDef: topWorkflowStep,
|
||||
TopKPolicyDef: topKPolicy,
|
||||
ClusterCount: genClusterCountInfo(clusterCount),
|
||||
EnabledAddon: enabledAddon,
|
||||
UpdateTime: time.Now(),
|
||||
}
|
||||
|
||||
systemInfo.StatisticInfo = statisticInfo
|
||||
if err := i.ds.Put(ctx, &systemInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateAppInfo(ctx context.Context) (int, []string, []string, []string, []string, error) {
|
||||
var err error
|
||||
var appCount int
|
||||
compDef := map[string]int{}
|
||||
traitDef := map[string]int{}
|
||||
workflowDef := map[string]int{}
|
||||
policyDef := map[string]int{}
|
||||
|
||||
var app = model.Application{}
|
||||
entities, err := i.ds.List(ctx, &app, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, entity := range entities {
|
||||
appModel, ok := entity.(*model.Application)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
appCount++
|
||||
comp := model.ApplicationComponent{
|
||||
AppPrimaryKey: appModel.Name,
|
||||
}
|
||||
comps, err := i.ds.List(ctx, &comp, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range comps {
|
||||
c, ok := e.(*model.ApplicationComponent)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
compDef[c.Type]++
|
||||
for _, t := range c.Traits {
|
||||
traitDef[t.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
workflow := model.Workflow{
|
||||
AppPrimaryKey: app.PrimaryKey(),
|
||||
}
|
||||
workflows, err := i.ds.List(ctx, &workflow, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range workflows {
|
||||
w, ok := e.(*model.Workflow)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, step := range w.Steps {
|
||||
workflowDef[step.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
policy := model.ApplicationPolicy{
|
||||
AppPrimaryKey: app.PrimaryKey(),
|
||||
}
|
||||
policies, err := i.ds.List(ctx, &policy, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, e := range policies {
|
||||
p, ok := e.(*model.ApplicationPolicy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
policyDef[p.Type]++
|
||||
}
|
||||
}
|
||||
|
||||
return appCount, topKFrequent(compDef, TopKFrequent), topKFrequent(traitDef, TopKFrequent), topKFrequent(workflowDef, TopKFrequent), topKFrequent(policyDef, TopKFrequent), nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateAddonInfo(ctx context.Context) (map[string]string, error) {
|
||||
client, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apps := &v1beta1.ApplicationList{}
|
||||
if err := client.List(ctx, apps, client2.InNamespace(types.DefaultKubeVelaNS), client2.HasLabels{oam.LabelAddonName}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := map[string]string{}
|
||||
for _, application := range apps.Items {
|
||||
if addonName := application.Labels[oam.LabelAddonName]; addonName != "" {
|
||||
var status string
|
||||
switch application.Status.Phase {
|
||||
case common.ApplicationRunning:
|
||||
status = "enabled"
|
||||
case common.ApplicationDeleting:
|
||||
status = "disabling"
|
||||
default:
|
||||
status = "enabling"
|
||||
}
|
||||
res[addonName] = status
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (i InfoCalculateCronJob) calculateClusterInfo(ctx context.Context) (int, error) {
|
||||
client, err := clients.GetKubeClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cs, err := multicluster.ListVirtualClusters(ctx, client)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(cs), nil
|
||||
}
|
||||
|
||||
type defPair struct {
|
||||
name string
|
||||
count int
|
||||
}
|
||||
|
||||
func topKFrequent(defs map[string]int, k int) []string {
|
||||
var pairs []defPair
|
||||
var res []string
|
||||
for name, num := range defs {
|
||||
pairs = append(pairs, defPair{name: name, count: num})
|
||||
}
|
||||
sort.Slice(pairs, func(i, j int) bool {
|
||||
return pairs[i].count >= pairs[j].count
|
||||
})
|
||||
i := 0
|
||||
for _, pair := range pairs {
|
||||
res = append(res, pair.name)
|
||||
i++
|
||||
if i == k {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func genCountInfo(num int) string {
|
||||
switch {
|
||||
case num < 10:
|
||||
return "<10"
|
||||
case num < 50:
|
||||
return "<50"
|
||||
case num < 100:
|
||||
return "<100"
|
||||
case num < 500:
|
||||
return "<500"
|
||||
case num < 2000:
|
||||
return "<2000"
|
||||
case num < 5000:
|
||||
return "<5000"
|
||||
case num < 10000:
|
||||
return "<10000"
|
||||
default:
|
||||
return ">=10000"
|
||||
}
|
||||
}
|
||||
|
||||
func genClusterCountInfo(num int) string {
|
||||
switch {
|
||||
case num < 3:
|
||||
return "<3"
|
||||
case num < 10:
|
||||
return "<10"
|
||||
case num < 50:
|
||||
return "<50"
|
||||
default:
|
||||
return ">=50"
|
||||
}
|
||||
}
|
||||
273
pkg/apiserver/collect/system_info_collect_test.go
Normal file
273
pkg/apiserver/collect/system_info_collect_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
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 collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/gomega/format"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test calculate cronJob", func() {
|
||||
var (
|
||||
ds datastore.DataStore
|
||||
testProject string
|
||||
i InfoCalculateCronJob
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
mockDataInDs := func() {
|
||||
app1 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app1", Project: testProject}
|
||||
app2 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app2", Project: testProject}
|
||||
trait1 := model.ApplicationTrait{Type: "rollout"}
|
||||
trait2 := model.ApplicationTrait{Type: "expose"}
|
||||
trait3 := model.ApplicationTrait{Type: "rollout"}
|
||||
trait4 := model.ApplicationTrait{Type: "patch"}
|
||||
trait5 := model.ApplicationTrait{Type: "patch"}
|
||||
trait6 := model.ApplicationTrait{Type: "rollout"}
|
||||
appComp1 := model.ApplicationComponent{AppPrimaryKey: app1.PrimaryKey(), Name: "comp1", Type: "helm", Traits: []model.ApplicationTrait{trait1, trait4}}
|
||||
appComp2 := model.ApplicationComponent{AppPrimaryKey: app2.PrimaryKey(), Name: "comp2", Type: "webservice", Traits: []model.ApplicationTrait{trait3}}
|
||||
appComp3 := model.ApplicationComponent{AppPrimaryKey: app2.PrimaryKey(), Name: "comp3", Type: "webservice", Traits: []model.ApplicationTrait{trait2, trait5, trait6}}
|
||||
Expect(ds.Add(ctx, &app1)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &app2)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &appComp1)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &appComp2)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(ds.Add(ctx, &appComp3)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-fluxcd", Labels: map[string]string{oam.LabelAddonName: "fluxcd"}}, Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-rollout", Labels: map[string]string{oam.LabelAddonName: "rollout"}}, Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
|
||||
Expect(ds).ShouldNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
testProject = "test-cronjob-project"
|
||||
mockDataInDs()
|
||||
i = InfoCalculateCronJob{
|
||||
ds: ds,
|
||||
}
|
||||
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: true}
|
||||
Expect(ds.Add(ctx, &systemInfo)).Should(SatisfyAny(BeNil(), DataExistMatcher{}))
|
||||
})
|
||||
|
||||
It("Test calculate app Info", func() {
|
||||
appNum, topKCom, topKTrait, _, _, err := i.calculateAppInfo(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(appNum).Should(BeEquivalentTo(2))
|
||||
Expect(topKCom).Should(BeEquivalentTo([]string{"webservice", "helm"}))
|
||||
Expect(topKTrait).Should(BeEquivalentTo([]string{"rollout", "patch", "expose"}))
|
||||
})
|
||||
|
||||
It("Test calculate addon Info", func() {
|
||||
enabledAddon, err := i.calculateAddonInfo(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(enabledAddon).Should(BeEquivalentTo(map[string]string{
|
||||
"fluxcd": "enabling",
|
||||
"rollout": "enabling",
|
||||
}))
|
||||
})
|
||||
|
||||
It("Test calculate cluster Info", func() {
|
||||
clusterNum, err := i.calculateClusterInfo(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(clusterNum).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
It("Test calculateAndUpdate func", func() {
|
||||
systemInfo := model.SystemInfo{}
|
||||
es, err := ds.List(ctx, &systemInfo, &datastore.ListOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(es)).Should(BeEquivalentTo(1))
|
||||
info, ok := es[0].(*model.SystemInfo)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(info.InstallID).Should(BeEquivalentTo("test-id"))
|
||||
|
||||
Expect(i.calculateAndUpdate(ctx, *info)).Should(BeNil())
|
||||
|
||||
systemInfo = model.SystemInfo{}
|
||||
es, err = ds.List(ctx, &systemInfo, &datastore.ListOptions{})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(es)).Should(BeEquivalentTo(1))
|
||||
info, ok = es[0].(*model.SystemInfo)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(info.InstallID).Should(BeEquivalentTo("test-id"))
|
||||
Expect(info.StatisticInfo.AppCount).Should(BeEquivalentTo("<10"))
|
||||
Expect(info.StatisticInfo.ClusterCount).Should(BeEquivalentTo("<3"))
|
||||
Expect(info.StatisticInfo.TopKCompDef).Should(BeEquivalentTo([]string{"webservice", "helm"}))
|
||||
Expect(info.StatisticInfo.TopKTraitDef).Should(BeEquivalentTo([]string{"rollout", "patch", "expose"}))
|
||||
Expect(info.StatisticInfo.EnabledAddon).Should(BeEquivalentTo(map[string]string{
|
||||
"fluxcd": "enabling",
|
||||
"rollout": "enabling",
|
||||
}))
|
||||
})
|
||||
|
||||
It("Test run func", func() {
|
||||
app3 := model.Application{BaseModel: model.BaseModel{CreateTime: time.Now()}, Name: "app3", Project: testProject}
|
||||
Expect(ds.Add(ctx, &app3)).Should(BeNil())
|
||||
|
||||
systemInfo := model.SystemInfo{InstallID: "test-id", EnableCollection: false}
|
||||
Expect(ds.Put(ctx, &systemInfo)).Should(BeNil())
|
||||
Expect(i.run()).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
func TestGenCountInfo(t *testing.T) {
|
||||
testcases := []struct {
|
||||
count int
|
||||
res string
|
||||
}{
|
||||
{
|
||||
count: 3,
|
||||
res: "<10",
|
||||
},
|
||||
{
|
||||
count: 14,
|
||||
res: "<50",
|
||||
},
|
||||
{
|
||||
count: 80,
|
||||
res: "<100",
|
||||
},
|
||||
{
|
||||
count: 350,
|
||||
res: "<500",
|
||||
},
|
||||
{
|
||||
count: 1800,
|
||||
res: "<2000",
|
||||
},
|
||||
{
|
||||
count: 4000,
|
||||
res: "<5000",
|
||||
},
|
||||
{
|
||||
count: 9000,
|
||||
res: "<10000",
|
||||
},
|
||||
{
|
||||
count: 30000,
|
||||
res: ">=10000",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, genCountInfo(testcase.count), testcase.res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenClusterCountInfo(t *testing.T) {
|
||||
testcases := []struct {
|
||||
count int
|
||||
res string
|
||||
}{
|
||||
{
|
||||
count: 2,
|
||||
res: "<3",
|
||||
},
|
||||
{
|
||||
count: 7,
|
||||
res: "<10",
|
||||
},
|
||||
{
|
||||
count: 34,
|
||||
res: "<50",
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
res: ">=50",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, genClusterCountInfo(testcase.count), testcase.res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopKFrequent(t *testing.T) {
|
||||
testCases := []struct {
|
||||
def map[string]int
|
||||
k int
|
||||
res []string
|
||||
}{
|
||||
{
|
||||
def: map[string]int{
|
||||
"rollout": 4,
|
||||
"patch": 3,
|
||||
"expose": 6,
|
||||
},
|
||||
k: 3,
|
||||
res: []string{"expose", "rollout", "patch"},
|
||||
},
|
||||
{
|
||||
// just return top2
|
||||
def: map[string]int{
|
||||
"rollout": 4,
|
||||
"patch": 3,
|
||||
"expose": 6,
|
||||
},
|
||||
k: 2,
|
||||
res: []string{"expose", "rollout"},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
assert.DeepEqual(t, topKFrequent(testCase.def, testCase.k), testCase.res)
|
||||
}
|
||||
}
|
||||
|
||||
type DataExistMatcher struct{}
|
||||
|
||||
// Match matches error.
|
||||
func (matcher DataExistMatcher) Match(actual interface{}) (success bool, err error) {
|
||||
if actual == nil {
|
||||
return false, nil
|
||||
}
|
||||
actualError := actual.(error)
|
||||
return errors.Is(actualError, datastore.ErrRecordExist), nil
|
||||
}
|
||||
|
||||
// FailureMessage builds an error message.
|
||||
func (matcher DataExistMatcher) FailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "to be already exist")
|
||||
}
|
||||
|
||||
// NegatedFailureMessage builds an error message.
|
||||
func (matcher DataExistMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "not to be already exist")
|
||||
}
|
||||
@@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
func init() {
|
||||
RegisterModel(&SystemInfo{})
|
||||
}
|
||||
@@ -30,10 +32,11 @@ const (
|
||||
// SystemInfo systemInfo model
|
||||
type SystemInfo struct {
|
||||
BaseModel
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
DexConfig DexConfig `json:"dexConfig,omitempty"`
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
DexConfig DexConfig `json:"dexConfig,omitempty"`
|
||||
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
|
||||
}
|
||||
|
||||
// DexConfig dex config
|
||||
@@ -46,6 +49,18 @@ type DexConfig struct {
|
||||
EnablePasswordDB bool `json:"enablePasswordDB"`
|
||||
}
|
||||
|
||||
// StatisticInfo the system statistic info
|
||||
type StatisticInfo struct {
|
||||
ClusterCount string `json:"clusterCount,omitempty"`
|
||||
AppCount string `json:"appCount,omitempty"`
|
||||
EnabledAddon map[string]string `json:"enabledAddon,omitempty"`
|
||||
TopKCompDef []string `json:"topKCompDef,omitempty"`
|
||||
TopKTraitDef []string `json:"topKTraitDef,omitempty"`
|
||||
TopKWorkflowStepDef []string `json:"topKWorkflowStepDef,omitempty"`
|
||||
TopKPolicyDef []string `json:"topKPolicyDef,omitempty"`
|
||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
// DexStorage dex storage
|
||||
type DexStorage struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
@@ -199,6 +199,7 @@ type Config struct {
|
||||
Name string `json:"name"`
|
||||
Project string `json:"project"`
|
||||
Identifier string `json:"identifier"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
CreatedTime *time.Time `json:"createdTime"`
|
||||
UpdatedTime *time.Time `json:"updatedTime"`
|
||||
@@ -408,6 +409,7 @@ type CreateApplicationRequest struct {
|
||||
type CreateConfigRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
Project string `json:"project"`
|
||||
ComponentType string `json:"componentType" validate:"checkname"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
@@ -1114,13 +1116,27 @@ type DetailRevisionResponse struct {
|
||||
type SystemInfoResponse struct {
|
||||
SystemInfo
|
||||
SystemVersion SystemVersion `json:"systemVersion"`
|
||||
StatisticInfo StatisticInfo `json:"statisticInfo,omitempty"`
|
||||
}
|
||||
|
||||
// SystemInfo system info
|
||||
type SystemInfo struct {
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
PlatformID string `json:"platformID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
InstallTime time.Time `json:"installTime,omitempty"`
|
||||
}
|
||||
|
||||
// StatisticInfo generated by cronJob running in backend
|
||||
type StatisticInfo struct {
|
||||
ClusterCount string `json:"clusterCount,omitempty"`
|
||||
AppCount string `json:"appCount,omitempty"`
|
||||
EnableAddonList map[string]string `json:"enableAddonList,omitempty"`
|
||||
ComponentDefinitionTopList []string `json:"componentDefinitionTopList,omitempty"`
|
||||
TraitDefinitionTopList []string `json:"traitDefinitionTopList,omitempty"`
|
||||
WorkflowDefinitionTopList []string `json:"workflowDefinitionTopList,omitempty"`
|
||||
PolicyDefinitionTopList []string `json:"policyDefinitionTopList,omitempty"`
|
||||
UpdateTime time.Time `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
// SystemInfoRequest request by update SystemInfo
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/collect"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/go-openapi/spec"
|
||||
@@ -60,6 +62,9 @@ type Config struct {
|
||||
|
||||
// AddonCacheTime is how long between two cache operations
|
||||
AddonCacheTime time.Duration
|
||||
|
||||
// DisableStatisticCronJob close the calculate system info cronJob
|
||||
DisableStatisticCronJob bool
|
||||
}
|
||||
|
||||
type leaderConfig struct {
|
||||
@@ -141,6 +146,10 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
|
||||
Callbacks: leaderelection.LeaderCallbacks{
|
||||
OnStartedLeading: func(ctx context.Context) {
|
||||
go velasync.Start(ctx, s.dataStore, restCfg, s.usecases)
|
||||
if !s.cfg.DisableStatisticCronJob {
|
||||
collect.StartCalculatingInfoCronJob(s.dataStore)
|
||||
}
|
||||
// this process would block the whole process, any other handler should start before this func
|
||||
s.runWorkflowRecordSync(ctx, s.cfg.LeaderConfig.Duration)
|
||||
},
|
||||
OnStoppedLeading: func() {
|
||||
|
||||
@@ -314,10 +314,10 @@ func (u *defaultAddonHandler) CreateAddonRegistry(ctx context.Context, req apis.
|
||||
func convertAddonRegistry(r pkgaddon.Registry) *apis.AddonRegistry {
|
||||
return &apis.AddonRegistry{
|
||||
Name: r.Name,
|
||||
Git: r.Git,
|
||||
Gitee: r.Gitee,
|
||||
Git: r.Git.SafeCopy(),
|
||||
Gitee: r.Gitee.SafeCopy(),
|
||||
OSS: r.OSS,
|
||||
Helm: r.Helm,
|
||||
Helm: r.Helm.SafeCopy(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
set "github.com/deckarep/golang-set"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -37,13 +39,13 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
)
|
||||
|
||||
const (
|
||||
definitionAlias = definition.UserPrefix + "alias.config.oam.dev"
|
||||
definitionType = definition.UserPrefix + "type.config.oam.dev"
|
||||
|
||||
velaCoreConfig = "velacore-config"
|
||||
configIsReady = "Ready"
|
||||
configIsNotReady = "Not ready"
|
||||
terraformProviderAlias = "Terraform Cloud Provider"
|
||||
@@ -81,7 +83,7 @@ type configUseCaseImpl struct {
|
||||
func (u *configUseCaseImpl) ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) {
|
||||
defs := &v1beta1.ComponentDefinitionList{}
|
||||
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig}); err != nil {
|
||||
client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -134,51 +136,59 @@ func (u *configUseCaseImpl) GetConfigType(ctx context.Context, configType string
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error {
|
||||
app := v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: req.Name,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
types.AnnotationConfigAlias: req.Alias,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: velaCoreConfig,
|
||||
types.LabelConfigType: req.ComponentType,
|
||||
types.LabelConfigProject: req.Project,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: req.Name,
|
||||
Type: req.ComponentType,
|
||||
Properties: &runtime.RawExtension{Raw: []byte(req.Properties)},
|
||||
},
|
||||
},
|
||||
},
|
||||
p := req.Properties
|
||||
// If the component is Terraform type, set the provider name same as the application name and the component name
|
||||
if strings.HasPrefix(req.ComponentType, types.TerraformComponentPrefix) {
|
||||
var properties map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(p), &properties); err != nil {
|
||||
return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType)
|
||||
}
|
||||
properties["name"] = req.Name
|
||||
tmp, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType)
|
||||
}
|
||||
p = string(tmp)
|
||||
}
|
||||
return u.kubeClient.Create(ctx, &app)
|
||||
ui := config.UIParam{
|
||||
Alias: req.Alias,
|
||||
Description: req.Description,
|
||||
Project: req.Project,
|
||||
}
|
||||
return config.CreateApplication(ctx, u.kubeClient, req.Name, req.ComponentType, p, ui)
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error) {
|
||||
switch configType {
|
||||
case types.TerraformProvider:
|
||||
defs := &v1beta1.ComponentDefinitionList{}
|
||||
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
|
||||
definition.UserPrefix + "type.config.oam.dev": types.TerraformProvider,
|
||||
}); err != nil {
|
||||
providers, err := config.ListTerraformProviders(ctx, u.kubeClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var configs []*apis.Config
|
||||
for _, d := range defs.Items {
|
||||
subConfigs, err := u.getConfigsByConfigType(ctx, d.Name)
|
||||
if err != nil {
|
||||
configs := make([]*apis.Config, len(providers))
|
||||
for i, p := range providers {
|
||||
var a v1beta1.Application
|
||||
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: p.Name}, &a); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
t := p.CreationTimestamp.Time
|
||||
configs[i] = &apis.Config{
|
||||
Name: p.Name,
|
||||
CreatedTime: &t,
|
||||
}
|
||||
if p.Status.State == terraformtypes.ProviderIsReady {
|
||||
configs[i].Status = configIsReady
|
||||
} else {
|
||||
configs[i].Status = configIsNotReady
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
configs = append(configs, subConfigs...)
|
||||
// If the application doesn't have any components, skip it as something wrong happened.
|
||||
if !strings.HasPrefix(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) {
|
||||
continue
|
||||
}
|
||||
configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject])
|
||||
}
|
||||
return configs, nil
|
||||
|
||||
@@ -193,7 +203,7 @@ func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configTy
|
||||
if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: velaCoreConfig,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: configType,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
@@ -201,18 +211,7 @@ func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configTy
|
||||
|
||||
configs := make([]*apis.Config, len(apps.Items))
|
||||
for i, a := range apps.Items {
|
||||
configs[i] = &apis.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: a.Labels[types.LabelConfigProject],
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
}
|
||||
switch a.Status.Phase {
|
||||
case common.ApplicationRunning:
|
||||
configs[i].Status = configIsReady
|
||||
default:
|
||||
configs[i].Status = configIsNotReady
|
||||
}
|
||||
configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject])
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
@@ -234,11 +233,11 @@ func (u *configUseCaseImpl) GetConfig(ctx context.Context, configType, name stri
|
||||
}
|
||||
|
||||
func (u *configUseCaseImpl) DeleteConfig(ctx context.Context, configType, name string) error {
|
||||
var a = &v1beta1.Application{}
|
||||
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
|
||||
return err
|
||||
var isTerraformProvider bool
|
||||
if strings.HasPrefix(configType, types.TerraformComponentPrefix) {
|
||||
isTerraformProvider = true
|
||||
}
|
||||
return u.kubeClient.Delete(ctx, a)
|
||||
return config.DeleteApplication(ctx, u.kubeClient, name, isTerraformProvider)
|
||||
}
|
||||
|
||||
// ApplicationDeployTarget is the struct of application deploy target
|
||||
@@ -254,7 +253,7 @@ func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, t
|
||||
var secrets v1.SecretList
|
||||
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: velaCoreConfig,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigSyncToMultiCluster: "true",
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -313,7 +312,7 @@ func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, t
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: velaCoreConfig,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigProject: project,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,8 +22,11 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/agiledragon/gomonkey/v2"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"gotest.tools/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -32,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
@@ -53,7 +57,7 @@ func TestListConfigTypes(t *testing.T) {
|
||||
Name: "def1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
|
||||
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
|
||||
definitionType: types.TerraformProvider,
|
||||
},
|
||||
},
|
||||
@@ -70,7 +74,7 @@ func TestListConfigTypes(t *testing.T) {
|
||||
definitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
|
||||
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -150,7 +154,7 @@ func TestGetConfigType(t *testing.T) {
|
||||
definitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
|
||||
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -236,6 +240,11 @@ func TestCreateConfig(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
properties, err := json.Marshal(map[string]interface{}{
|
||||
"name": "default",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
@@ -252,6 +261,18 @@ func TestCreateConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create terraform-alibaba config",
|
||||
args: args{
|
||||
h: h,
|
||||
req: apis.CreateConfigRequest{
|
||||
Name: "n1",
|
||||
ComponentType: "terraform-alibaba",
|
||||
Project: "p1",
|
||||
Properties: string(properties),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
@@ -268,37 +289,53 @@ func TestGetConfigs(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
def1 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
terraformapi.AddToScheme(s)
|
||||
createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022")
|
||||
|
||||
provider1 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
|
||||
definitionType: types.TerraformProvider,
|
||||
},
|
||||
Name: "provider1",
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsReady,
|
||||
},
|
||||
}
|
||||
def2 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
|
||||
provider2 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
definitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
|
||||
},
|
||||
Name: "provider2",
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsNotReady,
|
||||
},
|
||||
}
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
|
||||
|
||||
provider3 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider3",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider3",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigType: "terraform-alibaba",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRendering,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1).Build()
|
||||
|
||||
h := &configUseCaseImpl{kubeClient: k8sClient}
|
||||
|
||||
@@ -326,7 +363,25 @@ func TestGetConfigs(t *testing.T) {
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: nil,
|
||||
configs: []*apis.Config{
|
||||
{
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
},
|
||||
{
|
||||
Name: "provider2",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Not ready",
|
||||
},
|
||||
{
|
||||
Name: "provider3",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Not ready",
|
||||
ConfigType: "terraform-alibaba",
|
||||
ApplicationStatus: common.ApplicationRendering,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -502,7 +557,7 @@ func TestSyncConfigs(t *testing.T) {
|
||||
Name: "s1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigCatalog: velaCoreConfig,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigProject: "p1",
|
||||
types.LabelConfigSyncToMultiCluster: "true",
|
||||
},
|
||||
@@ -587,3 +642,129 @@ func TestSyncConfigs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteConfig(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
provider1 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p1",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
provider2 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p2",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
provider3 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p3",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-terraform-provider-p1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigType: "terraform-alibaba",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app2 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigType: "terraform-alibaba",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
normalApp := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a9",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1, app2, normalApp).Build()
|
||||
|
||||
h := &configUseCaseImpl{kubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
configType string
|
||||
name string
|
||||
h ConfigHandler
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "delete a legacy terraform provider",
|
||||
args: args{
|
||||
configType: "terraform-alibaba",
|
||||
name: "p1",
|
||||
h: h,
|
||||
},
|
||||
want: want{},
|
||||
},
|
||||
{
|
||||
name: "delete a terraform provider",
|
||||
args: args{
|
||||
configType: "terraform-alibaba",
|
||||
name: "p2",
|
||||
h: h,
|
||||
},
|
||||
want: want{},
|
||||
},
|
||||
{
|
||||
name: "delete a terraform provider, but its application not found",
|
||||
args: args{
|
||||
configType: "terraform-alibaba",
|
||||
name: "p3",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "could not be disabled because it was created by enabling a Terraform provider or was manually created",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete a normal config, but failed",
|
||||
args: args{
|
||||
configType: "config-image-registry",
|
||||
name: "a10",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "not found",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.args.h.DeleteConfig(ctx, tc.args.configType, tc.args.name)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
||||
@@ -31,7 +33,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types2 "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
@@ -133,41 +134,20 @@ func (d defaultHelmHandler) ListChartRepo(ctx context.Context, projectName strin
|
||||
var res []*v1.ChartRepoResponse
|
||||
var err error
|
||||
|
||||
if len(projectName) != 0 {
|
||||
projectSecrets := corev1.SecretList{}
|
||||
opts := []client.ListOption{
|
||||
client.MatchingLabels{oam.LabelConfigType: "config-helm-repository", types.LabelConfigProject: projectName},
|
||||
client.InNamespace(types.DefaultKubeVelaNS),
|
||||
}
|
||||
err = d.k8sClient.List(ctx, &projectSecrets, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range projectSecrets.Items {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
|
||||
}
|
||||
projectSecrets := corev1.SecretList{}
|
||||
opts := []client.ListOption{
|
||||
client.MatchingLabels{oam.LabelConfigType: "config-helm-repository"},
|
||||
client.InNamespace(types.DefaultKubeVelaNS),
|
||||
}
|
||||
|
||||
globalSecrets := corev1.SecretList{}
|
||||
selector := metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{oam.LabelConfigType: "config-helm-repository"},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{Key: types.LabelConfigProject, Operator: metav1.LabelSelectorOpDoesNotExist},
|
||||
},
|
||||
}
|
||||
|
||||
ls, _ := metav1.LabelSelectorAsSelector(&selector)
|
||||
err = d.k8sClient.List(ctx, &globalSecrets, &client.ListOptions{
|
||||
LabelSelector: ls,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
})
|
||||
err = d.k8sClient.List(ctx, &projectSecrets, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range globalSecrets.Items {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
|
||||
for _, item := range projectSecrets.Items {
|
||||
if config.ProjectMatched(item.DeepCopy(), projectName) {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
|
||||
}
|
||||
}
|
||||
|
||||
return &v1.ChartRepoResponseList{ChartRepoResponse: res}, nil
|
||||
|
||||
@@ -338,6 +338,7 @@ kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/project: ""
|
||||
name: global-helm-repo
|
||||
namespace: vela-system
|
||||
type: Opaque
|
||||
|
||||
@@ -487,7 +487,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
if err := p.k8sClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: velaCoreConfig,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -499,7 +499,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range providers.Items {
|
||||
if p.Labels[types.LabelConfigCatalog] == velaCoreConfig {
|
||||
if p.Labels[types.LabelConfigCatalog] == types.VelaCoreConfig {
|
||||
continue
|
||||
}
|
||||
t := p.CreationTimestamp.Time
|
||||
@@ -520,16 +520,10 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) ||
|
||||
!strings.Contains(a.Labels[types.LabelConfigType], "terraform-") {
|
||||
!strings.Contains(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: appProject,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
})
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
@@ -539,13 +533,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
if appProject != "" && appProject != projectName {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: appProject,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
})
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
case types.DexConnector, types.HelmRepository, types.ImageRegistry:
|
||||
@@ -556,13 +544,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
continue
|
||||
}
|
||||
if a.Labels[types.LabelConfigType] == t {
|
||||
configs = append(configs, &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: appProject,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: a.Status.Phase,
|
||||
})
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -579,13 +561,6 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config
|
||||
configs[i].ConfigTypeAlias = d.Annotations[definitionAlias]
|
||||
}
|
||||
}
|
||||
if c.ApplicationStatus != "" {
|
||||
if c.ApplicationStatus == common.ApplicationRunning {
|
||||
configs[i].Status = configIsReady
|
||||
} else {
|
||||
configs[i].Status = configIsNotReady
|
||||
}
|
||||
}
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
@@ -616,3 +591,25 @@ func ConvertProjectUserModel2Base(user *model.ProjectUser) *apisv1.ProjectUserBa
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func retrieveConfigFromApplication(a v1beta1.Application, project string) *apisv1.Config {
|
||||
var (
|
||||
applicationStatus = a.Status.Phase
|
||||
status string
|
||||
)
|
||||
if applicationStatus == common.ApplicationRunning {
|
||||
status = configIsReady
|
||||
} else {
|
||||
status = configIsNotReady
|
||||
}
|
||||
return &apisv1.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: project,
|
||||
CreatedTime: &(a.CreationTimestamp.Time),
|
||||
ApplicationStatus: applicationStatus,
|
||||
Status: status,
|
||||
Alias: a.Annotations[types.AnnotationConfigAlias],
|
||||
Description: a.Annotations[types.AnnotationConfigDescription],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ func TestProjectGetConfigs(t *testing.T) {
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velaCoreConfig,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "terraform-provider",
|
||||
"config.oam.dev/project": "p1",
|
||||
},
|
||||
@@ -308,7 +308,7 @@ func TestProjectGetConfigs(t *testing.T) {
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velaCoreConfig,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "terraform-provider",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
@@ -322,7 +322,7 @@ func TestProjectGetConfigs(t *testing.T) {
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velaCoreConfig,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "dex-connector",
|
||||
"config.oam.dev/project": "p3",
|
||||
},
|
||||
@@ -347,7 +347,7 @@ func TestProjectGetConfigs(t *testing.T) {
|
||||
Name: "provider2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velaCoreConfig,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -81,6 +82,7 @@ func (u systemInfoUsecaseImpl) Get(ctx context.Context) (*model.SystemInfo, erro
|
||||
info.InstallID = installID
|
||||
info.EnableCollection = true
|
||||
info.LoginType = model.LoginTypeLocal
|
||||
info.BaseModel = model.BaseModel{CreateTime: time.Now()}
|
||||
err = u.ds.Add(ctx, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,6 +102,16 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
|
||||
VelaVersion: version.VelaVersion,
|
||||
GitVersion: version.GitRevision,
|
||||
},
|
||||
StatisticInfo: v1.StatisticInfo{
|
||||
AppCount: info.StatisticInfo.AppCount,
|
||||
ClusterCount: info.StatisticInfo.ClusterCount,
|
||||
EnableAddonList: info.StatisticInfo.EnabledAddon,
|
||||
ComponentDefinitionTopList: info.StatisticInfo.TopKCompDef,
|
||||
TraitDefinitionTopList: info.StatisticInfo.TopKTraitDef,
|
||||
WorkflowDefinitionTopList: info.StatisticInfo.TopKWorkflowStepDef,
|
||||
PolicyDefinitionTopList: info.StatisticInfo.TopKPolicyDef,
|
||||
UpdateTime: info.StatisticInfo.UpdateTime,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +126,9 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
|
||||
LoginType: sysInfo.LoginType,
|
||||
BaseModel: model.BaseModel{
|
||||
CreateTime: info.CreateTime,
|
||||
UpdateTime: time.Now(),
|
||||
},
|
||||
StatisticInfo: info.StatisticInfo,
|
||||
}
|
||||
|
||||
if sysInfo.LoginType == model.LoginTypeDex {
|
||||
@@ -135,9 +149,11 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
|
||||
}
|
||||
return &v1.SystemInfoResponse{
|
||||
SystemInfo: v1.SystemInfo{
|
||||
InstallID: modifiedInfo.InstallID,
|
||||
PlatformID: modifiedInfo.InstallID,
|
||||
EnableCollection: modifiedInfo.EnableCollection,
|
||||
LoginType: modifiedInfo.LoginType,
|
||||
// always use the initial createTime as system's installTime
|
||||
InstallTime: info.CreateTime,
|
||||
},
|
||||
SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision},
|
||||
}, nil
|
||||
@@ -155,9 +171,10 @@ func (u systemInfoUsecaseImpl) Init(ctx context.Context) error {
|
||||
|
||||
func convertInfoToBase(info *model.SystemInfo) v1.SystemInfo {
|
||||
return v1.SystemInfo{
|
||||
InstallID: info.InstallID,
|
||||
PlatformID: info.InstallID,
|
||||
EnableCollection: info.EnableCollection,
|
||||
LoginType: info.LoginType,
|
||||
InstallTime: info.CreateTime,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ func (s *enabledAddonWebService) GetWebService() *restful.WebService {
|
||||
Filter(s.rbacUsecase.CheckPerm("addon", "list")).
|
||||
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
|
||||
Returns(200, "OK", apis.ListAddonResponse{}).
|
||||
Returns(200, "OK", apis.ListEnabledAddonResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListAddonResponse{}))
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"cuelang.org/go/cue/format"
|
||||
json2cue "cuelang.org/go/encoding/json"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -63,7 +63,9 @@ const (
|
||||
// WriteConnectionSecretToRefKey is used to create a secret for cloud resource connection
|
||||
WriteConnectionSecretToRefKey = "writeConnectionSecretToRef"
|
||||
// RegionKey is the region of a Cloud Provider
|
||||
RegionKey = "region"
|
||||
// It's used to override the region of a Cloud Provider
|
||||
// Refer to https://github.com/oam-dev/terraform-controller/blob/master/api/v1beta2/configuration_types.go#L66 for details
|
||||
RegionKey = "customRegion"
|
||||
// ProviderRefKey is the reference of a Provider
|
||||
ProviderRefKey = "providerRef"
|
||||
)
|
||||
@@ -666,7 +668,7 @@ func generateTerraformConfigurationWorkload(wl *Workload, ns string) (*unstructu
|
||||
}
|
||||
|
||||
configuration := terraformapi.Configuration{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta1", Kind: "Configuration"},
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: wl.Name,
|
||||
Namespace: ns,
|
||||
@@ -679,8 +681,6 @@ func generateTerraformConfigurationWorkload(wl *Workload, ns string) (*unstructu
|
||||
switch wl.FullTemplate.Terraform.Type {
|
||||
case "hcl":
|
||||
configuration.Spec.HCL = wl.FullTemplate.Terraform.Configuration
|
||||
case "json":
|
||||
configuration.Spec.JSON = wl.FullTemplate.Terraform.Configuration
|
||||
case "remote":
|
||||
configuration.Spec.Remote = wl.FullTemplate.Terraform.Configuration
|
||||
configuration.Spec.Path = wl.FullTemplate.Terraform.Path
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -584,10 +584,6 @@ variable "password" {
|
||||
raw.Raw = data
|
||||
|
||||
workload := terraformapi.Configuration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "terraform.core.oam.dev/v1beta1",
|
||||
Kind: "Configuration",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/appRevision": "v1",
|
||||
@@ -902,7 +898,6 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
writeConnectionSecretToRef *terraformtypes.SecretReference
|
||||
json string
|
||||
hcl string
|
||||
remote string
|
||||
params map[string]interface{}
|
||||
@@ -917,16 +912,6 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"json workload with secret": {
|
||||
args: args{
|
||||
|
||||
json: "abc",
|
||||
params: map[string]interface{}{"acl": "private",
|
||||
"writeConnectionSecretToRef": map[string]interface{}{"name": "oss", "namespace": ""}},
|
||||
writeConnectionSecretToRef: &terraformtypes.SecretReference{Name: "oss", Namespace: "default"},
|
||||
},
|
||||
want: want{err: nil}},
|
||||
|
||||
"valid hcl workload": {
|
||||
args: args{
|
||||
hcl: "abc",
|
||||
@@ -999,19 +984,6 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
}
|
||||
configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
|
||||
}
|
||||
if tc.args.json != "" {
|
||||
template = &Template{
|
||||
Terraform: &common.Terraform{
|
||||
Configuration: tc.args.json,
|
||||
Type: "json",
|
||||
},
|
||||
}
|
||||
configSpec = terraformapi.ConfigurationSpec{
|
||||
JSON: tc.args.json,
|
||||
Variable: raw,
|
||||
}
|
||||
configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
|
||||
}
|
||||
if tc.args.remote != "" {
|
||||
template = &Template{
|
||||
Terraform: &common.Terraform{
|
||||
@@ -1025,7 +997,7 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
}
|
||||
configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
|
||||
}
|
||||
if tc.args.hcl == "" && tc.args.json == "" && tc.args.remote == "" {
|
||||
if tc.args.hcl == "" && tc.args.remote == "" {
|
||||
template = &Template{
|
||||
Terraform: &common.Terraform{},
|
||||
}
|
||||
@@ -1061,7 +1033,7 @@ func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
|
||||
|
||||
if err == nil {
|
||||
tfConfiguration := terraformapi.Configuration{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta1", Kind: "Configuration"},
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
|
||||
Spec: configSpec,
|
||||
}
|
||||
|
||||
@@ -288,6 +288,30 @@ func (p *Parser) GenerateAppFileFromRevision(appRev *v1beta1.ApplicationRevision
|
||||
for k, v := range appRev.Spec.WorkflowStepDefinitions {
|
||||
appfile.RelatedWorkflowStepDefinitions[k] = v.DeepCopy()
|
||||
}
|
||||
|
||||
// add compatible code for upgrading to v1.3 as the workflow steps were not recorded before v1.2
|
||||
if len(appfile.RelatedWorkflowStepDefinitions) == 0 && len(appfile.WorkflowSteps) > 0 {
|
||||
ctx := context.Background()
|
||||
for _, workflowStep := range appfile.WorkflowSteps {
|
||||
if wftypes.IsBuiltinWorkflowStepType(workflowStep.Type) {
|
||||
continue
|
||||
}
|
||||
if _, found := appfile.RelatedWorkflowStepDefinitions[workflowStep.Type]; found {
|
||||
continue
|
||||
}
|
||||
def := &v1beta1.WorkflowStepDefinition{}
|
||||
if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStep.Type); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get workflow step definition %s", workflowStep.Type)
|
||||
}
|
||||
appfile.RelatedWorkflowStepDefinitions[workflowStep.Type] = def
|
||||
}
|
||||
|
||||
appRev.Spec.WorkflowStepDefinitions = make(map[string]v1beta1.WorkflowStepDefinition)
|
||||
for name, def := range appfile.RelatedWorkflowStepDefinitions {
|
||||
appRev.Spec.WorkflowStepDefinitions[name] = *def
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range appRev.Spec.ScopeDefinitions {
|
||||
appfile.RelatedScopeDefinitions[k] = v.DeepCopy()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
@@ -506,3 +508,202 @@ var _ = Describe("Test appFile parser", func() {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Test application parser", func() {
|
||||
var app v1beta1.Application
|
||||
var apprev v1beta1.ApplicationRevision
|
||||
var wsd v1beta1.WorkflowStepDefinition
|
||||
var expectedExceptAppfile *Appfile
|
||||
var mockClient test.MockClient
|
||||
|
||||
BeforeEach(func() {
|
||||
// prepare WorkflowStepDefinition
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/wsd.yaml", &wsd)).Should(BeNil())
|
||||
|
||||
// prepare verify data
|
||||
expectedExceptAppfile = &Appfile{
|
||||
Name: "backport-1-2-test-demo",
|
||||
Workloads: []*Workload{
|
||||
{
|
||||
Name: "backport-1-2-test-demo",
|
||||
Type: "webservice",
|
||||
Params: map[string]interface{}{
|
||||
"image": "nginx",
|
||||
},
|
||||
FullTemplate: &Template{
|
||||
TemplateStr: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
|
||||
cmd?: [...string]
|
||||
}`,
|
||||
},
|
||||
Traits: []*Trait{
|
||||
{
|
||||
Name: "scaler",
|
||||
Params: map[string]interface{}{
|
||||
"replicas": float64(1),
|
||||
},
|
||||
Template: `
|
||||
parameter: {
|
||||
// +usage=Specify the number of workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
// +patchStrategy=retainKeys
|
||||
patch: spec: replicas: parameter.replicas
|
||||
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
WorkflowSteps: []v1beta1.WorkflowStep{
|
||||
{
|
||||
Name: "apply",
|
||||
Type: "apply-application",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create mock client
|
||||
mockClient = test.MockClient{
|
||||
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
if strings.Contains(key.Name, "unknown") {
|
||||
return &errors2.StatusError{ErrStatus: metav1.Status{Reason: "NotFound", Message: "not found"}}
|
||||
}
|
||||
switch o := obj.(type) {
|
||||
case *v1beta1.ComponentDefinition:
|
||||
wd, err := util.UnMarshalStringToComponentDefinition(componenetDefinition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*o = *wd
|
||||
case *v1beta1.TraitDefinition:
|
||||
td, err := util.UnMarshalStringToTraitDefinition(traitDefinition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*o = *td
|
||||
case *v1beta1.WorkflowStepDefinition:
|
||||
*o = wsd
|
||||
case *v1beta1.ApplicationRevision:
|
||||
*o = apprev
|
||||
default:
|
||||
// skip
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
When("with apply-application workflowStep", func() {
|
||||
BeforeEach(func() {
|
||||
// prepare application
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
|
||||
// prepare application revision
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev1.yaml", &apprev)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test we can parse an application revision to an appFile 1", func() {
|
||||
|
||||
appfile, err := NewApplicationParser(&mockClient, dm, pd).GenerateAppFile(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(equal(expectedExceptAppfile, appfile)).Should(BeTrue())
|
||||
Expect(len(appfile.WorkflowSteps) > 0 &&
|
||||
len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions)).Should(BeTrue())
|
||||
|
||||
Expect(len(appfile.WorkflowSteps) > 0 && func() bool {
|
||||
this := appfile.RelatedWorkflowStepDefinitions
|
||||
that := appfile.AppRevision.Spec.WorkflowStepDefinitions
|
||||
for i, w := range this {
|
||||
thatW := that[i]
|
||||
if !reflect.DeepEqual(*w, thatW) {
|
||||
fmt.Printf("appfile wsd:%s apprev wsd%s", (*w).Name, thatW.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
When("with apply-application and apply-component build-in workflowStep", func() {
|
||||
BeforeEach(func() {
|
||||
// prepare application
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
|
||||
// prepare application revision
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev2.yaml", &apprev)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test we can parse an application revision to an appFile 2", func() {
|
||||
|
||||
appfile, err := NewApplicationParser(&mockClient, dm, pd).GenerateAppFile(context.TODO(), &app)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(equal(expectedExceptAppfile, appfile)).Should(BeTrue())
|
||||
Expect(len(appfile.WorkflowSteps) > 0 &&
|
||||
len(appfile.RelatedWorkflowStepDefinitions) == len(appfile.AppRevision.Spec.WorkflowStepDefinitions)).Should(BeTrue())
|
||||
|
||||
Expect(len(appfile.WorkflowSteps) > 0 && func() bool {
|
||||
this := appfile.RelatedWorkflowStepDefinitions
|
||||
that := appfile.AppRevision.Spec.WorkflowStepDefinitions
|
||||
for i, w := range this {
|
||||
thatW := that[i]
|
||||
if !reflect.DeepEqual(*w, thatW) {
|
||||
fmt.Printf("appfile wsd:%s apprev wsd%s", (*w).Name, thatW.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
When("with unknown workflowStep", func() {
|
||||
BeforeEach(func() {
|
||||
// prepare application
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/app.yaml", &app)).Should(BeNil())
|
||||
// prepare application revision
|
||||
Expect(common2.ReadYamlToObject("testdata/backport-1-2/apprev3.yaml", &apprev)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test we can parse an application revision to an appFile 3", func() {
|
||||
|
||||
_, err := NewApplicationParser(&mockClient, dm, pd).GenerateAppFile(context.TODO(), &app)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error() == "failed to get workflow step definition apply-application-unknown: not found").Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
26
pkg/appfile/testdata/backport-1-2/app.yaml
vendored
Normal file
26
pkg/appfile/testdata/backport-1-2/app.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
traits:
|
||||
- properties:
|
||||
replicas: 1
|
||||
type: scaler
|
||||
type: webservice
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply
|
||||
type: apply-application
|
||||
status:
|
||||
latestRevision:
|
||||
name: backport-1-2-test-demo-v1
|
||||
revision: 1
|
||||
revisionHash: 38ddf4e721073703
|
||||
271
pkg/appfile/testdata/backport-1-2/apprev1.yaml
vendored
Normal file
271
pkg/appfile/testdata/backport-1-2/apprev1.yaml
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ApplicationRevision
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo-v1
|
||||
namespace: default
|
||||
spec:
|
||||
application:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
traits:
|
||||
- properties:
|
||||
replicas: 1
|
||||
type: scaler
|
||||
type: webservice
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply
|
||||
type: apply-application
|
||||
status: {}
|
||||
componentDefinitions:
|
||||
webservice:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes long-running, scalable, containerized
|
||||
services that have a stable network endpoint to receive external network
|
||||
traffic from customers.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: webservice
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tname:
|
||||
\ v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor
|
||||
v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret:
|
||||
*[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\temptyDir:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.emptyDir {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesArray:
|
||||
{\n\tpvc: *[\n\t\tfor v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tpersistentVolumeClaim: claimName: v.claimName\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tconfigMap: *[\n\t\t\tfor v in parameter.volumeMounts.configMap
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap: {\n\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif v.items
|
||||
!= _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName:
|
||||
\ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n}\noutput: {\n\tapiVersion: \"apps/v1\"\n\tkind: \"Deployment\"\n\tspec:
|
||||
{\n\t\tselector: matchLabels: \"app.oam.dev/component\": context.name\n\n\t\ttemplate:
|
||||
{\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif parameter.labels
|
||||
!= _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif parameter.addRevisionLabel
|
||||
{\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/component\":
|
||||
context.name\n\t\t\t\t}\n\t\t\t\tif parameter.annotations != _|_ {\n\t\t\t\t\tannotations:
|
||||
parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec: {\n\t\t\t\tcontainers:
|
||||
[{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage: parameter.image\n\t\t\t\t\tif
|
||||
parameter[\"port\"] != _|_ && parameter[\"ports\"] == _|_ {\n\t\t\t\t\t\tports:
|
||||
[{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports
|
||||
{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol:
|
||||
\ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
\"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy:
|
||||
parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"cmd\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\t\tvolumeMounts: [ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc
|
||||
+ mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir
|
||||
+ mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"livenessProbe\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe:
|
||||
parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif parameter[\"hostAliases\"]
|
||||
!= _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases: parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"imagePullSecrets\"] != _|_ {\n\t\t\t\t\timagePullSecrets:
|
||||
[ for v in parameter.imagePullSecrets {\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\tvolumes: [ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\tif v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim:
|
||||
claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif v.type ==
|
||||
\"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: volumesArray.pvc
|
||||
+ volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir
|
||||
+ volumesArray.hostPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts:
|
||||
[\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort:
|
||||
v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name
|
||||
== _|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs:
|
||||
{\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion:
|
||||
\"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec:
|
||||
{\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports:
|
||||
exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter:
|
||||
{\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]:
|
||||
string\n\n\t// +usage=Specify the annotations in the workload\n\tannotations?:
|
||||
[string]: string\n\n\t// +usage=Which image would you like to use for
|
||||
your service\n\t// +short=i\n\timage: string\n\n\t// +usage=Specify
|
||||
image pull policy for your service\n\timagePullPolicy?: \"Always\" |
|
||||
\"Never\" | \"IfNotPresent\"\n\n\t// +usage=Specify image pull secrets
|
||||
for your service\n\timagePullSecrets?: [...string]\n\n\t// +ignore\n\t//
|
||||
+usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?:
|
||||
int\n\n\t// +usage=Which ports do you want customer traffic sent to,
|
||||
defaults to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose
|
||||
on the pod's IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?:
|
||||
string\n\t\t// +usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol:
|
||||
*\"TCP\" | \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should
|
||||
be exposed\n\t\texpose: *false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify
|
||||
what kind of Service you want. options: \"ClusterIP\", \"NodePort\",
|
||||
\"LoadBalancer\", \"ExternalName\"\n\texposeType: *\"ClusterIP\" | \"NodePort\"
|
||||
| \"LoadBalancer\" | \"ExternalName\"\n\n\t// +ignore\n\t// +usage=If
|
||||
addRevisionLabel is true, the revision label will be added to the underlying
|
||||
pods\n\taddRevisionLabel: *false | bool\n\n\t// +usage=Commands to run
|
||||
in the container\n\tcmd?: [...string]\n\n\t// +usage=Define arguments
|
||||
by using environment variables\n\tenv?: [...{\n\t\t// +usage=Environment
|
||||
variable name\n\t\tname: string\n\t\t// +usage=The value of the environment
|
||||
variable\n\t\tvalue?: string\n\t\t// +usage=Specifies a source the value
|
||||
of this var should come from\n\t\tvalueFrom?: {\n\t\t\t// +usage=Selects
|
||||
a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?: {\n\t\t\t\t//
|
||||
+usage=The name of the secret in the pod's namespace to select from\n\t\t\t\tname:
|
||||
string\n\t\t\t\t// +usage=The key of the secret to select from. Must
|
||||
be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t// +usage=Selects
|
||||
a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the config map in the pod's namespace
|
||||
to select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the
|
||||
config map to select from. Must be a valid secret key\n\t\t\t\tkey:
|
||||
string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number of CPU units for
|
||||
the service, like \n\tcpu?: string\n\n\t//
|
||||
+usage=Specifies the attributes of the memory resource required for
|
||||
the container.\n\tmemory?: string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount
|
||||
PVC type volume\n\t\tpvc?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\t// +usage=The name of the PVC\n\t\t\tclaimName: string\n\t\t}]\n\t\t//
|
||||
+usage=Mount ConfigMap type volume\n\t\tconfigMap?: [...{\n\t\t\tname:
|
||||
\ string\n\t\t\tmountPath: string\n\t\t\tdefaultMode: *420 |
|
||||
int\n\t\t\tcmName: string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath:
|
||||
string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount
|
||||
Secret type volume\n\t\tsecret?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount EmptyDir type volume\n\t\temptyDir?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tmedium:
|
||||
\ *\"\" | \"Memory\"\n\t\t}]\n\t\t// +usage=Mount HostPath type volume\n\t\thostPath?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tpath:
|
||||
\ string\n\t\t}]\n\t}\n\n\t// +usage=Deprecated field, use volumeMounts
|
||||
instead.\n\tvolumes?: [...{\n\t\tname: string\n\t\tmountPath: string\n\t\t//
|
||||
+usage=Specify volume type, options: \"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype:
|
||||
\"pvc\" | \"configMap\" | \"secret\" | \"emptyDir\"\n\t\tif type ==
|
||||
\"pvc\" {\n\t\t\tclaimName: string\n\t\t}\n\t\tif type == \"configMap\"
|
||||
{\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}\n\t\tif type == \"secret\" {\n\t\t\tdefaultMode:
|
||||
*420 | int\n\t\t\tsecretName: string\n\t\t\titems?: [...{\n\t\t\t\tkey:
|
||||
\ string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"emptyDir\" {\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t//
|
||||
+usage=Instructions for assessing whether the container is alive.\n\tlivenessProbe?:
|
||||
#HealthProbe\n\n\t// +usage=Instructions for assessing whether the container
|
||||
is in a suitable state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t//
|
||||
+usage=Specify the hostAliases to add\n\thostAliases?: [...{\n\t\tip:
|
||||
string\n\t\thostnames: [...string]\n\t}]\n}\n#HealthProbe: {\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing a command.
|
||||
Either this attribute or the httpGet attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
httpGet attribute and the tcpSocket attribute.\n\texec?: {\n\t\t// +usage=A
|
||||
command to be executed inside the container to assess its health. Each
|
||||
space delimited token of the command is a separate array element. Commands
|
||||
exiting 0 are considered to be successful probes, whilst all other exit
|
||||
codes are considered failures.\n\t\tcommand: [...string]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket
|
||||
attribute MUST be specified. This attribute is mutually exclusive with
|
||||
both the exec attribute and the tcpSocket attribute.\n\thttpGet?: {\n\t\t//
|
||||
+usage=The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.\n\t\tpath: string\n\t\t// +usage=The TCP socket
|
||||
within the container to which the HTTP GET request should be directed.\n\t\tport:
|
||||
int\n\t\thttpHeaders?: [...{\n\t\t\tname: string\n\t\t\tvalue: string\n\t\t}]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by probing a TCP
|
||||
socket. Either this attribute or the exec attribute or the httpGet attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
exec attribute and the httpGet attribute.\n\ttcpSocket?: {\n\t\t// +usage=The
|
||||
TCP socket within the container that should be probed to assess container
|
||||
health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after the
|
||||
container is started before the first probe is initiated.\n\tinitialDelaySeconds:
|
||||
*0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds:
|
||||
*10 | int\n\n\t// +usage=Number of seconds after which the probe times
|
||||
out.\n\ttimeoutSeconds: *1 | int\n\n\t// +usage=Minimum consecutive
|
||||
successes for the probe to be considered successful after having failed.\n\tsuccessThreshold:
|
||||
*1 | int\n\n\t// +usage=Number of consecutive failures required to determine
|
||||
the container is not alive (liveness probe) or not ready (readiness
|
||||
probe).\n\tfailureThreshold: *3 | int\n}\n"
|
||||
status:
|
||||
customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage:
|
||||
\"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\""
|
||||
healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas:
|
||||
\ *0 | int\n\treplicas: *0 | int\n\tobservedGeneration:
|
||||
*0 | int\n} & {\n\tif context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas:
|
||||
context.output.status.updatedReplicas\n\t}\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif
|
||||
context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif
|
||||
context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration:
|
||||
context.output.status.observedGeneration\n\t}\n}\nisHealth: (context.output.spec.replicas
|
||||
== ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas)
|
||||
&& (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration
|
||||
== context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)"
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
type: deployments.apps
|
||||
status: {}
|
||||
traitDefinitions:
|
||||
scaler:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Manually scale K8s pod for your workload
|
||||
which follows the pod spec in path 'spec.template'.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: scaler
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
definitionRef:
|
||||
name: ""
|
||||
schematic:
|
||||
cue:
|
||||
template: "parameter: {\n\t// +usage=Specify the number of workload\n\treplicas:
|
||||
*1 | int\n}\n// +patchStrategy=retainKeys\npatch: spec: replicas: parameter.replicas\n"
|
||||
status: {}
|
||||
status: {}
|
||||
277
pkg/appfile/testdata/backport-1-2/apprev2.yaml
vendored
Normal file
277
pkg/appfile/testdata/backport-1-2/apprev2.yaml
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ApplicationRevision
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo-v1
|
||||
namespace: default
|
||||
spec:
|
||||
application:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
traits:
|
||||
- properties:
|
||||
replicas: 1
|
||||
type: scaler
|
||||
type: webservice
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply-component
|
||||
type: apply-component
|
||||
properties:
|
||||
name: backport-1-2-test-demo
|
||||
- name: apply1
|
||||
type: apply-application
|
||||
- name: apply2
|
||||
type: apply-application
|
||||
status: {}
|
||||
componentDefinitions:
|
||||
webservice:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes long-running, scalable, containerized
|
||||
services that have a stable network endpoint to receive external network
|
||||
traffic from customers.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: webservice
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tname:
|
||||
\ v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor
|
||||
v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret:
|
||||
*[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\temptyDir:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.emptyDir {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesArray:
|
||||
{\n\tpvc: *[\n\t\tfor v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tpersistentVolumeClaim: claimName: v.claimName\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tconfigMap: *[\n\t\t\tfor v in parameter.volumeMounts.configMap
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap: {\n\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif v.items
|
||||
!= _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName:
|
||||
\ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n}\noutput: {\n\tapiVersion: \"apps/v1\"\n\tkind: \"Deployment\"\n\tspec:
|
||||
{\n\t\tselector: matchLabels: \"app.oam.dev/component\": context.name\n\n\t\ttemplate:
|
||||
{\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif parameter.labels
|
||||
!= _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif parameter.addRevisionLabel
|
||||
{\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/component\":
|
||||
context.name\n\t\t\t\t}\n\t\t\t\tif parameter.annotations != _|_ {\n\t\t\t\t\tannotations:
|
||||
parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec: {\n\t\t\t\tcontainers:
|
||||
[{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage: parameter.image\n\t\t\t\t\tif
|
||||
parameter[\"port\"] != _|_ && parameter[\"ports\"] == _|_ {\n\t\t\t\t\t\tports:
|
||||
[{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports
|
||||
{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol:
|
||||
\ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
\"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy:
|
||||
parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"cmd\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\t\tvolumeMounts: [ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc
|
||||
+ mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir
|
||||
+ mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"livenessProbe\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe:
|
||||
parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif parameter[\"hostAliases\"]
|
||||
!= _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases: parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"imagePullSecrets\"] != _|_ {\n\t\t\t\t\timagePullSecrets:
|
||||
[ for v in parameter.imagePullSecrets {\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\tvolumes: [ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\tif v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim:
|
||||
claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif v.type ==
|
||||
\"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: volumesArray.pvc
|
||||
+ volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir
|
||||
+ volumesArray.hostPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts:
|
||||
[\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort:
|
||||
v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name
|
||||
== _|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs:
|
||||
{\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion:
|
||||
\"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec:
|
||||
{\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports:
|
||||
exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter:
|
||||
{\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]:
|
||||
string\n\n\t// +usage=Specify the annotations in the workload\n\tannotations?:
|
||||
[string]: string\n\n\t// +usage=Which image would you like to use for
|
||||
your service\n\t// +short=i\n\timage: string\n\n\t// +usage=Specify
|
||||
image pull policy for your service\n\timagePullPolicy?: \"Always\" |
|
||||
\"Never\" | \"IfNotPresent\"\n\n\t// +usage=Specify image pull secrets
|
||||
for your service\n\timagePullSecrets?: [...string]\n\n\t// +ignore\n\t//
|
||||
+usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?:
|
||||
int\n\n\t// +usage=Which ports do you want customer traffic sent to,
|
||||
defaults to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose
|
||||
on the pod's IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?:
|
||||
string\n\t\t// +usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol:
|
||||
*\"TCP\" | \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should
|
||||
be exposed\n\t\texpose: *false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify
|
||||
what kind of Service you want. options: \"ClusterIP\", \"NodePort\",
|
||||
\"LoadBalancer\", \"ExternalName\"\n\texposeType: *\"ClusterIP\" | \"NodePort\"
|
||||
| \"LoadBalancer\" | \"ExternalName\"\n\n\t// +ignore\n\t// +usage=If
|
||||
addRevisionLabel is true, the revision label will be added to the underlying
|
||||
pods\n\taddRevisionLabel: *false | bool\n\n\t// +usage=Commands to run
|
||||
in the container\n\tcmd?: [...string]\n\n\t// +usage=Define arguments
|
||||
by using environment variables\n\tenv?: [...{\n\t\t// +usage=Environment
|
||||
variable name\n\t\tname: string\n\t\t// +usage=The value of the environment
|
||||
variable\n\t\tvalue?: string\n\t\t// +usage=Specifies a source the value
|
||||
of this var should come from\n\t\tvalueFrom?: {\n\t\t\t// +usage=Selects
|
||||
a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?: {\n\t\t\t\t//
|
||||
+usage=The name of the secret in the pod's namespace to select from\n\t\t\t\tname:
|
||||
string\n\t\t\t\t// +usage=The key of the secret to select from. Must
|
||||
be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t// +usage=Selects
|
||||
a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the config map in the pod's namespace
|
||||
to select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the
|
||||
config map to select from. Must be a valid secret key\n\t\t\t\tkey:
|
||||
string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number of CPU units for
|
||||
the service, like \n\tcpu?: string\n\n\t//
|
||||
+usage=Specifies the attributes of the memory resource required for
|
||||
the container.\n\tmemory?: string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount
|
||||
PVC type volume\n\t\tpvc?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\t// +usage=The name of the PVC\n\t\t\tclaimName: string\n\t\t}]\n\t\t//
|
||||
+usage=Mount ConfigMap type volume\n\t\tconfigMap?: [...{\n\t\t\tname:
|
||||
\ string\n\t\t\tmountPath: string\n\t\t\tdefaultMode: *420 |
|
||||
int\n\t\t\tcmName: string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath:
|
||||
string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount
|
||||
Secret type volume\n\t\tsecret?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount EmptyDir type volume\n\t\temptyDir?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tmedium:
|
||||
\ *\"\" | \"Memory\"\n\t\t}]\n\t\t// +usage=Mount HostPath type volume\n\t\thostPath?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tpath:
|
||||
\ string\n\t\t}]\n\t}\n\n\t// +usage=Deprecated field, use volumeMounts
|
||||
instead.\n\tvolumes?: [...{\n\t\tname: string\n\t\tmountPath: string\n\t\t//
|
||||
+usage=Specify volume type, options: \"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype:
|
||||
\"pvc\" | \"configMap\" | \"secret\" | \"emptyDir\"\n\t\tif type ==
|
||||
\"pvc\" {\n\t\t\tclaimName: string\n\t\t}\n\t\tif type == \"configMap\"
|
||||
{\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}\n\t\tif type == \"secret\" {\n\t\t\tdefaultMode:
|
||||
*420 | int\n\t\t\tsecretName: string\n\t\t\titems?: [...{\n\t\t\t\tkey:
|
||||
\ string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"emptyDir\" {\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t//
|
||||
+usage=Instructions for assessing whether the container is alive.\n\tlivenessProbe?:
|
||||
#HealthProbe\n\n\t// +usage=Instructions for assessing whether the container
|
||||
is in a suitable state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t//
|
||||
+usage=Specify the hostAliases to add\n\thostAliases?: [...{\n\t\tip:
|
||||
string\n\t\thostnames: [...string]\n\t}]\n}\n#HealthProbe: {\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing a command.
|
||||
Either this attribute or the httpGet attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
httpGet attribute and the tcpSocket attribute.\n\texec?: {\n\t\t// +usage=A
|
||||
command to be executed inside the container to assess its health. Each
|
||||
space delimited token of the command is a separate array element. Commands
|
||||
exiting 0 are considered to be successful probes, whilst all other exit
|
||||
codes are considered failures.\n\t\tcommand: [...string]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket
|
||||
attribute MUST be specified. This attribute is mutually exclusive with
|
||||
both the exec attribute and the tcpSocket attribute.\n\thttpGet?: {\n\t\t//
|
||||
+usage=The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.\n\t\tpath: string\n\t\t// +usage=The TCP socket
|
||||
within the container to which the HTTP GET request should be directed.\n\t\tport:
|
||||
int\n\t\thttpHeaders?: [...{\n\t\t\tname: string\n\t\t\tvalue: string\n\t\t}]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by probing a TCP
|
||||
socket. Either this attribute or the exec attribute or the httpGet attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
exec attribute and the httpGet attribute.\n\ttcpSocket?: {\n\t\t// +usage=The
|
||||
TCP socket within the container that should be probed to assess container
|
||||
health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after the
|
||||
container is started before the first probe is initiated.\n\tinitialDelaySeconds:
|
||||
*0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds:
|
||||
*10 | int\n\n\t// +usage=Number of seconds after which the probe times
|
||||
out.\n\ttimeoutSeconds: *1 | int\n\n\t// +usage=Minimum consecutive
|
||||
successes for the probe to be considered successful after having failed.\n\tsuccessThreshold:
|
||||
*1 | int\n\n\t// +usage=Number of consecutive failures required to determine
|
||||
the container is not alive (liveness probe) or not ready (readiness
|
||||
probe).\n\tfailureThreshold: *3 | int\n}\n"
|
||||
status:
|
||||
customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage:
|
||||
\"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\""
|
||||
healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas:
|
||||
\ *0 | int\n\treplicas: *0 | int\n\tobservedGeneration:
|
||||
*0 | int\n} & {\n\tif context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas:
|
||||
context.output.status.updatedReplicas\n\t}\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif
|
||||
context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif
|
||||
context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration:
|
||||
context.output.status.observedGeneration\n\t}\n}\nisHealth: (context.output.spec.replicas
|
||||
== ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas)
|
||||
&& (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration
|
||||
== context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)"
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
type: deployments.apps
|
||||
status: {}
|
||||
traitDefinitions:
|
||||
scaler:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Manually scale K8s pod for your workload
|
||||
which follows the pod spec in path 'spec.template'.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: scaler
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
definitionRef:
|
||||
name: ""
|
||||
schematic:
|
||||
cue:
|
||||
template: "parameter: {\n\t// +usage=Specify the number of workload\n\treplicas:
|
||||
*1 | int\n}\n// +patchStrategy=retainKeys\npatch: spec: replicas: parameter.replicas\n"
|
||||
status: {}
|
||||
status: {}
|
||||
271
pkg/appfile/testdata/backport-1-2/apprev3.yaml
vendored
Normal file
271
pkg/appfile/testdata/backport-1-2/apprev3.yaml
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ApplicationRevision
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo-v1
|
||||
namespace: default
|
||||
spec:
|
||||
application:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: workflow-default-123456
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
traits:
|
||||
- properties:
|
||||
replicas: 1
|
||||
type: scaler
|
||||
type: webservice
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply
|
||||
type: apply-application-unknown
|
||||
status: {}
|
||||
componentDefinitions:
|
||||
webservice:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes long-running, scalable, containerized
|
||||
services that have a stable network endpoint to receive external network
|
||||
traffic from customers.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: webservice
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tname:
|
||||
\ v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor
|
||||
v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret:
|
||||
*[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\temptyDir:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.emptyDir {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesArray:
|
||||
{\n\tpvc: *[\n\t\tfor v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tpersistentVolumeClaim: claimName: v.claimName\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tconfigMap: *[\n\t\t\tfor v in parameter.volumeMounts.configMap
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap: {\n\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif v.items
|
||||
!= _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName:
|
||||
\ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n}\noutput: {\n\tapiVersion: \"apps/v1\"\n\tkind: \"Deployment\"\n\tspec:
|
||||
{\n\t\tselector: matchLabels: \"app.oam.dev/component\": context.name\n\n\t\ttemplate:
|
||||
{\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif parameter.labels
|
||||
!= _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif parameter.addRevisionLabel
|
||||
{\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/component\":
|
||||
context.name\n\t\t\t\t}\n\t\t\t\tif parameter.annotations != _|_ {\n\t\t\t\t\tannotations:
|
||||
parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec: {\n\t\t\t\tcontainers:
|
||||
[{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage: parameter.image\n\t\t\t\t\tif
|
||||
parameter[\"port\"] != _|_ && parameter[\"ports\"] == _|_ {\n\t\t\t\t\t\tports:
|
||||
[{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports
|
||||
{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol:
|
||||
\ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
\"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy:
|
||||
parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"cmd\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\t\tvolumeMounts: [ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc
|
||||
+ mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir
|
||||
+ mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"livenessProbe\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe:
|
||||
parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif parameter[\"hostAliases\"]
|
||||
!= _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases: parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"imagePullSecrets\"] != _|_ {\n\t\t\t\t\timagePullSecrets:
|
||||
[ for v in parameter.imagePullSecrets {\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\tvolumes: [ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\tif v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim:
|
||||
claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif v.type ==
|
||||
\"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: volumesArray.pvc
|
||||
+ volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir
|
||||
+ volumesArray.hostPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts:
|
||||
[\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort:
|
||||
v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name
|
||||
== _|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs:
|
||||
{\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion:
|
||||
\"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec:
|
||||
{\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports:
|
||||
exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter:
|
||||
{\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]:
|
||||
string\n\n\t// +usage=Specify the annotations in the workload\n\tannotations?:
|
||||
[string]: string\n\n\t// +usage=Which image would you like to use for
|
||||
your service\n\t// +short=i\n\timage: string\n\n\t// +usage=Specify
|
||||
image pull policy for your service\n\timagePullPolicy?: \"Always\" |
|
||||
\"Never\" | \"IfNotPresent\"\n\n\t// +usage=Specify image pull secrets
|
||||
for your service\n\timagePullSecrets?: [...string]\n\n\t// +ignore\n\t//
|
||||
+usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?:
|
||||
int\n\n\t// +usage=Which ports do you want customer traffic sent to,
|
||||
defaults to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose
|
||||
on the pod's IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?:
|
||||
string\n\t\t// +usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol:
|
||||
*\"TCP\" | \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should
|
||||
be exposed\n\t\texpose: *false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify
|
||||
what kind of Service you want. options: \"ClusterIP\", \"NodePort\",
|
||||
\"LoadBalancer\", \"ExternalName\"\n\texposeType: *\"ClusterIP\" | \"NodePort\"
|
||||
| \"LoadBalancer\" | \"ExternalName\"\n\n\t// +ignore\n\t// +usage=If
|
||||
addRevisionLabel is true, the revision label will be added to the underlying
|
||||
pods\n\taddRevisionLabel: *false | bool\n\n\t// +usage=Commands to run
|
||||
in the container\n\tcmd?: [...string]\n\n\t// +usage=Define arguments
|
||||
by using environment variables\n\tenv?: [...{\n\t\t// +usage=Environment
|
||||
variable name\n\t\tname: string\n\t\t// +usage=The value of the environment
|
||||
variable\n\t\tvalue?: string\n\t\t// +usage=Specifies a source the value
|
||||
of this var should come from\n\t\tvalueFrom?: {\n\t\t\t// +usage=Selects
|
||||
a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?: {\n\t\t\t\t//
|
||||
+usage=The name of the secret in the pod's namespace to select from\n\t\t\t\tname:
|
||||
string\n\t\t\t\t// +usage=The key of the secret to select from. Must
|
||||
be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t// +usage=Selects
|
||||
a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the config map in the pod's namespace
|
||||
to select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the
|
||||
config map to select from. Must be a valid secret key\n\t\t\t\tkey:
|
||||
string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number of CPU units for
|
||||
the service, like \n\tcpu?: string\n\n\t//
|
||||
+usage=Specifies the attributes of the memory resource required for
|
||||
the container.\n\tmemory?: string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount
|
||||
PVC type volume\n\t\tpvc?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\t// +usage=The name of the PVC\n\t\t\tclaimName: string\n\t\t}]\n\t\t//
|
||||
+usage=Mount ConfigMap type volume\n\t\tconfigMap?: [...{\n\t\t\tname:
|
||||
\ string\n\t\t\tmountPath: string\n\t\t\tdefaultMode: *420 |
|
||||
int\n\t\t\tcmName: string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath:
|
||||
string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount
|
||||
Secret type volume\n\t\tsecret?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount EmptyDir type volume\n\t\temptyDir?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tmedium:
|
||||
\ *\"\" | \"Memory\"\n\t\t}]\n\t\t// +usage=Mount HostPath type volume\n\t\thostPath?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tpath:
|
||||
\ string\n\t\t}]\n\t}\n\n\t// +usage=Deprecated field, use volumeMounts
|
||||
instead.\n\tvolumes?: [...{\n\t\tname: string\n\t\tmountPath: string\n\t\t//
|
||||
+usage=Specify volume type, options: \"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype:
|
||||
\"pvc\" | \"configMap\" | \"secret\" | \"emptyDir\"\n\t\tif type ==
|
||||
\"pvc\" {\n\t\t\tclaimName: string\n\t\t}\n\t\tif type == \"configMap\"
|
||||
{\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}\n\t\tif type == \"secret\" {\n\t\t\tdefaultMode:
|
||||
*420 | int\n\t\t\tsecretName: string\n\t\t\titems?: [...{\n\t\t\t\tkey:
|
||||
\ string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"emptyDir\" {\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t//
|
||||
+usage=Instructions for assessing whether the container is alive.\n\tlivenessProbe?:
|
||||
#HealthProbe\n\n\t// +usage=Instructions for assessing whether the container
|
||||
is in a suitable state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t//
|
||||
+usage=Specify the hostAliases to add\n\thostAliases?: [...{\n\t\tip:
|
||||
string\n\t\thostnames: [...string]\n\t}]\n}\n#HealthProbe: {\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing a command.
|
||||
Either this attribute or the httpGet attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
httpGet attribute and the tcpSocket attribute.\n\texec?: {\n\t\t// +usage=A
|
||||
command to be executed inside the container to assess its health. Each
|
||||
space delimited token of the command is a separate array element. Commands
|
||||
exiting 0 are considered to be successful probes, whilst all other exit
|
||||
codes are considered failures.\n\t\tcommand: [...string]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket
|
||||
attribute MUST be specified. This attribute is mutually exclusive with
|
||||
both the exec attribute and the tcpSocket attribute.\n\thttpGet?: {\n\t\t//
|
||||
+usage=The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.\n\t\tpath: string\n\t\t// +usage=The TCP socket
|
||||
within the container to which the HTTP GET request should be directed.\n\t\tport:
|
||||
int\n\t\thttpHeaders?: [...{\n\t\t\tname: string\n\t\t\tvalue: string\n\t\t}]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by probing a TCP
|
||||
socket. Either this attribute or the exec attribute or the httpGet attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
exec attribute and the httpGet attribute.\n\ttcpSocket?: {\n\t\t// +usage=The
|
||||
TCP socket within the container that should be probed to assess container
|
||||
health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after the
|
||||
container is started before the first probe is initiated.\n\tinitialDelaySeconds:
|
||||
*0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds:
|
||||
*10 | int\n\n\t// +usage=Number of seconds after which the probe times
|
||||
out.\n\ttimeoutSeconds: *1 | int\n\n\t// +usage=Minimum consecutive
|
||||
successes for the probe to be considered successful after having failed.\n\tsuccessThreshold:
|
||||
*1 | int\n\n\t// +usage=Number of consecutive failures required to determine
|
||||
the container is not alive (liveness probe) or not ready (readiness
|
||||
probe).\n\tfailureThreshold: *3 | int\n}\n"
|
||||
status:
|
||||
customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage:
|
||||
\"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\""
|
||||
healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas:
|
||||
\ *0 | int\n\treplicas: *0 | int\n\tobservedGeneration:
|
||||
*0 | int\n} & {\n\tif context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas:
|
||||
context.output.status.updatedReplicas\n\t}\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif
|
||||
context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif
|
||||
context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration:
|
||||
context.output.status.observedGeneration\n\t}\n}\nisHealth: (context.output.spec.replicas
|
||||
== ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas)
|
||||
&& (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration
|
||||
== context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)"
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
type: deployments.apps
|
||||
status: {}
|
||||
traitDefinitions:
|
||||
scaler:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Manually scale K8s pod for your workload
|
||||
which follows the pod spec in path 'spec.template'.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: scaler
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
definitionRef:
|
||||
name: ""
|
||||
schematic:
|
||||
cue:
|
||||
template: "parameter: {\n\t// +usage=Specify the number of workload\n\treplicas:
|
||||
*1 | int\n}\n// +patchStrategy=retainKeys\npatch: spec: replicas: parameter.replicas\n"
|
||||
status: {}
|
||||
status: {}
|
||||
19
pkg/appfile/testdata/backport-1-2/wsd.yaml
vendored
Normal file
19
pkg/appfile/testdata/backport-1-2/wsd.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Apply application for your workflow steps
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-application
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
// apply application
|
||||
output: op.#ApplyApplication & {}
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/client"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
types "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
|
||||
v1beta12 "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
v1beta12 "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
@@ -18,8 +18,9 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
|
||||
)
|
||||
|
||||
// Builder build command with factory
|
||||
|
||||
@@ -17,8 +17,9 @@ limitations under the License.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
|
||||
)
|
||||
|
||||
// Factory client factory for running command
|
||||
|
||||
32
pkg/cmd/util/helpers.go
Normal file
32
pkg/cmd/util/helpers.go
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2022 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 util
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
)
|
||||
|
||||
// CheckErr wraps the kubectl CheckErr func by preventing inappropriate type conversion and panic
|
||||
func CheckErr(err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
cmdutil.CheckErr(errors.New(err.Error()))
|
||||
}
|
||||
}()
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
@@ -18,9 +18,9 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/env"
|
||||
)
|
||||
|
||||
@@ -3897,7 +3897,7 @@ spec:
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: #PatchParams | close({
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the environment variables for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
|
||||
@@ -21,9 +21,11 @@ import (
|
||||
"sync"
|
||||
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraforv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraforv1beta2 "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -32,11 +34,10 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
monitorContext "github.com/oam-dev/kubevela/pkg/monitor/context"
|
||||
"github.com/oam-dev/kubevela/pkg/monitor/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
)
|
||||
|
||||
@@ -229,34 +230,22 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
)
|
||||
|
||||
if wl.CapabilityCategory == types.TerraformCategory {
|
||||
var configuration terraformapi.Configuration
|
||||
var configuration terraforv1beta2.Configuration
|
||||
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: namespace}, &configuration); err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appName, wl.Name)
|
||||
}
|
||||
|
||||
isLatest := func() bool {
|
||||
if configuration.Status.ObservedGeneration != 0 {
|
||||
if configuration.Status.ObservedGeneration != configuration.Generation {
|
||||
return false
|
||||
if kerrors.IsNotFound(err) {
|
||||
var legacyConfiguration terraforv1beta1.Configuration
|
||||
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: namespace}, &legacyConfiguration); err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appName, wl.Name)
|
||||
}
|
||||
isHealth = setStatus(&status, legacyConfiguration.Status.ObservedGeneration, legacyConfiguration.Generation,
|
||||
legacyConfiguration.GetLabels(), appRev.Name, legacyConfiguration.Status.Apply.State, legacyConfiguration.Status.Apply.Message)
|
||||
} else {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appName, wl.Name)
|
||||
}
|
||||
// Use AppRevision to avoid getting the configuration before the patch.
|
||||
if v, ok := configuration.GetLabels()[oam.LabelAppRevision]; ok {
|
||||
if v != appRev.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
if !isLatest() || configuration.Status.Apply.State != terraformtypes.Available {
|
||||
status.Healthy = false
|
||||
isHealth = false
|
||||
} else {
|
||||
status.Healthy = true
|
||||
isHealth = true
|
||||
isHealth = setStatus(&status, configuration.Status.ObservedGeneration, configuration.Generation, configuration.GetLabels(),
|
||||
appRev.Name, configuration.Status.Apply.State, configuration.Status.Apply.Message)
|
||||
}
|
||||
status.Message = configuration.Status.Apply.Message
|
||||
} else {
|
||||
if ok, err := wl.EvalHealth(wl.Ctx, h.r.Client, namespace); !ok || err != nil {
|
||||
isHealth = false
|
||||
@@ -292,6 +281,29 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
return &status, isHealth, nil
|
||||
}
|
||||
|
||||
func setStatus(status *common.ApplicationComponentStatus, observedGeneration, generation int64, labels map[string]string,
|
||||
appRevName string, state terraformtypes.ConfigurationState, message string) bool {
|
||||
isLatest := func() bool {
|
||||
if observedGeneration != 0 && observedGeneration != generation {
|
||||
return false
|
||||
}
|
||||
// Use AppRevision to avoid getting the configuration before the patch.
|
||||
if v, ok := labels[oam.LabelAppRevision]; ok {
|
||||
if v != appRevName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !isLatest() || state != terraformtypes.Available {
|
||||
status.Healthy = false
|
||||
return false
|
||||
}
|
||||
status.Healthy = true
|
||||
status.Message = message
|
||||
return true
|
||||
}
|
||||
|
||||
func generateScopeReference(scopes []appfile.Scope) []corev1.ObjectReference {
|
||||
var references []corev1.ObjectReference
|
||||
for _, scope := range scopes {
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/testutil"
|
||||
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -343,7 +343,7 @@ var _ = Describe("Test Application health check", func() {
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "terraform.core.oam.dev/v1beta1",
|
||||
APIVersion: "terraform.core.oam.dev/v1beta2",
|
||||
Kind: "Configuration",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -117,8 +117,16 @@ func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application)
|
||||
return err
|
||||
}
|
||||
|
||||
var componentNames []string
|
||||
for _, c := range app.Spec.Components {
|
||||
componentNames = append(componentNames, c.Name)
|
||||
}
|
||||
|
||||
for _, c := range app.Spec.Components {
|
||||
if c.Name == o.Component {
|
||||
if dcName, ok := checkDependsOnValidComponent(c.DependsOn, componentNames); !ok {
|
||||
return errors.Errorf("component %s not found, which is depended by %s", dcName, c.Name)
|
||||
}
|
||||
step.Inputs = append(step.Inputs, c.Inputs...)
|
||||
for index := range step.Inputs {
|
||||
parameterKey := strings.TrimSpace(step.Inputs[index].ParameterKey)
|
||||
@@ -135,11 +143,23 @@ func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application)
|
||||
step.Properties = util.Object2RawExtension(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
return errors.Errorf("component %s not found", o.Component)
|
||||
}
|
||||
|
||||
func checkDependsOnValidComponent(dependsOnComponentNames, allComponentNames []string) (string, bool) {
|
||||
// does not depends on other components
|
||||
if dependsOnComponentNames == nil {
|
||||
return "", true
|
||||
}
|
||||
for _, dc := range dependsOnComponentNames {
|
||||
if !utils.StringsContain(allComponentNames, dc) {
|
||||
return dc, false
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
func (h *AppHandler) renderComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentRender {
|
||||
return func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string, env string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
ctx := multicluster.ContextWithClusterName(context.Background(), clusterName)
|
||||
|
||||
@@ -240,4 +240,118 @@ var _ = Describe("Test Application workflow generator", func() {
|
||||
_, _, err = renderFunc(comp, nil, "", "", "")
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test generate application workflow with dependsOn", func() {
|
||||
app := &oamcore.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-input-output",
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
DependsOn: []string{"myweb1"},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
af, err := appParser.GenerateAppFile(ctx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
appRev := &oamcore.ApplicationRevision{}
|
||||
|
||||
handler, err := NewAppHandler(ctx, reconciler, app, appParser)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
taskRunner, err := handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(taskRunner)).Should(BeEquivalentTo(2))
|
||||
Expect(taskRunner[0].Name()).Should(BeEquivalentTo("myweb1"))
|
||||
Expect(taskRunner[1].Name()).Should(BeEquivalentTo("myweb2"))
|
||||
})
|
||||
|
||||
It("Test generate application workflow with invalid dependsOn", func() {
|
||||
app := &oamcore.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-input-output",
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
DependsOn: []string{"myweb0"},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
af, err := appParser.GenerateAppFile(ctx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
appRev := &oamcore.ApplicationRevision{}
|
||||
|
||||
handler, err := NewAppHandler(ctx, reconciler, app, appParser)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
|
||||
It("Test generate application workflow with multiple invalid dependsOn", func() {
|
||||
app := &oamcore.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-input-output",
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
DependsOn: []string{"myweb1", "myweb0", "myweb3"},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
af, err := appParser.GenerateAppFile(ctx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
appRev := &oamcore.ApplicationRevision{}
|
||||
|
||||
handler, err := NewAppHandler(ctx, reconciler, app, appParser)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -410,10 +410,12 @@ func (h *AppHandler) currentAppRevIsNew(ctx context.Context) (bool, bool, error)
|
||||
if isLatestRev {
|
||||
appSpec := h.currentAppRev.Spec.Application.Spec
|
||||
traitDef := h.currentAppRev.Spec.TraitDefinitions
|
||||
workflowStepDef := h.currentAppRev.Spec.WorkflowStepDefinitions
|
||||
h.currentAppRev = h.latestAppRev.DeepCopy()
|
||||
h.currentRevHash = h.app.Status.LatestRevision.RevisionHash
|
||||
h.currentAppRev.Spec.Application.Spec = appSpec
|
||||
h.currentAppRev.Spec.TraitDefinitions = traitDef
|
||||
h.currentAppRev.Spec.WorkflowStepDefinitions = workflowStepDef
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
@@ -755,6 +757,7 @@ func (h *AppHandler) FinalizeAndApplyAppRevision(ctx context.Context) error {
|
||||
appRev.SetGroupVersionKind(v1beta1.ApplicationRevisionGroupVersionKind)
|
||||
// pass application's annotations & labels to app revision
|
||||
appRev.SetAnnotations(h.app.GetAnnotations())
|
||||
delete(appRev.Annotations, oam.AnnotationLastAppliedConfiguration)
|
||||
appRev.SetLabels(h.app.GetLabels())
|
||||
util.AddLabels(appRev, map[string]string{
|
||||
oam.LabelAppName: h.app.GetName(),
|
||||
|
||||
@@ -19,6 +19,8 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -825,3 +827,347 @@ var _ = Describe("Test remove SkipAppRev func", func() {
|
||||
Expect(res.Components[0].Traits[1].Type).Should(BeEquivalentTo("service"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Test PrepareCurrentAppRevision", func() {
|
||||
var app v1beta1.Application
|
||||
var apprev v1beta1.ApplicationRevision
|
||||
ctx := context.Background()
|
||||
var handler *AppHandler
|
||||
|
||||
BeforeEach(func() {
|
||||
// prepare ComponentDefinition
|
||||
var compd v1beta1.ComponentDefinition
|
||||
Expect(yaml.Unmarshal([]byte(componentDefYaml), &compd)).To(Succeed())
|
||||
Expect(k8sClient.Create(ctx, &compd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
// prepare WorkflowStepDefinition
|
||||
wsdYaml := `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Apply application for your workflow steps
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-application
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
// apply application
|
||||
output: op.#ApplyApplication & {}
|
||||
`
|
||||
var wsd v1beta1.WorkflowStepDefinition
|
||||
Expect(yaml.Unmarshal([]byte(wsdYaml), &wsd)).To(Succeed())
|
||||
Expect(k8sClient.Create(ctx, &wsd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
// prepare application and application revision
|
||||
appYaml := `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
type: worker
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply
|
||||
type: apply-application
|
||||
status:
|
||||
latestRevision:
|
||||
name: backport-1-2-test-demo-v1
|
||||
revision: 1
|
||||
revisionHash: 38ddf4e721073703
|
||||
`
|
||||
Expect(yaml.Unmarshal([]byte(appYaml), &app)).To(Succeed())
|
||||
Expect(k8sClient.Create(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
// prepare application revision
|
||||
apprevYaml := `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ApplicationRevision
|
||||
metadata:
|
||||
name: backport-1-2-test-demo-v1
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: core.oam.dev/v1beta1
|
||||
controller: true
|
||||
kind: Application
|
||||
name: backport-1-2-test-demo
|
||||
uid: b69fab34-7058-412b-994d-1465a9421f06
|
||||
spec:
|
||||
application:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: backport-1-2-test-demo
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: backport-1-2-test-demo
|
||||
properties:
|
||||
image: nginx
|
||||
type: worker
|
||||
status: {}
|
||||
componentDefinitions:
|
||||
webservice:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes long-running, scalable, containerized
|
||||
services that have a stable network endpoint to receive external network
|
||||
traffic from customers.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: webservice
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tname:
|
||||
\ v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor
|
||||
v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret:
|
||||
*[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\temptyDir:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.emptyDir {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesArray:
|
||||
{\n\tpvc: *[\n\t\tfor v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tpersistentVolumeClaim: claimName: v.claimName\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tconfigMap: *[\n\t\t\tfor v in parameter.volumeMounts.configMap
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap: {\n\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif v.items
|
||||
!= _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName:
|
||||
\ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n}\noutput: {\n\tapiVersion: \"apps/v1\"\n\tkind: \"Deployment\"\n\tspec:
|
||||
{\n\t\tselector: matchLabels: \"app.oam.dev/component\": context.name\n\n\t\ttemplate:
|
||||
{\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif parameter.labels
|
||||
!= _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif parameter.addRevisionLabel
|
||||
{\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/component\":
|
||||
context.name\n\t\t\t\t}\n\t\t\t\tif parameter.annotations != _|_ {\n\t\t\t\t\tannotations:
|
||||
parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec: {\n\t\t\t\tcontainers:
|
||||
[{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage: parameter.image\n\t\t\t\t\tif
|
||||
parameter[\"port\"] != _|_ && parameter[\"ports\"] == _|_ {\n\t\t\t\t\t\tports:
|
||||
[{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports
|
||||
{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol:
|
||||
\ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
\"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy:
|
||||
parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"cmd\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\t\tvolumeMounts: [ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc
|
||||
+ mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir
|
||||
+ mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"livenessProbe\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe:
|
||||
parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif parameter[\"hostAliases\"]
|
||||
!= _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases: parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"imagePullSecrets\"] != _|_ {\n\t\t\t\t\timagePullSecrets:
|
||||
[ for v in parameter.imagePullSecrets {\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\tvolumes: [ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\tif v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim:
|
||||
claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif v.type ==
|
||||
\"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: volumesArray.pvc
|
||||
+ volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir
|
||||
+ volumesArray.hostPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts:
|
||||
[\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort:
|
||||
v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name
|
||||
== _|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs:
|
||||
{\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion:
|
||||
\"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec:
|
||||
{\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports:
|
||||
exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter:
|
||||
{\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]:
|
||||
string\n\n\t// +usage=Specify the annotations in the workload\n\tannotations?:
|
||||
[string]: string\n\n\t// +usage=Which image would you like to use for
|
||||
your service\n\t// +short=i\n\timage: string\n\n\t// +usage=Specify
|
||||
image pull policy for your service\n\timagePullPolicy?: \"Always\" |
|
||||
\"Never\" | \"IfNotPresent\"\n\n\t// +usage=Specify image pull secrets
|
||||
for your service\n\timagePullSecrets?: [...string]\n\n\t// +ignore\n\t//
|
||||
+usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?:
|
||||
int\n\n\t// +usage=Which ports do you want customer traffic sent to,
|
||||
defaults to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose
|
||||
on the pod's IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?:
|
||||
string\n\t\t// +usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol:
|
||||
*\"TCP\" | \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should
|
||||
be exposed\n\t\texpose: *false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify
|
||||
what kind of Service you want. options: \"ClusterIP\", \"NodePort\",
|
||||
\"LoadBalancer\", \"ExternalName\"\n\texposeType: *\"ClusterIP\" | \"NodePort\"
|
||||
| \"LoadBalancer\" | \"ExternalName\"\n\n\t// +ignore\n\t// +usage=If
|
||||
addRevisionLabel is true, the revision label will be added to the underlying
|
||||
pods\n\taddRevisionLabel: *false | bool\n\n\t// +usage=Commands to run
|
||||
in the container\n\tcmd?: [...string]\n\n\t// +usage=Define arguments
|
||||
by using environment variables\n\tenv?: [...{\n\t\t// +usage=Environment
|
||||
variable name\n\t\tname: string\n\t\t// +usage=The value of the environment
|
||||
variable\n\t\tvalue?: string\n\t\t// +usage=Specifies a source the value
|
||||
of this var should come from\n\t\tvalueFrom?: {\n\t\t\t// +usage=Selects
|
||||
a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?: {\n\t\t\t\t//
|
||||
+usage=The name of the secret in the pod's namespace to select from\n\t\t\t\tname:
|
||||
string\n\t\t\t\t// +usage=The key of the secret to select from. Must
|
||||
be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t// +usage=Selects
|
||||
a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the config map in the pod's namespace
|
||||
to select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the
|
||||
config map to select from. Must be a valid secret key\n\t\t\t\tkey:
|
||||
string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number of CPU units for
|
||||
the service, like \n\tcpu?: string\n\n\t//
|
||||
+usage=Specifies the attributes of the memory resource required for
|
||||
the container.\n\tmemory?: string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount
|
||||
PVC type volume\n\t\tpvc?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\t// +usage=The name of the PVC\n\t\t\tclaimName: string\n\t\t}]\n\t\t//
|
||||
+usage=Mount ConfigMap type volume\n\t\tconfigMap?: [...{\n\t\t\tname:
|
||||
\ string\n\t\t\tmountPath: string\n\t\t\tdefaultMode: *420 |
|
||||
int\n\t\t\tcmName: string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath:
|
||||
string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount
|
||||
Secret type volume\n\t\tsecret?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount EmptyDir type volume\n\t\temptyDir?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tmedium:
|
||||
\ *\"\" | \"Memory\"\n\t\t}]\n\t\t// +usage=Mount HostPath type volume\n\t\thostPath?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tpath:
|
||||
\ string\n\t\t}]\n\t}\n\n\t// +usage=Deprecated field, use volumeMounts
|
||||
instead.\n\tvolumes?: [...{\n\t\tname: string\n\t\tmountPath: string\n\t\t//
|
||||
+usage=Specify volume type, options: \"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype:
|
||||
\"pvc\" | \"configMap\" | \"secret\" | \"emptyDir\"\n\t\tif type ==
|
||||
\"pvc\" {\n\t\t\tclaimName: string\n\t\t}\n\t\tif type == \"configMap\"
|
||||
{\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}\n\t\tif type == \"secret\" {\n\t\t\tdefaultMode:
|
||||
*420 | int\n\t\t\tsecretName: string\n\t\t\titems?: [...{\n\t\t\t\tkey:
|
||||
\ string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"emptyDir\" {\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t//
|
||||
+usage=Instructions for assessing whether the container is alive.\n\tlivenessProbe?:
|
||||
#HealthProbe\n\n\t// +usage=Instructions for assessing whether the container
|
||||
is in a suitable state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t//
|
||||
+usage=Specify the hostAliases to add\n\thostAliases?: [...{\n\t\tip:
|
||||
string\n\t\thostnames: [...string]\n\t}]\n}\n#HealthProbe: {\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing a command.
|
||||
Either this attribute or the httpGet attribute or the tcpSocket attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
httpGet attribute and the tcpSocket attribute.\n\texec?: {\n\t\t// +usage=A
|
||||
command to be executed inside the container to assess its health. Each
|
||||
space delimited token of the command is a separate array element. Commands
|
||||
exiting 0 are considered to be successful probes, whilst all other exit
|
||||
codes are considered failures.\n\t\tcommand: [...string]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by executing an HTTP
|
||||
GET request. Either this attribute or the exec attribute or the tcpSocket
|
||||
attribute MUST be specified. This attribute is mutually exclusive with
|
||||
both the exec attribute and the tcpSocket attribute.\n\thttpGet?: {\n\t\t//
|
||||
+usage=The endpoint, relative to the port, to which the HTTP GET request
|
||||
should be directed.\n\t\tpath: string\n\t\t// +usage=The TCP socket
|
||||
within the container to which the HTTP GET request should be directed.\n\t\tport:
|
||||
int\n\t\thttpHeaders?: [...{\n\t\t\tname: string\n\t\t\tvalue: string\n\t\t}]\n\t}\n\n\t//
|
||||
+usage=Instructions for assessing container health by probing a TCP
|
||||
socket. Either this attribute or the exec attribute or the httpGet attribute
|
||||
MUST be specified. This attribute is mutually exclusive with both the
|
||||
exec attribute and the httpGet attribute.\n\ttcpSocket?: {\n\t\t// +usage=The
|
||||
TCP socket within the container that should be probed to assess container
|
||||
health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after the
|
||||
container is started before the first probe is initiated.\n\tinitialDelaySeconds:
|
||||
*0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds:
|
||||
*10 | int\n\n\t// +usage=Number of seconds after which the probe times
|
||||
out.\n\ttimeoutSeconds: *1 | int\n\n\t// +usage=Minimum consecutive
|
||||
successes for the probe to be considered successful after having failed.\n\tsuccessThreshold:
|
||||
*1 | int\n\n\t// +usage=Number of consecutive failures required to determine
|
||||
the container is not alive (liveness probe) or not ready (readiness
|
||||
probe).\n\tfailureThreshold: *3 | int\n}\n"
|
||||
status:
|
||||
customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage:
|
||||
\"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\""
|
||||
healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas:
|
||||
\ *0 | int\n\treplicas: *0 | int\n\tobservedGeneration:
|
||||
*0 | int\n} & {\n\tif context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas:
|
||||
context.output.status.updatedReplicas\n\t}\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif
|
||||
context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif
|
||||
context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration:
|
||||
context.output.status.observedGeneration\n\t}\n}\nisHealth: (context.output.spec.replicas
|
||||
== ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas)
|
||||
&& (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration
|
||||
== context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)"
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
type: deployments.apps
|
||||
status: {}
|
||||
status: {}
|
||||
`
|
||||
Expect(yaml.Unmarshal([]byte(apprevYaml), &apprev)).To(Succeed())
|
||||
// simulate 1.2 version that WorkflowStepDefinitions are not patched in appliacation revision
|
||||
apprev.ObjectMeta.OwnerReferences[0].UID = app.ObjectMeta.UID
|
||||
Expect(k8sClient.Create(ctx, &apprev)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
// prepare handler
|
||||
_handler, err := NewAppHandler(ctx, reconciler, &app, nil)
|
||||
Expect(err).Should(Succeed())
|
||||
handler = _handler
|
||||
|
||||
})
|
||||
|
||||
It("Test currentAppRevIsNew func", func() {
|
||||
By("Backport 1.2 version that WorkflowStepDefinitions are not patched to application revision")
|
||||
// generate appfile
|
||||
appfile, err := appfile.NewApplicationParser(reconciler.Client, reconciler.dm, reconciler.pd).GenerateAppFile(ctx, &app)
|
||||
ctx = util.SetNamespaceInCtx(ctx, app.Namespace)
|
||||
Expect(err).To(Succeed())
|
||||
Expect(handler.PrepareCurrentAppRevision(ctx, appfile)).Should(Succeed())
|
||||
|
||||
// prepare apprev
|
||||
thisWSD := handler.currentAppRev.Spec.WorkflowStepDefinitions
|
||||
Expect(len(thisWSD) > 0 && func() bool {
|
||||
expected := appfile.RelatedWorkflowStepDefinitions
|
||||
for i, w := range thisWSD {
|
||||
expW := *(expected[i])
|
||||
if !reflect.DeepEqual(w, expW) {
|
||||
fmt.Printf("appfile wsd:%s apprev wsd%s", w.Name, expW.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/go-logr/logr"
|
||||
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformv1beta2 "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
@@ -121,7 +121,7 @@ var _ = BeforeSuite(func(done Done) {
|
||||
err = scheme.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
terraformv1beta1.AddToScheme(testScheme)
|
||||
terraformv1beta2.AddToScheme(testScheme)
|
||||
|
||||
crdv1.AddToScheme(testScheme)
|
||||
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.5
|
||||
controller-gen.kubebuilder.io/version: v0.6.0
|
||||
creationTimestamp: null
|
||||
name: configurations.terraform.core.oam.dev
|
||||
spec:
|
||||
additionalPrinterColumns:
|
||||
- JSONPath: .status.state
|
||||
name: STATE
|
||||
type: string
|
||||
- JSONPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
group: terraform.core.oam.dev
|
||||
names:
|
||||
kind: Configuration
|
||||
@@ -22,96 +13,294 @@ spec:
|
||||
plural: configurations
|
||||
singular: configuration
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Configuration is the Schema for the configurations API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ConfigurationSpec defines the desired state of Configuration
|
||||
properties:
|
||||
JSON:
|
||||
description: JSON is the Terraform JSON syntax configuration
|
||||
type: string
|
||||
backend:
|
||||
description: Backend stores the state in a Kubernetes secret with locking
|
||||
done using a Lease resource. TODO(zzxwill) If a backend exists in
|
||||
HCL/JSON, this can be optional. Currently, if Backend is not set by
|
||||
users, it still will set by the controller, ignoring the settings
|
||||
in HCL/JSON backend
|
||||
properties:
|
||||
inClusterConfig:
|
||||
description: InClusterConfig Used to authenticate to the cluster
|
||||
from inside a pod. Only `true` is allowed
|
||||
type: boolean
|
||||
secretSuffix:
|
||||
description: 'SecretSuffix used when creating secrets. Secrets will
|
||||
be named in the format: tfstate-{workspace}-{secretSuffix}'
|
||||
type: string
|
||||
type: object
|
||||
hcl:
|
||||
description: HCL is the Terraform HCL type configuration
|
||||
type: string
|
||||
variable:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
writeConnectionSecretToRef:
|
||||
description: WriteConnectionSecretToReference specifies the namespace
|
||||
and name of a Secret to which any connection details for this managed
|
||||
resource should be written. Connection details frequently include
|
||||
the endpoint, username, and password required to connect to the managed
|
||||
resource.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the secret.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the secret.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: ConfigurationStatus defines the observed state of Configuration
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
outputs:
|
||||
additionalProperties:
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
state:
|
||||
description: A ResourceState represents the status of a resource
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
served: true
|
||||
storage: true
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.apply.state
|
||||
name: STATE
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Configuration is the Schema for the configurations API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ConfigurationSpec defines the desired state of Configuration
|
||||
properties:
|
||||
JSON:
|
||||
description: 'JSON is the Terraform JSON syntax configuration. Deprecated:
|
||||
after v0.3.1, use HCL instead.'
|
||||
type: string
|
||||
backend:
|
||||
description: Backend stores the state in a Kubernetes secret with
|
||||
locking done using a Lease resource. TODO(zzxwill) If a backend
|
||||
exists in HCL/JSON, this can be optional. Currently, if Backend
|
||||
is not set by users, it still will set by the controller, ignoring
|
||||
the settings in HCL/JSON backend
|
||||
properties:
|
||||
inClusterConfig:
|
||||
description: InClusterConfig Used to authenticate to the cluster
|
||||
from inside a pod. Only `true` is allowed
|
||||
type: boolean
|
||||
secretSuffix:
|
||||
description: 'SecretSuffix used when creating secrets. Secrets
|
||||
will be named in the format: tfstate-{workspace}-{secretSuffix}'
|
||||
type: string
|
||||
type: object
|
||||
deleteResource:
|
||||
default: true
|
||||
description: DeleteResource will determine whether provisioned cloud
|
||||
resources will be deleted when CR is deleted
|
||||
type: boolean
|
||||
hcl:
|
||||
description: HCL is the Terraform HCL type configuration
|
||||
type: string
|
||||
path:
|
||||
description: Path is the sub-directory of remote git repository.
|
||||
type: string
|
||||
providerRef:
|
||||
description: ProviderReference specifies the reference to Provider
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
namespace:
|
||||
default: default
|
||||
description: Namespace of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
region:
|
||||
description: Region is cloud provider's region. It will override the
|
||||
region in the region field of ProviderReference
|
||||
type: string
|
||||
remote:
|
||||
description: Remote is a git repo which contains hcl files. Currently,
|
||||
only public git repos are supported.
|
||||
type: string
|
||||
variable:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
writeConnectionSecretToRef:
|
||||
description: WriteConnectionSecretToReference specifies the namespace
|
||||
and name of a Secret to which any connection details for this managed
|
||||
resource should be written. Connection details frequently include
|
||||
the endpoint, username, and password required to connect to the
|
||||
managed resource.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the secret.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the secret.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: ConfigurationStatus defines the observed state of Configuration
|
||||
properties:
|
||||
apply:
|
||||
description: ConfigurationApplyStatus is the status for Configuration
|
||||
apply
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
outputs:
|
||||
additionalProperties:
|
||||
description: Property is the property for an output
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
state:
|
||||
description: A ConfigurationState represents the status of a resource
|
||||
type: string
|
||||
type: object
|
||||
destroy:
|
||||
description: ConfigurationDestroyStatus is the status for Configuration
|
||||
destroy
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
state:
|
||||
description: A ConfigurationState represents the status of a resource
|
||||
type: string
|
||||
type: object
|
||||
observedGeneration:
|
||||
description: observedGeneration is the most recent generation observed
|
||||
for this Configuration. It corresponds to the Configuration's generation,
|
||||
which is updated on mutation by the API Server.
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
subresources:
|
||||
status: {}
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.apply.state
|
||||
name: STATE
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
name: v1beta2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Configuration is the Schema for the configurations API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ConfigurationSpec defines the desired state of Configuration
|
||||
properties:
|
||||
backend:
|
||||
description: Backend stores the state in a Kubernetes secret with
|
||||
locking done using a Lease resource. TODO(zzxwill) If a backend
|
||||
exists in HCL/JSON, this can be optional. Currently, if Backend
|
||||
is not set by users, it still will set by the controller, ignoring
|
||||
the settings in HCL/JSON backend
|
||||
properties:
|
||||
inClusterConfig:
|
||||
description: InClusterConfig Used to authenticate to the cluster
|
||||
from inside a pod. Only `true` is allowed
|
||||
type: boolean
|
||||
secretSuffix:
|
||||
description: 'SecretSuffix used when creating secrets. Secrets
|
||||
will be named in the format: tfstate-{workspace}-{secretSuffix}'
|
||||
type: string
|
||||
type: object
|
||||
customRegion:
|
||||
description: Region is cloud provider's region. It will override the
|
||||
region in the region field of ProviderReference
|
||||
type: string
|
||||
deleteResource:
|
||||
default: true
|
||||
description: DeleteResource will determine whether provisioned cloud
|
||||
resources will be deleted when CR is deleted
|
||||
type: boolean
|
||||
hcl:
|
||||
description: HCL is the Terraform HCL type configuration
|
||||
type: string
|
||||
path:
|
||||
description: Path is the sub-directory of remote git repository.
|
||||
type: string
|
||||
providerRef:
|
||||
description: ProviderReference specifies the reference to Provider
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referenced object.
|
||||
type: string
|
||||
namespace:
|
||||
default: default
|
||||
description: Namespace of the referenced object.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
remote:
|
||||
description: Remote is a git repo which contains hcl files. Currently,
|
||||
only public git repos are supported.
|
||||
type: string
|
||||
variable:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
writeConnectionSecretToRef:
|
||||
description: WriteConnectionSecretToReference specifies the namespace
|
||||
and name of a Secret to which any connection details for this managed
|
||||
resource should be written. Connection details frequently include
|
||||
the endpoint, username, and password required to connect to the
|
||||
managed resource.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the secret.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the secret.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: ConfigurationStatus defines the observed state of Configuration
|
||||
properties:
|
||||
apply:
|
||||
description: ConfigurationApplyStatus is the status for Configuration
|
||||
apply
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
outputs:
|
||||
additionalProperties:
|
||||
description: Property is the property for an output
|
||||
properties:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
state:
|
||||
description: A ConfigurationState represents the status of a resource
|
||||
type: string
|
||||
type: object
|
||||
destroy:
|
||||
description: ConfigurationDestroyStatus is the status for Configuration
|
||||
destroy
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
state:
|
||||
description: A ConfigurationState represents the status of a resource
|
||||
type: string
|
||||
type: object
|
||||
observedGeneration:
|
||||
description: observedGeneration is the most recent generation observed
|
||||
for this Configuration. It corresponds to the Configuration's generation,
|
||||
which is updated on mutation by the API Server. If ObservedGeneration
|
||||
equals Generation, and State is Available, the value of Outputs
|
||||
is latest
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
storedVersions: []
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
oamtypes "github.com/oam-dev/kubevela/apis/types"
|
||||
|
||||
@@ -317,7 +317,7 @@ func (val *Value) LookupValue(paths ...string) (*Value, error) {
|
||||
func (val *Value) LookupByScript(script string) (*Value, error) {
|
||||
var outputKey = "zz_output__"
|
||||
script = strings.TrimSpace(script)
|
||||
scriptFile, err := parser.ParseFile("-", script)
|
||||
scriptFile, err := parser.ParseFile("-", script, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "parse script")
|
||||
}
|
||||
@@ -327,7 +327,7 @@ func (val *Value) LookupByScript(script string) (*Value, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawFile, err := parser.ParseFile("-", raw)
|
||||
rawFile, err := parser.ParseFile("-", raw, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "parse script")
|
||||
}
|
||||
|
||||
@@ -597,6 +597,23 @@ func TestLookupByScript(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
src: `
|
||||
traits: {
|
||||
ingress: {
|
||||
// +patchKey=name
|
||||
test: [{name: "main", image: "busybox"}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
script: `traits["ingress"]`,
|
||||
expect: `// +patchKey=name
|
||||
test: [{
|
||||
name: "main"
|
||||
image: "busybox"
|
||||
}]
|
||||
`,
|
||||
},
|
||||
{
|
||||
src: `
|
||||
apply: containers: [{name: "main", image: "busybox"}]
|
||||
`,
|
||||
script: `apply.containers[0].image`,
|
||||
|
||||
@@ -29,12 +29,15 @@ const (
|
||||
LegacyObjectTypeIdentifier featuregate.Feature = "LegacyObjectTypeIdentifier"
|
||||
// DeprecatedObjectLabelSelector enable the use of deprecated object label selector for selecting ref-object
|
||||
DeprecatedObjectLabelSelector featuregate.Feature = "DeprecatedObjectLabelSelector"
|
||||
// LegacyResourceTrackerGC enable the gc of legacy resource tracker in managed clusters
|
||||
LegacyResourceTrackerGC featuregate.Feature = "LegacyResourceTrackerGC"
|
||||
)
|
||||
|
||||
var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
DeprecatedPolicySpec: {Default: false, PreRelease: featuregate.Alpha},
|
||||
LegacyObjectTypeIdentifier: {Default: false, PreRelease: featuregate.Alpha},
|
||||
DeprecatedObjectLabelSelector: {Default: false, PreRelease: featuregate.Alpha},
|
||||
LegacyResourceTrackerGC: {Default: true, PreRelease: featuregate.Alpha},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
errors3 "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
@@ -46,7 +47,7 @@ const (
|
||||
// ClusterContextKey is the name of cluster using in client http context
|
||||
ClusterContextKey = contextKey("ClusterName")
|
||||
// ClusterLocalName specifies the local cluster
|
||||
ClusterLocalName = "local"
|
||||
ClusterLocalName = velatypes.ClusterLocalName
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -117,7 +117,7 @@ const (
|
||||
AnnotationAppGeneration = "app.oam.dev/generation"
|
||||
|
||||
// AnnotationLastAppliedConfig records the previous configuration of a
|
||||
// resource for use in a three way diff during a patching apply
|
||||
// resource for use in a three-way diff during a patching apply
|
||||
AnnotationLastAppliedConfig = "app.oam.dev/last-applied-configuration"
|
||||
|
||||
// AnnotationLastAppliedTime indicates the last applied time
|
||||
|
||||
@@ -20,7 +20,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -89,7 +91,7 @@ func (cache *resourceCache) get(ctx context.Context, mr v1beta1.ManagedResource)
|
||||
}
|
||||
if !entry.loaded {
|
||||
if err := cache.cli.Get(multicluster.ContextWithClusterName(ctx, mr.Cluster), mr.NamespacedName(), entry.obj); err != nil {
|
||||
if multicluster.IsNotFoundOrClusterNotExists(err) {
|
||||
if multicluster.IsNotFoundOrClusterNotExists(err) || meta.IsNoMatchError(err) || runtime.IsNotRegisteredError(err) {
|
||||
entry.exists = false
|
||||
} else {
|
||||
entry.err = errors.Wrapf(err, "failed to get resource %s", key)
|
||||
|
||||
@@ -30,10 +30,12 @@ import (
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/monitor/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
@@ -319,6 +321,10 @@ func (h *gcHandler) GarbageCollectComponentRevisionResourceTracker(ctx context.C
|
||||
const velaVersionNumberToUpgradeResourceTracker = "v1.2.0"
|
||||
|
||||
func (h *gcHandler) GarbageCollectLegacyResourceTrackers(ctx context.Context) error {
|
||||
// skip legacy gc if controller not enable this feature
|
||||
if !utilfeature.DefaultMutableFeatureGate.Enabled(features.LegacyResourceTrackerGC) {
|
||||
return nil
|
||||
}
|
||||
// skip legacy gc if application is not handled by new version rt
|
||||
if h.app.GetDeletionTimestamp() == nil && h.resourceKeeper._currentRT == nil {
|
||||
return nil
|
||||
|
||||
@@ -20,19 +20,19 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -107,13 +107,42 @@ func loggingApply(msg string, desired client.Object) {
|
||||
klog.InfoS(msg, "name", d.GetName(), "resource", desired.GetObjectKind().GroupVersionKind().String())
|
||||
}
|
||||
|
||||
// filterRecordForSpecial will filter special object that can reduce the record for "app.oam.dev/last-applied-configuration" annotation.
|
||||
func filterRecordForSpecial(desired client.Object) bool {
|
||||
if desired == nil {
|
||||
return false
|
||||
}
|
||||
gvk := desired.GetObjectKind().GroupVersionKind()
|
||||
gp, kd := gvk.Group, gvk.Kind
|
||||
if gp == "" {
|
||||
// group is empty means it's Kubernetes core API, we won't record annotation for Secret and Configmap
|
||||
if kd == "Secret" || kd == "ConfigMap" {
|
||||
return false
|
||||
}
|
||||
if _, ok := desired.(*corev1.ConfigMap); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := desired.(*corev1.Secret); ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ann := desired.GetAnnotations()
|
||||
if ann != nil {
|
||||
lac := ann[oam.AnnotationLastAppliedConfig]
|
||||
if lac == "-" || lac == "skip" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Apply applies new state to an object or create it if not exist
|
||||
func (a *APIApplicator) Apply(ctx context.Context, desired client.Object, ao ...ApplyOption) error {
|
||||
_, err := generateRenderHash(desired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
applyAct := &applyAction{updateAnnotation: true}
|
||||
applyAct := &applyAction{updateAnnotation: filterRecordForSpecial(desired)}
|
||||
existing, err := a.createOrGetExisting(ctx, applyAct, a.c, desired, ao...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -23,8 +23,10 @@ import (
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -423,3 +425,19 @@ func TestMustBeControlledByApp(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterSpecialAnn(t *testing.T) {
|
||||
var cm = &corev1.ConfigMap{}
|
||||
var sc = &corev1.Secret{}
|
||||
var dp = &appsv1.Deployment{}
|
||||
assert.Equal(t, false, filterRecordForSpecial(cm))
|
||||
assert.Equal(t, false, filterRecordForSpecial(sc))
|
||||
assert.Equal(t, true, filterRecordForSpecial(dp))
|
||||
|
||||
dp.Annotations = map[string]string{oam.AnnotationLastAppliedConfig: "-"}
|
||||
assert.Equal(t, false, filterRecordForSpecial(dp))
|
||||
dp.Annotations = map[string]string{oam.AnnotationLastAppliedConfig: "skip"}
|
||||
assert.Equal(t, false, filterRecordForSpecial(dp))
|
||||
dp.Annotations = map[string]string{oam.AnnotationLastAppliedConfig: "xxx"}
|
||||
assert.Equal(t, true, filterRecordForSpecial(dp))
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
clustergatewayapi "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/terraform-config-inspect/tfconfig"
|
||||
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
errors2 "github.com/pkg/errors"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
@@ -93,7 +93,7 @@ func init() {
|
||||
_ = istioclientv1beta1.AddToScheme(Scheme)
|
||||
_ = certmanager.AddToScheme(Scheme)
|
||||
_ = kruise.AddToScheme(Scheme)
|
||||
_ = terraformv1beta1.AddToScheme(Scheme)
|
||||
_ = terraformapi.AddToScheme(Scheme)
|
||||
_ = ocmclusterv1alpha1.Install(Scheme)
|
||||
_ = ocmclusterv1.Install(Scheme)
|
||||
_ = ocmworkv1.Install(Scheme)
|
||||
|
||||
160
pkg/utils/config/application.go
Normal file
160
pkg/utils/config/application.go
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2022 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 config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
tcv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"github.com/pkg/errors"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
||||
)
|
||||
|
||||
const (
|
||||
errAuthenticateProvider = "failed to authenticate Terraform cloud provider %s for %s"
|
||||
errProviderExists = "terraform provider %s for %s already exists"
|
||||
errDeleteProvider = "failed to delete Terraform Provider %s"
|
||||
errCouldNotDeleteProvider = "the Terraform Provider %s could not be disabled because it was created by enabling a Terraform provider or was manually created"
|
||||
errCheckProviderExistence = "failed to check if Terraform Provider %s exists"
|
||||
)
|
||||
|
||||
// UIParam is the UI parameters from VelaUX for the application
|
||||
type UIParam struct {
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
Project string `json:"project"`
|
||||
}
|
||||
|
||||
// CreateApplication creates a new application for the config
|
||||
func CreateApplication(ctx context.Context, k8sClient client.Client, name, componentType, properties string, ui UIParam) error {
|
||||
if strings.HasPrefix(componentType, types.TerraformComponentPrefix) {
|
||||
existed, err := IsTerraformProviderExisted(ctx, k8sClient, name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errAuthenticateProvider, name, componentType)
|
||||
}
|
||||
if existed {
|
||||
return fmt.Errorf(errProviderExists, name, componentType)
|
||||
}
|
||||
}
|
||||
app := v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
types.AnnotationConfigAlias: ui.Alias,
|
||||
types.AnnotationConfigDescription: ui.Description,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: componentType,
|
||||
types.LabelConfigProject: ui.Project,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: name,
|
||||
Type: componentType,
|
||||
Properties: &runtime.RawExtension{Raw: []byte(properties)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return k8sClient.Create(ctx, &app)
|
||||
}
|
||||
|
||||
// DeleteApplication deletes a config application, including a Terraform provider.
|
||||
// For a Terraform Provider, it can come from
|
||||
// 1). manually created a Terraform Provider object, like https://github.com/oam-dev/terraform-controller/blob/master/getting-started.md#aws
|
||||
// 2). by enabling a Terraform provider addon in version older than v1.3.0
|
||||
// 3). by create a Terraform provider via `vela provider add`
|
||||
// 4). by VelaUX
|
||||
// We will only target on deleting a provider which comes from 3) or 4) as for 1), it can be easily delete by hand, and
|
||||
// for 2), it will be recreated by the addon.
|
||||
func DeleteApplication(ctx context.Context, k8sClient client.Client, name string, isTerraformProvider bool) error {
|
||||
if isTerraformProvider {
|
||||
existed, err := IsTerraformProviderExisted(ctx, k8sClient, name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errCheckProviderExistence, name)
|
||||
}
|
||||
if existed {
|
||||
// In version 1.3.0, we used `providerAppName` as the name of the application to create a provider, but
|
||||
// in version 1.3.1, a config name is the config name, ie, the provider name. To keep backward compatibility,
|
||||
// we need to check the legacy name and the current name of the application.
|
||||
legacyName := fmt.Sprintf("%s-%s", types.ProviderAppPrefix, name)
|
||||
err1 := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: legacyName}, &v1beta1.Application{})
|
||||
if err1 == nil {
|
||||
name = legacyName
|
||||
}
|
||||
if err1 != nil {
|
||||
if kerrors.IsNotFound(err1) {
|
||||
err2 := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, &v1beta1.Application{})
|
||||
if err2 != nil {
|
||||
if kerrors.IsNotFound(err2) {
|
||||
return fmt.Errorf(errCouldNotDeleteProvider, name)
|
||||
}
|
||||
return fmt.Errorf(errDeleteProvider, name)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(errDeleteProvider, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a := &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k8sClient.Delete(ctx, a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListTerraformProviders returns a list of Terraform providers.
|
||||
func ListTerraformProviders(ctx context.Context, k8sClient client.Client) ([]tcv1beta1.Provider, error) {
|
||||
l := &tcv1beta1.ProviderList{}
|
||||
if err := k8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.Items, nil
|
||||
}
|
||||
|
||||
// IsTerraformProviderExisted returns whether a Terraform provider exists.
|
||||
func IsTerraformProviderExisted(ctx context.Context, k8sClient client.Client, name string) (bool, error) {
|
||||
l, err := ListTerraformProviders(ctx, k8sClient)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, p := range l {
|
||||
if p.Name == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
31
pkg/utils/config/project.go
Normal file
31
pkg/utils/config/project.go
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2022 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 config
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
// ProjectMatched will check whether a config secret can be used in a given project
|
||||
func ProjectMatched(s *v1.Secret, project string) bool {
|
||||
if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
97
pkg/utils/config/project_test.go
Normal file
97
pkg/utils/config/project_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2022 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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
var ResponseString = "Hello HTTP Get."
|
||||
|
||||
func TestMatchProject(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
corev1.AddToScheme(s)
|
||||
secret1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigProject: "p1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
secret2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigProject: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
secret *corev1.Secret
|
||||
project string
|
||||
}
|
||||
|
||||
type want struct {
|
||||
matched bool
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "matched",
|
||||
args: args{
|
||||
project: "p99",
|
||||
secret: secret1,
|
||||
},
|
||||
want: want{
|
||||
matched: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not matched",
|
||||
args: args{
|
||||
project: "p99",
|
||||
secret: secret2,
|
||||
},
|
||||
want: want{
|
||||
matched: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := ProjectMatched(tc.args.secret, tc.args.project)
|
||||
assert.Equal(t, tc.want.matched, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,8 @@ import (
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -136,6 +136,7 @@ func (c *AppCollector) FindResourceFromResourceTrackerSpec(app *v1beta1.Applicat
|
||||
ctx := context.Background()
|
||||
rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app)
|
||||
if err != nil {
|
||||
klog.Errorf("query the resourcetrackers failure %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
var resources = []Resource{}
|
||||
@@ -151,11 +152,19 @@ func (c *AppCollector) FindResourceFromResourceTrackerSpec(app *v1beta1.Applicat
|
||||
existResources[managedResource.ClusterObjectReference] = true
|
||||
obj, err := managedResource.ToUnstructuredWithData()
|
||||
if err != nil {
|
||||
klog.Errorf("get obj from resource tracker failure %s", err.Error())
|
||||
continue
|
||||
// For the application with apply once policy, there is no data in RT.
|
||||
_, obj, err = getObjectCreatedByComponent(c.k8sClient, managedResource.ObjectReference, managedResource.Cluster)
|
||||
if err != nil {
|
||||
klog.Errorf("get obj from the cluster failure %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
clusterName := managedResource.Cluster
|
||||
if clusterName == "" {
|
||||
clusterName = multicluster.ClusterLocalName
|
||||
}
|
||||
resources = append(resources, Resource{
|
||||
Cluster: managedResource.Cluster,
|
||||
Cluster: clusterName,
|
||||
Revision: oam.GetPublishVersion(rt),
|
||||
Component: managedResource.Component,
|
||||
Object: obj,
|
||||
@@ -393,16 +402,32 @@ func (c *HelmReleaseCollector) CollectServices(ctx context.Context, cluster stri
|
||||
}
|
||||
|
||||
// CollectIngress collect ingress of HelmRelease
|
||||
func (c *HelmReleaseCollector) CollectIngress(ctx context.Context, cluster string) ([]networkv1beta1.Ingress, error) {
|
||||
cctx := multicluster.ContextWithClusterName(ctx, cluster)
|
||||
func (c *HelmReleaseCollector) CollectIngress(ctx context.Context, cluster string) ([]unstructured.Unstructured, error) {
|
||||
clusterCTX := multicluster.ContextWithClusterName(ctx, cluster)
|
||||
listOptions := []client.ListOption{
|
||||
client.MatchingLabels(c.matchLabels),
|
||||
}
|
||||
var ingreses networkv1beta1.IngressList
|
||||
if err := c.cli.List(cctx, &ingreses, listOptions...); err != nil {
|
||||
return nil, err
|
||||
var ingresses = new(unstructured.UnstructuredList)
|
||||
ingresses.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "networking.k8s.io",
|
||||
Version: "v1beta1",
|
||||
Kind: "IngressList",
|
||||
})
|
||||
if err := c.cli.List(clusterCTX, ingresses, listOptions...); err != nil {
|
||||
if meta.IsNoMatchError(err) {
|
||||
ingresses.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "networking.k8s.io",
|
||||
Version: "v1",
|
||||
Kind: "IngressList",
|
||||
})
|
||||
if err := c.cli.List(clusterCTX, ingresses, listOptions...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ingreses.Items, nil
|
||||
return ingresses.Items, nil
|
||||
}
|
||||
|
||||
// helmReleasePodCollector collect pods created by helmRelease
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -275,14 +276,17 @@ func (h *provider) GeneratorServiceEndpoints(wfctx wfContext.Context, v *value.V
|
||||
for _, service := range services {
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromService(service, selectorNodeIP, cluster, resource.Component, "")...)
|
||||
}
|
||||
|
||||
// only support network/v1beta1
|
||||
ingress, err := hc.CollectIngress(ctx, resource.Cluster)
|
||||
if err != nil {
|
||||
klog.Error(err, "collect ingres by helm release failure", "helmRelease", resource.Name, "namespace", resource.Namespace, "cluster", resource.Cluster)
|
||||
}
|
||||
for _, ing := range ingress {
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromIngress(ing, cluster, resource.Component)...)
|
||||
for _, uns := range ingress {
|
||||
var ingress networkv1beta1.Ingress
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uns.UnstructuredContent(), &ingress); err != nil {
|
||||
klog.Errorf("fail to convert unstructured to ingress %s", err.Error())
|
||||
continue
|
||||
}
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromIngress(ingress, cluster, resource.Component)...)
|
||||
}
|
||||
case "SeldonDeployment":
|
||||
obj := new(unstructured.Unstructured)
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/klog"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@@ -83,7 +84,8 @@ var _ = Describe("Test Query Provider", func() {
|
||||
Name: "test",
|
||||
Namespace: "test",
|
||||
Annotations: map[string]string{
|
||||
"oam.dev/kubevela-version": "v1.2.0-beta.2",
|
||||
oam.AnnotationKubeVelaVersion: "v1.3.1",
|
||||
oam.AnnotationPublishVersion: "v1",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
@@ -154,6 +156,53 @@ var _ = Describe("Test Query Provider", func() {
|
||||
})
|
||||
Expect(k8sClient.Create(ctx, appService)).Should(BeNil())
|
||||
|
||||
rt := &v1beta1.ResourceTracker{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-v1-%s", oldApp.Name, oldApp.Namespace),
|
||||
Labels: map[string]string{
|
||||
oam.LabelAppName: oldApp.Name,
|
||||
oam.LabelAppNamespace: oldApp.Namespace,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationPublishVersion: "v1",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ManagedResources: []v1beta1.ManagedResource{
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
Namespace: namespace,
|
||||
Name: "web",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "web",
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Namespace: namespace,
|
||||
Name: "web",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "web",
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: v1beta1.ResourceTrackerTypeVersioned,
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, rt)).Should(BeNil())
|
||||
|
||||
prd := provider{cli: k8sClient}
|
||||
opt := `app: {
|
||||
name: "test"
|
||||
@@ -170,6 +219,9 @@ var _ = Describe("Test Query Provider", func() {
|
||||
|
||||
appResList := new(AppResourcesList)
|
||||
Expect(v.UnmarshalTo(appResList)).Should(BeNil())
|
||||
if appResList.Err != "" {
|
||||
klog.Error(appResList.Err)
|
||||
}
|
||||
|
||||
Expect(len(appResList.List)).Should(Equal(2))
|
||||
|
||||
@@ -180,7 +232,7 @@ var _ = Describe("Test Query Provider", func() {
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&app), updateApp)).Should(BeNil())
|
||||
|
||||
updateApp.ObjectMeta.Annotations = map[string]string{
|
||||
"oam.dev/kubevela-version": "master",
|
||||
oam.AnnotationKubeVelaVersion: "v1.1.0",
|
||||
}
|
||||
Expect(k8sClient.Update(ctx, updateApp)).Should(BeNil())
|
||||
newValue, err := value.NewValue(opt, nil, "")
|
||||
|
||||
@@ -19,7 +19,7 @@ package template
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -60,7 +60,8 @@ func (loader *WorkflowStepLoader) LoadTaskTemplate(ctx context.Context, name str
|
||||
staticFilename := name + ".cue"
|
||||
for _, file := range files {
|
||||
if staticFilename == file.Name() {
|
||||
content, err := templateFS.ReadFile(filepath.Join(templateDir, file.Name()))
|
||||
fileName := fmt.Sprintf("%s/%s", templateDir, file.Name())
|
||||
content, err := templateFS.ReadFile(fileName)
|
||||
return string(content), err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,11 @@ import (
|
||||
|
||||
"helm.sh/helm/v3/pkg/strvals"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
types2 "k8s.io/apimachinery/pkg/types"
|
||||
@@ -111,10 +108,11 @@ func NewAddonListCommand(c common.Args) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = listAddons(context.Background(), k8sClient, "")
|
||||
table, err := listAddons(context.Background(), k8sClient, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(table.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -131,7 +129,7 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com
|
||||
Enable addon by:
|
||||
vela addon enable <addon-name>
|
||||
Enable addon with specify version:
|
||||
vela addon enable <addon-name> --version <addon-version>
|
||||
vela addon enable <addon-name> --version <addon-version>
|
||||
Enable addon for specific clusters, (local means control plane):
|
||||
vela addon enable <addon-name> --clusters={local,cluster1,cluster2}
|
||||
`,
|
||||
@@ -181,7 +179,7 @@ Enable addon for specific clusters, (local means control plane):
|
||||
}
|
||||
}
|
||||
fmt.Printf("Addon: %s enabled Successfully.\n", name)
|
||||
AdditionalEndpointPrinter(ctx, c, k8sClient, name)
|
||||
AdditionalEndpointPrinter(ctx, c, k8sClient, name, false)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -192,25 +190,21 @@ Enable addon for specific clusters, (local means control plane):
|
||||
}
|
||||
|
||||
// AdditionalEndpointPrinter will print endpoints
|
||||
func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient client.Client, name string) {
|
||||
endpoints, _ := GetServiceEndpoints(ctx, k8sClient, pkgaddon.Convert2AppName(name), types.DefaultKubeVelaNS, c, Filter{})
|
||||
if len(endpoints) > 0 {
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetColWidth(100)
|
||||
table.SetHeader([]string{"Cluster", "Component", "Ref(Kind/Namespace/Name)", "Endpoint"})
|
||||
for _, endpoint := range endpoints {
|
||||
table.Append([]string{endpoint.Cluster, endpoint.Component, fmt.Sprintf("%s/%s/%s", endpoint.Ref.Kind, endpoint.Ref.Namespace, endpoint.Ref.Name), endpoint.String()})
|
||||
}
|
||||
fmt.Printf("Please access the %s from the following endpoints:\n", name)
|
||||
table.Render()
|
||||
func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient client.Client, name string, isUpgrade bool) {
|
||||
fmt.Printf("Please access the %s from the following endpoints:\n", name)
|
||||
err := printAppEndpoints(ctx, pkgaddon.Convert2AppName(name), types.DefaultKubeVelaNS, Filter{}, c)
|
||||
if err != nil {
|
||||
fmt.Println("Get application endpoints error:", err)
|
||||
return
|
||||
}
|
||||
if name == "velaux" {
|
||||
fmt.Println(`To check the initialized admin user name and password by:`)
|
||||
fmt.Println(` vela logs -n vela-system --name apiserver addon-velaux | grep "initialized admin username"`)
|
||||
if !isUpgrade {
|
||||
fmt.Println(`To check the initialized admin user name and password by:`)
|
||||
fmt.Println(` vela logs -n vela-system --name apiserver addon-velaux | grep "initialized admin username"`)
|
||||
}
|
||||
fmt.Println(`To open the dashboard directly by port-forward:`)
|
||||
fmt.Println(` vela port-forward -n vela-system addon-velaux 9082:80`)
|
||||
fmt.Println(`Select "Cluster: local | Namespace: vela-system | Component: velaux | Kind: Service" from the prompt.`)
|
||||
fmt.Println(`Select "Cluster: local | Namespace: vela-system | Kind: Service | Name: velaux" from the prompt.`)
|
||||
fmt.Println(`Please refer to https://kubevela.io/docs/reference/addons/velaux for more VelaUX addon installation and visiting method.`)
|
||||
}
|
||||
}
|
||||
@@ -226,7 +220,7 @@ func NewAddonUpgradeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Co
|
||||
Upgrade addon by:
|
||||
vela addon upgrade <addon-name>
|
||||
Upgrade addon with specify version:
|
||||
vela addon upgrade <addon-name> --version <addon-version>
|
||||
vela addon upgrade <addon-name> --version <addon-version>
|
||||
Upgrade addon for specific clusters, (local means control plane):
|
||||
vela addon upgrade <addon-name> --clusters={local,cluster1,cluster2}
|
||||
`,
|
||||
@@ -284,7 +278,7 @@ Upgrade addon for specific clusters, (local means control plane):
|
||||
}
|
||||
|
||||
fmt.Printf("Addon: %s\n enabled Successfully.", name)
|
||||
AdditionalEndpointPrinter(ctx, c, k8sClient, name)
|
||||
AdditionalEndpointPrinter(ctx, c, k8sClient, name, true)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -295,7 +289,7 @@ Upgrade addon for specific clusters, (local means control plane):
|
||||
func parseAddonArgsToMap(args []string) (map[string]interface{}, error) {
|
||||
res := map[string]interface{}{}
|
||||
for _, arg := range args {
|
||||
if err := strvals.ParseIntoString(arg, res); err != nil {
|
||||
if err := strvals.ParseInto(arg, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -449,15 +443,15 @@ func generateAddonInfo(name string, status pkgaddon.Status) string {
|
||||
return res
|
||||
}
|
||||
|
||||
func listAddons(ctx context.Context, clt client.Client, registry string) error {
|
||||
func listAddons(ctx context.Context, clt client.Client, registry string) (*uitable.Table, error) {
|
||||
var addons []*pkgaddon.UIData
|
||||
var err error
|
||||
registryDS := pkgaddon.NewRegistryDataStore(clt)
|
||||
registries, err := registryDS.ListRegistries(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
onlineAddon := map[string]bool{}
|
||||
|
||||
for _, r := range registries {
|
||||
if registry != "" && r.Name != registry {
|
||||
continue
|
||||
@@ -486,31 +480,38 @@ func listAddons(ctx context.Context, clt client.Client, registry string) error {
|
||||
table := uitable.New()
|
||||
table.AddRow("NAME", "REGISTRY", "DESCRIPTION", "AVAILABLE-VERSIONS", "STATUS")
|
||||
|
||||
// get locally installed addons first
|
||||
locallyInstalledAddons := map[string]bool{}
|
||||
appList := v1beta1.ApplicationList{}
|
||||
if err := clt.List(ctx, &appList, client.MatchingLabels{oam.LabelAddonRegistry: pkgaddon.LocalAddonRegistryName}); err != nil {
|
||||
return table, err
|
||||
}
|
||||
for _, app := range appList.Items {
|
||||
labels := app.GetLabels()
|
||||
addonName := labels[oam.LabelAddonName]
|
||||
addonVersion := labels[oam.LabelAddonVersion]
|
||||
table.AddRow(addonName, app.GetLabels()[oam.LabelAddonRegistry], "", genAvailableVersionInfo([]string{addonVersion}, addonVersion), statusEnabled)
|
||||
locallyInstalledAddons[addonName] = true
|
||||
}
|
||||
|
||||
for _, addon := range addons {
|
||||
// if the addon with same name has already installed locally, display the registry one as not installed
|
||||
if locallyInstalledAddons[addon.Name] {
|
||||
table.AddRow(addon.Name, addon.RegistryName, addon.Description, addon.AvailableVersions, "disabled")
|
||||
continue
|
||||
}
|
||||
status, err := pkgaddon.GetAddonStatus(ctx, clt, addon.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
return table, err
|
||||
}
|
||||
statusRow := status.AddonPhase
|
||||
if len(status.InstalledVersion) != 0 {
|
||||
statusRow += fmt.Sprintf(" (%s)", status.InstalledVersion)
|
||||
}
|
||||
table.AddRow(addon.Name, addon.RegistryName, addon.Description, genAvailableVersionInfo(addon.AvailableVersions, status), statusRow)
|
||||
onlineAddon[addon.Name] = true
|
||||
table.AddRow(addon.Name, addon.RegistryName, addon.Description, genAvailableVersionInfo(addon.AvailableVersions, status.InstalledVersion), statusRow)
|
||||
}
|
||||
appList := v1alpha2.ApplicationList{}
|
||||
if err := clt.List(ctx, &appList, client.MatchingLabels{oam.LabelAddonRegistry: pkgaddon.LocalAddonRegistryName}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, app := range appList.Items {
|
||||
addonName := app.GetLabels()[oam.LabelAddonName]
|
||||
if onlineAddon[addonName] {
|
||||
continue
|
||||
}
|
||||
table.AddRow(addonName, app.GetLabels()[oam.LabelAddonRegistry], "", statusEnabled)
|
||||
}
|
||||
fmt.Println(table.String())
|
||||
return nil
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func waitApplicationRunning(k8sClient client.Client, addonName string) error {
|
||||
@@ -546,13 +547,13 @@ func waitApplicationRunning(k8sClient client.Client, addonName string) error {
|
||||
// generate the available version
|
||||
// this func put the installed version as the first version and keep the origin order
|
||||
// print ... if available version too much
|
||||
func genAvailableVersionInfo(versions []string, status pkgaddon.Status) string {
|
||||
func genAvailableVersionInfo(versions []string, installedVersion string) string {
|
||||
var v []string
|
||||
|
||||
// put installed-version as the first version and keep the origin order
|
||||
if len(status.InstalledVersion) != 0 {
|
||||
if len(installedVersion) != 0 {
|
||||
for i, version := range versions {
|
||||
if version == status.InstalledVersion {
|
||||
if version == installedVersion {
|
||||
v = append(v, version)
|
||||
versions = append(versions[:i], versions[i+1:]...)
|
||||
}
|
||||
@@ -568,7 +569,7 @@ func genAvailableVersionInfo(versions []string, status pkgaddon.Status) string {
|
||||
res += "..."
|
||||
break
|
||||
}
|
||||
if version == status.InstalledVersion {
|
||||
if version == installedVersion {
|
||||
col := color.New(color.Bold, color.FgGreen)
|
||||
res += col.Sprintf("%s", version)
|
||||
} else {
|
||||
|
||||
125
references/cli/addon_suite_test.go
Normal file
125
references/cli/addon_suite_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
pkgaddon "github.com/oam-dev/kubevela/pkg/addon"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
)
|
||||
|
||||
var _ = Describe("Output of listing addons tests", func() {
|
||||
// Output of function listAddons to test
|
||||
var actualTable *uitable.Table
|
||||
|
||||
// getRowsByName extracts every rows with its NAME matching name
|
||||
getRowsByName := func(name string) []*uitable.Row {
|
||||
matchedRows := []*uitable.Row{}
|
||||
for _, row := range actualTable.Rows {
|
||||
// Check column NAME(0) = name
|
||||
if row.Cells[0].Data == name {
|
||||
matchedRows = append(matchedRows, row)
|
||||
}
|
||||
}
|
||||
return matchedRows
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
// Prepare KubeVela registry
|
||||
reg := &pkgaddon.Registry{
|
||||
Name: "KubeVela",
|
||||
Helm: &pkgaddon.HelmSource{
|
||||
URL: "https://addons.kubevela.net",
|
||||
},
|
||||
}
|
||||
ds := pkgaddon.NewRegistryDataStore(k8sClient)
|
||||
Expect(ds.AddRegistry(context.Background(), *reg)).To(Succeed())
|
||||
})
|
||||
|
||||
JustBeforeEach(func() {
|
||||
// Print addon list to table for later comparison
|
||||
ret, err := listAddons(context.Background(), k8sClient, "")
|
||||
Expect(err).Should(BeNil())
|
||||
actualTable = ret
|
||||
})
|
||||
|
||||
When("there is no addons installed", func() {
|
||||
It("should not have any enabled addon", func() {
|
||||
Expect(actualTable.Rows).ToNot(HaveLen(0))
|
||||
for idx, row := range actualTable.Rows {
|
||||
// Skip header
|
||||
if idx == 0 {
|
||||
continue
|
||||
}
|
||||
// Check column STATUS(4) = disabled
|
||||
Expect(row.Cells[4].Data).To(Equal("disabled"))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
When("there is locally installed addons", func() {
|
||||
BeforeEach(func() {
|
||||
// Install fluxcd locally
|
||||
fluxcd := v1beta1.Application{}
|
||||
err := yaml.Unmarshal([]byte(fluxcdYaml), &fluxcd)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.Background(), &fluxcd)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("should print fluxcd addon as local", func() {
|
||||
matchedRows := getRowsByName("fluxcd")
|
||||
Expect(matchedRows).ToNot(HaveLen(0))
|
||||
// Only use first row (local first), check column REGISTRY(1) = local
|
||||
Expect(matchedRows[0].Cells[1].Data).To(Equal("local"))
|
||||
Eventually(func() error {
|
||||
matchedRows = getRowsByName("fluxcd")
|
||||
// Check column STATUS(4) = enabled
|
||||
if matchedRows[0].Cells[4].Data != "enabled" {
|
||||
return fmt.Errorf("fluxcd is not enabled yet")
|
||||
}
|
||||
// Check column AVAILABLE-VERSIONS(3) = 1.1.0
|
||||
if versionString := matchedRows[0].Cells[3].Data; versionString != fmt.Sprintf("[%s]", color.New(color.Bold, color.FgGreen).Sprintf("1.1.0")) {
|
||||
return fmt.Errorf("fluxcd version string is incorrect: %s", versionString)
|
||||
}
|
||||
return nil
|
||||
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
})
|
||||
|
||||
It("should print fluxcd in the registry as disabled", func() {
|
||||
matchedRows := getRowsByName("fluxcd")
|
||||
// There should be a local one and a registry one
|
||||
Expect(len(matchedRows)).To(Equal(2))
|
||||
// The registry one should be disabled
|
||||
Expect(matchedRows[1].Cells[1].Data).To(Equal("KubeVela"))
|
||||
Expect(matchedRows[1].Cells[4].Data).To(Equal("disabled"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -70,6 +70,20 @@ func TestParseMap(t *testing.T) {
|
||||
},
|
||||
nilError: true,
|
||||
},
|
||||
{
|
||||
args: []string{"local=true"},
|
||||
res: map[string]interface{}{
|
||||
"local": true,
|
||||
},
|
||||
nilError: true,
|
||||
},
|
||||
{
|
||||
args: []string{"replicas=3"},
|
||||
res: map[string]interface{}{
|
||||
"replicas": int64(3),
|
||||
},
|
||||
nilError: true,
|
||||
},
|
||||
}
|
||||
for _, s := range testcase {
|
||||
r, err := parseAddonArgsToMap(s.args)
|
||||
@@ -211,7 +225,7 @@ func TestGenerateAvailableVersions(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, s := range testcases {
|
||||
re := genAvailableVersionInfo(s.c.versions, pkgaddon.Status{InstalledVersion: s.c.inVersion})
|
||||
re := genAvailableVersionInfo(s.c.versions, s.c.inVersion)
|
||||
assert.Equal(t, re, s.res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, p
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Workload: commontype.WorkloadTypeDescriptor{
|
||||
Definition: commontype.WorkloadGVK{
|
||||
APIVersion: "terraform.core.oam.dev/v1beta1",
|
||||
APIVersion: "terraform.core.oam.dev/v1beta2",
|
||||
Kind: "Configuration",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -300,7 +300,7 @@ spec:
|
||||
namespace: default
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: terraform.core.oam.dev/v1beta1
|
||||
apiVersion: terraform.core.oam.dev/v1beta2
|
||||
kind: Configuration
|
||||
status: {}
|
||||
`,
|
||||
@@ -327,7 +327,7 @@ spec:
|
||||
type: remote
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: terraform.core.oam.dev/v1beta1
|
||||
apiVersion: terraform.core.oam.dev/v1beta2
|
||||
kind: Configuration
|
||||
status: {}`,
|
||||
},
|
||||
|
||||
@@ -27,27 +27,22 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
coreapi "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/plugins"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// ProviderNamespace is the namespace of Terraform Cloud Provider
|
||||
ProviderNamespace = "default"
|
||||
|
||||
providerNameParam = "name"
|
||||
providerNameParam = "name"
|
||||
errAuthenticateProvider = "failed to authenticate Terraform cloud provider %s"
|
||||
)
|
||||
|
||||
// NewProviderCommand create `addon` command
|
||||
@@ -193,38 +188,14 @@ func prepareProviderAddSubCommand(c common.Args, ioStreams cmdutil.IOStreams) ([
|
||||
}
|
||||
data, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
|
||||
return fmt.Errorf(errAuthenticateProvider, providerType)
|
||||
}
|
||||
providerAppName := fmt.Sprintf("config-terraform-provider-%s", name)
|
||||
a := &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: providerAppName}, a); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
a = &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: providerAppName,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []coreapi.ApplicationComponent{
|
||||
{
|
||||
Name: providerAppName,
|
||||
Type: providerType,
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: data,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := k8sClient.Create(ctx, a); err != nil {
|
||||
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
|
||||
}
|
||||
ioStreams.Infof("Successfully authentiate provider %s for %s\n", name, providerType)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
|
||||
|
||||
if err := config.CreateApplication(ctx, k8sClient, name, providerType, string(data), config.UIParam{}); err != nil {
|
||||
return fmt.Errorf(errAuthenticateProvider, providerType)
|
||||
}
|
||||
return fmt.Errorf("terraform provider %s for %s already exists", name, providerType)
|
||||
ioStreams.Infof("Successfully authenticate provider %s for %s\n", name, providerType)
|
||||
return nil
|
||||
}
|
||||
cmds[i] = cmd
|
||||
}
|
||||
@@ -259,14 +230,14 @@ func listProviders(ctx context.Context, k8sClient client.Client, ioStreams cmdut
|
||||
currentProviders []tcv1beta1.Provider
|
||||
legacyProviders []tcv1beta1.Provider
|
||||
)
|
||||
tcProviders := &tcv1beta1.ProviderList{}
|
||||
// client.MatchingLabels{: }
|
||||
if err := k8sClient.List(ctx, tcProviders, client.InNamespace(ProviderNamespace)); err != nil {
|
||||
l, err := config.ListTerraformProviders(ctx, k8sClient)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve providers")
|
||||
}
|
||||
|
||||
for _, p := range tcProviders.Items {
|
||||
if p.Labels["config.oam.dev/type"] == types.TerraformProvider {
|
||||
for _, p := range l {
|
||||
// The first condition matches the providers created by `vela provider` in 1.3.2 and earlier.
|
||||
if p.Labels[types.LabelConfigType] == types.TerraformProvider || p.Labels[types.LabelConfigCatalog] == types.VelaCoreConfig {
|
||||
currentProviders = append(currentProviders, p)
|
||||
} else {
|
||||
// if not labeled, the provider is manually created or created by `vela addon enable`.
|
||||
@@ -429,15 +400,8 @@ func prepareProviderDeleteSubCommand(c common.Args, ioStreams cmdutil.IOStreams)
|
||||
if err != nil || name == "" {
|
||||
return fmt.Errorf("must specify a name for the Terraform Cloud Provider %s", providerType)
|
||||
}
|
||||
providerAppName := fmt.Sprintf("config-terraform-provider-%s", name)
|
||||
a := &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: providerAppName}, a); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return fmt.Errorf("provider %s for %s does not exist", name, providerType)
|
||||
}
|
||||
}
|
||||
if err := k8sClient.Delete(ctx, a); err != nil {
|
||||
return err
|
||||
if err := config.DeleteApplication(ctx, k8sClient, name, true); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete Terraform Cloud Provider %s", name)
|
||||
}
|
||||
ioStreams.Infof("Successfully delete provider %s for %s\n", name, providerType)
|
||||
return nil
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
func TestLlistProviders(t *testing.T) {
|
||||
func TestListProviders(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
type args struct {
|
||||
k8sClient client.Client
|
||||
|
||||
@@ -124,7 +124,7 @@ func NewAppStatusCommand(c common.Args, order string, ioStreams cmdutil.IOStream
|
||||
f := Filter{
|
||||
Component: component,
|
||||
}
|
||||
return printAppEndpoints(ctx, newClient, appName, namespace, f, c)
|
||||
return printAppEndpoints(ctx, appName, namespace, f, c)
|
||||
}
|
||||
return printAppStatus(ctx, newClient, ioStreams, appName, namespace, cmd, c)
|
||||
},
|
||||
@@ -163,7 +163,15 @@ func printAppStatus(_ context.Context, c client.Client, ioStreams cmdutil.IOStre
|
||||
return loopCheckStatus(c, ioStreams, appName, namespace)
|
||||
}
|
||||
|
||||
func printAppEndpoints(ctx context.Context, client client.Client, appName string, namespace string, f Filter, velaC common.Args) error {
|
||||
func printAppEndpoints(ctx context.Context, appName string, namespace string, f Filter, velaC common.Args) error {
|
||||
config, err := velaC.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := multicluster.Initialize(config, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoints, err := GetServiceEndpoints(ctx, client, appName, namespace, velaC, f)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -172,6 +180,9 @@ func printAppEndpoints(ctx context.Context, client client.Client, appName string
|
||||
table.SetColWidth(100)
|
||||
table.SetHeader([]string{"Cluster", "Component", "Ref(Kind/Namespace/Name)", "Endpoint"})
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Cluster == "" {
|
||||
endpoint.Cluster = multicluster.ClusterLocalName
|
||||
}
|
||||
table.Append([]string{endpoint.Cluster, endpoint.Component, fmt.Sprintf("%s/%s/%s", endpoint.Ref.Kind, endpoint.Ref.Namespace, endpoint.Ref.Name), endpoint.String()})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
@@ -237,7 +237,7 @@ func PrintInstalledTraitDef(c common2.Args, io cmdutil.IOStreams, filter filterF
|
||||
}
|
||||
capa, err := ParseCapability(dm, data)
|
||||
if err != nil {
|
||||
io.Errorf("error parsing capability: %s\n", td.Name)
|
||||
io.Errorf("error parsing capability: %s (message: %s)\n", td.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
if filter != nil && !filter(capa) {
|
||||
|
||||
@@ -17,11 +17,22 @@ limitations under the License.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
util2 "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/common"
|
||||
)
|
||||
|
||||
@@ -43,3 +54,170 @@ func TestTraitsAppliedToAllWorkloads(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, []string{"*"}, common.ConvertApplyTo(trait.AppliesTo, workloads))
|
||||
}
|
||||
|
||||
var _ = Describe("Test trait cli", func() {
|
||||
|
||||
When("there are container-image and configmap traits", func() {
|
||||
BeforeEach(func() {
|
||||
// Install trait locally
|
||||
containerImage := v1beta1.TraitDefinition{}
|
||||
Expect(yaml.Unmarshal([]byte(containerImageYaml), &containerImage)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.Background(), &containerImage)).Should(SatisfyAny(BeNil(), util2.AlreadyExistMatcher{}))
|
||||
|
||||
configMap := v1beta1.TraitDefinition{}
|
||||
Expect(yaml.Unmarshal([]byte(configmapYaml), &configMap)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.Background(), &configMap)).Should(SatisfyAny(BeNil(), util2.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("should not have any err", func() {
|
||||
arg := common2.Args{}
|
||||
arg.SetClient(k8sClient)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
ioStreams := util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer}
|
||||
cmd := NewTraitCommand(arg, ioStreams)
|
||||
Expect(cmd.Execute()).Should(BeNil())
|
||||
buf, ok := ioStreams.Out.(*bytes.Buffer)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(strings.Contains(buf.String(), "error")).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const (
|
||||
containerImageYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Set the image of the container.
|
||||
name: container-image
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
podDisruptive: true
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
#PatchParams: {
|
||||
// +usage=Specify the name of the target container, if not set, use the component name
|
||||
containerName: *"" | string
|
||||
// +usage=Specify the image of the container
|
||||
image: string
|
||||
// +usage=Specify the image pull policy of the container
|
||||
imagePullPolicy: *"" | "IfNotPresent" | "Always" | "Never"
|
||||
}
|
||||
PatchContainer: {
|
||||
_params: #PatchParams
|
||||
name: _params.containerName
|
||||
_baseContainers: context.output.spec.template.spec.containers
|
||||
_matchContainers_: [ for _container_ in _baseContainers if _container_.name == name {_container_}]
|
||||
_baseContainer: *_|_ | {...}
|
||||
if len(_matchContainers_) == 0 {
|
||||
err: "container \(name) not found"
|
||||
}
|
||||
if len(_matchContainers_) > 0 {
|
||||
// +patchStrategy=retainKeys
|
||||
image: _params.image
|
||||
|
||||
if _params.imagePullPolicy != "" {
|
||||
// +patchStrategy=retainKeys
|
||||
imagePullPolicy: _params.imagePullPolicy
|
||||
}
|
||||
}
|
||||
}
|
||||
patch: spec: template: spec: {
|
||||
if parameter.containers == _|_ {
|
||||
// +patchKey=name
|
||||
containers: [{
|
||||
PatchContainer & {_params: {
|
||||
if parameter.containerName == "" {
|
||||
containerName: context.name
|
||||
}
|
||||
if parameter.containerName != "" {
|
||||
containerName: parameter.containerName
|
||||
}
|
||||
image: parameter.image
|
||||
imagePullPolicy: parameter.imagePullPolicy
|
||||
}}
|
||||
}]
|
||||
}
|
||||
if parameter.containers != _|_ {
|
||||
// +patchKey=name
|
||||
containers: [ for c in parameter.containers {
|
||||
if c.containerName == "" {
|
||||
err: "containerName must be set for containers"
|
||||
}
|
||||
if c.containerName != "" {
|
||||
PatchContainer & {_params: c}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
parameter: *#PatchParams | close({
|
||||
// +usage=Specify the container image for multiple containers
|
||||
containers: [...#PatchParams]
|
||||
})
|
||||
errs: [ for c in patch.spec.template.spec.containers if c.err != _|_ {c.err}]
|
||||
`
|
||||
configmapYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Create/Attach configmaps on K8s pod for your workload which follows the pod spec in path 'spec.template'. This definition is DEPRECATED, please specify configmap in 'storage' instead.
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
name: configmap
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- '*'
|
||||
podDisruptive: true
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
patch: spec: template: spec: {
|
||||
containers: [{
|
||||
// +patchKey=name
|
||||
volumeMounts: [
|
||||
for v in parameter.volumes {
|
||||
{
|
||||
name: "volume-\(v.name)"
|
||||
mountPath: v.mountPath
|
||||
readOnly: v.readOnly
|
||||
}
|
||||
},
|
||||
]
|
||||
}, ...]
|
||||
// +patchKey=name
|
||||
volumes: [
|
||||
for v in parameter.volumes {
|
||||
{
|
||||
name: "volume-\(v.name)"
|
||||
configMap: name: v.name
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
outputs: {
|
||||
for v in parameter.volumes {
|
||||
if v.data != _|_ {
|
||||
"\(v.name)": {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: name: v.name
|
||||
data: v.data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify mounted configmap names and their mount paths in the container
|
||||
volumes: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
readOnly: *false | bool
|
||||
data?: [string]: string
|
||||
}]
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
@@ -70,7 +70,9 @@ metadata:
|
||||
name: addon-fluxcd
|
||||
namespace: vela-system
|
||||
labels:
|
||||
addons.oam.dev/name: fluxcd
|
||||
addons.oam.dev/name: fluxcd
|
||||
addons.oam.dev/registry: local
|
||||
addons.oam.dev/version: 1.1.0
|
||||
spec:
|
||||
components:
|
||||
- name: ns-flux-system
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -33,6 +32,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
|
||||
"github.com/oam-dev/kubevela/pkg/component"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user