mirror of https://github.com/kubevela/kubevela.git
Feat: support to manage the configs by the CLI, UI, and workflow. (#4794)
* Feat: support to manage the integrations by the CLI and the workflow Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: remove the xml Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: add the unit test for the nacos writer Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add the integration API Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: make the provider commands to be deprecated Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: make the unit test work Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: rename the integration to the config Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: make the unit test cases work Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: refactor the config commands Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add the distribution status for the config Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: sort the import packages Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: refine the code style Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: refine the code style Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: get the content format before render the content Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add some examples Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: the command test cases Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add the definitions of the workflow step Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: add some tests Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: add some tests Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: change the name Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: retry the CI Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: refine some words Signed-off-by: barnettZQG <barnett.zqg@gmail.com> Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
This commit is contained in:
parent
5a4bdd4f6e
commit
49ed837f97
|
|
@ -74,9 +74,9 @@ const (
|
|||
AnnoIngressControllerHTTPPort = "ingress.controller/http-port"
|
||||
// AnnoIngressControllerHost define ingress controller externally host
|
||||
AnnoIngressControllerHost = "ingress.controller/host"
|
||||
// LabelConfigType is the label for config type
|
||||
// LabelConfigType is the label marked as the template that generated the config.
|
||||
LabelConfigType = "config.oam.dev/type"
|
||||
// LabelConfigCatalog is the label for config catalog
|
||||
// LabelConfigCatalog is the label marked as the secret generated from the config.
|
||||
LabelConfigCatalog = "config.oam.dev/catalog"
|
||||
// LabelConfigSubType is the sub-type for a config type
|
||||
LabelConfigSubType = "config.oam.dev/sub-type"
|
||||
|
|
@ -86,10 +86,18 @@ const (
|
|||
LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster"
|
||||
// LabelConfigIdentifier is the label for config identifier
|
||||
LabelConfigIdentifier = "config.oam.dev/identifier"
|
||||
// LabelConfigScope is the label for config scope
|
||||
LabelConfigScope = "config.oam.dev/scope"
|
||||
// AnnotationConfigSensitive is the annotation for the sensitization
|
||||
AnnotationConfigSensitive = "config.oam.dev/sensitive"
|
||||
// AnnotationConfigTemplateNamespace is the annotation for the template namespace
|
||||
AnnotationConfigTemplateNamespace = "config.oam.dev/template-namespace"
|
||||
// AnnotationConfigDescription is the annotation for config description
|
||||
AnnotationConfigDescription = "config.oam.dev/description"
|
||||
// AnnotationConfigAlias is the annotation for config alias
|
||||
AnnotationConfigAlias = "config.oam.dev/alias"
|
||||
// AnnotationConfigDistributionSpec is the annotation key of the application that distributes the configs
|
||||
AnnotationConfigDistributionSpec = "config.oam.dev/distribution-spec"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -156,11 +164,13 @@ const (
|
|||
// TerraformProvider is the config type for terraform provider
|
||||
TerraformProvider = "terraform-provider"
|
||||
// DexConnector is the config type for dex connector
|
||||
DexConnector = "config-dex-connector"
|
||||
DexConnector = "dex-connector"
|
||||
// ImageRegistry is the config type for image registry
|
||||
ImageRegistry = "config-image-registry"
|
||||
ImageRegistry = "image-registry"
|
||||
// HelmRepository is the config type for Helm chart repository
|
||||
HelmRepository = "config-helm-repository"
|
||||
HelmRepository = "helm-repository"
|
||||
// CatalogConfigDistribution is the catalog type
|
||||
CatalogConfigDistribution = "config-distribution"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/config-image-registry.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
alias.config.oam.dev: Image Registry
|
||||
definition.oam.dev/description: Config information to authenticate image registry
|
||||
labels:
|
||||
catalog.config.oam.dev: velacore-config
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
multi-cluster.config.oam.dev: "true"
|
||||
type.config.oam.dev: image-registry
|
||||
name: config-image-registry
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
labels: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "image-registry"
|
||||
"config.oam.dev/multi-cluster": "true"
|
||||
"config.oam.dev/identifier": parameter.registry
|
||||
"config.oam.dev/sub-type": "auth"
|
||||
}
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".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.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
// +usage=Private Image registry username
|
||||
username: string
|
||||
// +usage=Private Image registry password
|
||||
password: string
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/create-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Create or update a config
|
||||
name: create-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
deploy: op.#CreateConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
if parameter.template != _|_ {
|
||||
template: parameter.template
|
||||
}
|
||||
config: parameter.config
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
|
||||
//+usage=Specify the template of the config.
|
||||
template?: string
|
||||
|
||||
//+usage=Specify the content of the config.
|
||||
config: {...}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/delete-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Delete a config
|
||||
name: delete-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
deploy: op.#DeleteConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/list-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: List the configs
|
||||
name: list-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
output: op.#ListConfig & {
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
template: parameter.template
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the template of the config.
|
||||
template: string
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/read-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Read a config
|
||||
name: read-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
output: op.#ReadConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/create-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Create or update a config
|
||||
name: create-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
deploy: op.#CreateConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
if parameter.template != _|_ {
|
||||
template: parameter.template
|
||||
}
|
||||
config: parameter.config
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
|
||||
//+usage=Specify the template of the config.
|
||||
template?: string
|
||||
|
||||
//+usage=Specify the content of the config.
|
||||
config: {...}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/delete-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Delete a config
|
||||
name: delete-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
deploy: op.#DeleteConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/list-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: List the configs
|
||||
name: list-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
output: op.#ListConfig & {
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
template: parameter.template
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the template of the config.
|
||||
template: string
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/read-config.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Read a config
|
||||
name: read-config
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
output: op.#ReadConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,37 @@
|
|||
# How to config a dex connector
|
||||
|
||||
* Step 1: Make sure there is a dex-connector template
|
||||
|
||||
```bash
|
||||
$ vela config-template list -A
|
||||
NAMESPACE NAME ALIAS SCOPE SENSITIVE CREATED-TIME
|
||||
vela-system dex-connector Dex Connector system false 2022-10-12 23:48:05 +0800 CST
|
||||
vela-system helm-repository Helm Repository project false 2022-10-14 12:04:58 +0800 CST
|
||||
vela-system image-registry Image Registry project false 2022-10-13 15:39:37 +0800 CST
|
||||
|
||||
# View the document of the properties
|
||||
$ vela config-template show dex-connector
|
||||
```
|
||||
|
||||
If not exist, please enable the dex addon firstly.
|
||||
|
||||
* Step 2: Create a config
|
||||
|
||||
```bash
|
||||
# Create a connector config
|
||||
$ vela config create github-oauth --template dex-connector type=github github.clientID=*** github.clientSecret=*** github.redirectURI=***
|
||||
```
|
||||
|
||||
Write a yaml file to create the config.
|
||||
|
||||
```bash
|
||||
$ cat>github.yaml<<EOF
|
||||
|
||||
github:
|
||||
clientID: ***
|
||||
clientSecret: ***
|
||||
redirectURI: ***
|
||||
EOF
|
||||
|
||||
$ vela config create github-oauth --template dex-connector type=github -f github.yaml
|
||||
```
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: config-dex-connector-dev
|
||||
namespace: vela-system
|
||||
labels:
|
||||
"app.oam.dev/source-of-truth": "from-inner-system"
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "config-dex-connector"
|
||||
"config.oam.dev/sub-type": "github"
|
||||
project: abc
|
||||
spec:
|
||||
components:
|
||||
- name: dev
|
||||
type: config-dex-connector
|
||||
properties:
|
||||
type: github
|
||||
github:
|
||||
clientID: "aa"
|
||||
clientSecret: "bb"
|
||||
redirectURI: "http://localhost:8080/callback"
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# How to config a helm repository
|
||||
|
||||
* Step 1: Make sure there is a helm-repository template
|
||||
|
||||
```bash
|
||||
$ vela config-template list -A
|
||||
NAMESPACE NAME ALIAS SCOPE SENSITIVE CREATED-TIME
|
||||
vela-system dex-connector Dex Connector system false 2022-10-12 23:48:05 +0800 CST
|
||||
vela-system helm-repository Helm Repository project false 2022-10-14 12:04:58 +0800 CST
|
||||
vela-system image-registry Image Registry project false 2022-10-13 15:39:37 +0800 CST
|
||||
|
||||
# View the document of the properties
|
||||
$ vela config-template show helm-repository
|
||||
|
||||
```
|
||||
|
||||
If not exist, please enable the flux addon firstly.
|
||||
|
||||
* Step 2: Create a config and distribute to the developer namespace
|
||||
|
||||
```bash
|
||||
# Create a developer environment(namespace)
|
||||
$ vela env init developer --namespace developer
|
||||
|
||||
# Create a registry config for the chart repository hosted by KubeVela team
|
||||
$ vela config create kubevela-core --template helm-repository --target developer url=https://charts.kubevela.net/core
|
||||
```
|
||||
|
||||
* Step 3: Create a application to use the helm repository
|
||||
|
||||
```bash
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: helm-app
|
||||
namespace: developer
|
||||
spec:
|
||||
components:
|
||||
- name: helm
|
||||
properties:
|
||||
chart: vela-rollout
|
||||
repoType: helm
|
||||
retries: 3
|
||||
secretRef: kubevela-core
|
||||
url: https://charts.kubevela.net/core
|
||||
type: helm
|
||||
```
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
# How to store and use configurations
|
||||
|
||||
## General
|
||||
|
||||
- list all configuration types. Note before vela v1.5, the key is "custom.definition.oam.dev/catalog.config.oam.dev"
|
||||
```shell
|
||||
$ vela components --label catalog.config.oam.dev=velacore-config
|
||||
NAME DEFINITION
|
||||
config-dex-connector autodetects.core.oam.dev
|
||||
config-helm-repository autodetects.core.oam.dev
|
||||
config-image-registry autodetects.core.oam.dev
|
||||
terraform-azure autodetects.core.oam.dev
|
||||
terraform-baidu autodetects.core.oam.dev
|
||||
```
|
||||
|
||||
```json
|
||||
# Get http://127.0.0.1:8000/api/v1/configs
|
||||
|
||||
[
|
||||
{
|
||||
"definitions": [
|
||||
"config-dex-connector"
|
||||
],
|
||||
"name": "Dex Connectors",
|
||||
"type": "dex-connector"
|
||||
},
|
||||
{
|
||||
"definitions": [
|
||||
"config-helm-repository"
|
||||
],
|
||||
"name": "Helm Repository",
|
||||
"type": "helm-repository"
|
||||
},
|
||||
{
|
||||
"definitions": [
|
||||
"config-image-registry"
|
||||
],
|
||||
"name": "Image Registry",
|
||||
"type": "image-registry"
|
||||
},
|
||||
null,
|
||||
{
|
||||
"definitions": [
|
||||
"terraform-baidu"
|
||||
],
|
||||
"name": "Terraform Cloud Provider",
|
||||
"type": "terraform-provider"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- list all configurations
|
||||
|
||||
```shell
|
||||
$ kubectl get secret -n vela-system -l=config.oam.dev/catalog=velacore-config
|
||||
NAME TYPE DATA AGE
|
||||
image-registry-dev kubernetes.io/dockerconfigjson 1 3h51m
|
||||
```
|
||||
|
||||
## Image registry
|
||||
|
||||
- Create a config for an image registry
|
||||
|
||||
```shell
|
||||
$ vela up -f app-config-image-registry-account-auth.yaml
|
||||
Applying an application in vela K8s object format...
|
||||
I0323 10:45:25.347102 85930 apply.go:107] "creating object" name="config-image-registry-account-auth-dev" resource="core.oam.dev/v1beta1, Kind=Application"
|
||||
✅ App has been deployed 🚀🚀🚀
|
||||
Port forward: vela port-forward config-image-registry-account-auth-dev
|
||||
SSH: vela exec config-image-registry-account-auth-dev
|
||||
Logging: vela logs config-image-registry-account-auth-dev
|
||||
App status: vela status config-image-registry-account-auth-dev
|
||||
Endpoint: vela status config-image-registry-account-auth-dev
|
||||
--endpoint%
|
||||
|
||||
|
||||
$ kubectl get secret -n vela-system -l=config.oam.dev/catalog=velacore-config
|
||||
NAME TYPE DATA AGE
|
||||
image-registry-dev kubernetes.io/dockerconfigjson 1 77s
|
||||
```
|
||||
|
||||
- Deliver the config secret to working cluster
|
||||
|
||||
```shell
|
||||
$ vela cluster list
|
||||
CLUSTER TYPE ENDPOINT ACCEPTED LABELS
|
||||
local Internal - true
|
||||
bj X509Certificate https://123.57.73.107:6443 true
|
||||
|
||||
$ vela up -f app-deliever-secret.yaml
|
||||
```
|
||||
|
||||
- Deploy an application who needs to pull images from the private image registry
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=~/.kube/config-bj
|
||||
$ kubectl get secret -n vela-system -l=config.oam.dev/catalog=velacore-config
|
||||
NAME TYPE DATA AGE
|
||||
image-registry-dev kubernetes.io/dockerconfigjson 1 120s
|
||||
|
||||
$ vela up -f app-validate-imagePullSecret.yaml
|
||||
```
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: image-dev
|
||||
namespace: vela-system
|
||||
labels:
|
||||
"app.oam.dev/source-of-truth": "from-inner-system"
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "config-image-registry"
|
||||
project: abc
|
||||
spec:
|
||||
components:
|
||||
- name: image-dev
|
||||
type: config-image-registry
|
||||
properties:
|
||||
registry: "registry.cn-beijing.aliyuncs.com"
|
||||
auth:
|
||||
username: "xxx"
|
||||
password: "PfwrjwifjFaked"
|
||||
email: "a@gmail.com"
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: config-project1
|
||||
namespace: vela-system
|
||||
labels:
|
||||
config.oam.dev/catalog: "velacore-config"
|
||||
config.oam.dev/type: "helm-repository"
|
||||
spec:
|
||||
components:
|
||||
- name: deliver-secret
|
||||
type: ref-objects
|
||||
properties:
|
||||
objects:
|
||||
- name: reg-demo
|
||||
resource: secret
|
||||
policies:
|
||||
- type: topology
|
||||
name: dev
|
||||
properties:
|
||||
clusters: ["bj"]
|
||||
namespace: default
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-sample
|
||||
namespace: ns1
|
||||
spec:
|
||||
components:
|
||||
- name: sample
|
||||
type: webservice
|
||||
properties:
|
||||
image: registry.cn-beijing.aliyuncs.com/vela/nginx:latest
|
||||
imagePullPolicy: Always
|
||||
imagePullSecrets:
|
||||
- image-registry-dev
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-validate-image-pull-secret
|
||||
namespace: vela-system
|
||||
spec:
|
||||
components:
|
||||
- name: validate
|
||||
type: webservice
|
||||
properties:
|
||||
image: registry.cn-beijing.aliyuncs.com/vela/nginx:latest
|
||||
imagePullPolicy: Always
|
||||
imagePullSecrets:
|
||||
- image-registry-dev
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# How to write the config to Nacos server
|
||||
|
||||
* Step 1: Make sure there are a nacos-server and nacos-config templates
|
||||
|
||||
```bash
|
||||
$ vela config-template list -A
|
||||
NAMESPACE NAME ALIAS SCOPE SENSITIVE CREATED-TIME
|
||||
vela-system nacos-config Nacos Configuration system false 2022-10-13 15:39:44 +0800 CST
|
||||
vela-system nacos-server Nacos Server system false 2022-10-13 15:39:47 +0800 CST
|
||||
|
||||
# View the document of the properties
|
||||
$ vela config-template show nacos-server
|
||||
```
|
||||
|
||||
If not exist, please enable the VelaUX addon firstly.
|
||||
|
||||
* Step 2: Create a config to added a Nacos server
|
||||
|
||||
```bash
|
||||
|
||||
# Create a nacos server config, the config name must be "nacos"
|
||||
$ vela config create nacos --template nacos-server servers[0].ipAddr=127.0.0.1 servers[0].port=8849
|
||||
```
|
||||
|
||||
* Step 3: Create a config to the Nacos server
|
||||
|
||||
```bash
|
||||
# Use the default template, you could define custom template.
|
||||
$ vela config create db-config --template nacos-config dataId=db group="DEFAULT_GROUP" contentType="properties" content.host=127.0.0.1 content.port=3306 content.username=root
|
||||
```
|
||||
|
||||
Then, the content will be written to the Nacos server.
|
||||
|
||||
```properties
|
||||
host = 127.0.0.1
|
||||
port = 3306
|
||||
username = root
|
||||
```
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# How to config a private registry
|
||||
|
||||
* Step 1: Make sure there is a image-registry template
|
||||
|
||||
```bash
|
||||
$ vela config-template list -A
|
||||
NAMESPACE NAME ALIAS SCOPE SENSITIVE CREATED-TIME
|
||||
vela-system dex-connector Dex Connector system false 2022-10-12 23:48:05 +0800 CST
|
||||
vela-system helm-repository Helm Repository project false 2022-10-14 12:04:58 +0800 CST
|
||||
vela-system image-registry Image Registry project false 2022-10-13 15:39:37 +0800 CST
|
||||
|
||||
# View the document of the properties
|
||||
$ vela config-template show image-registry
|
||||
```
|
||||
|
||||
If not exist, please enable the VelaUX addon firstly.
|
||||
|
||||
* Step 2: Create a config and distribute to the developer namespace
|
||||
|
||||
```bash
|
||||
# Create a developer environment(namespace)
|
||||
$ vela env init developer --namespace developer
|
||||
|
||||
# Create a registry config for the docker hub, you could change the username and password
|
||||
$ vela config create private-demo --template image-registry --target developer registry=index.docker.io auth.username=demo auth.password=demo
|
||||
```
|
||||
|
||||
* Step 3: Create a application to use the private registry.
|
||||
|
||||
```bash
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: private-image
|
||||
namespace: developer
|
||||
spec:
|
||||
components:
|
||||
- name: private-image
|
||||
properties:
|
||||
cpu: "0.5"
|
||||
image: private/nginx
|
||||
imagePullSecrets:
|
||||
- private-demo
|
||||
type: webservice
|
||||
```
|
||||
20
go.mod
20
go.mod
|
|
@ -41,6 +41,7 @@ require (
|
|||
github.com/go-openapi/spec v0.19.8
|
||||
github.com/go-playground/validator/v10 v10.9.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/go-containerregistry v0.9.0
|
||||
github.com/google/go-github/v32 v32.1.0
|
||||
|
|
@ -68,7 +69,7 @@ require (
|
|||
github.com/onsi/gomega v1.20.2
|
||||
github.com/openkruise/kruise-api v1.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
|
|
@ -83,7 +84,7 @@ require (
|
|||
github.com/xanzy/go-gitlab v0.60.0
|
||||
github.com/xlab/treeprint v1.1.0
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.19.1
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
|
|
@ -145,12 +146,14 @@ require (
|
|||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.0 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
|
||||
github.com/aliyun/credentials-go v1.1.2 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/aws/aws-sdk-go v1.36.30 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.5.5 // indirect
|
||||
|
|
@ -247,7 +250,6 @@ require (
|
|||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/openshift/library-go v0.0.0-20220112153822-ac82336bd076 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect
|
||||
|
|
@ -295,21 +297,21 @@ require (
|
|||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/gorp.v1 v1.7.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
istio.io/api v0.0.0-20220512212136-561ffec82582 // indirect
|
||||
istio.io/gogo-genproto v0.0.0-20211208193508-5ab4acc9eb1e // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
|
|
@ -328,10 +330,14 @@ require (
|
|||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/magiconair/properties v1.8.5
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.1.0
|
||||
github.com/opencontainers/runc v1.1.3 // indirect
|
||||
github.com/openkruise/rollouts v0.1.1-0.20220622054609-149e5a48da5e
|
||||
github.com/pelletier/go-toml v1.9.4
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
replace (
|
||||
|
|
|
|||
30
go.sum
30
go.sum
|
|
@ -253,6 +253,8 @@ github.com/alibabacloud-go/tea-utils/v2 v2.0.0 h1:s3XRBCDVHBQ42ck4xnLGcWgRMDf9v4
|
|||
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
|
|
@ -342,6 +344,8 @@ github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR
|
|||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
|
||||
|
|
@ -910,6 +914,7 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
|||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
|
|
@ -932,6 +937,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
|
@ -1258,6 +1264,7 @@ github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOv
|
|||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
|
@ -1369,6 +1376,7 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
|
|||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
|
|
@ -1525,6 +1533,8 @@ github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod
|
|||
github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.1.0 h1:PxRwOzHhnK6eGGvioEGkn8s6XRXmUVuXu91i2yQcdDs=
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.1.0/go.mod h1:ys/1adWeKXXzbNWfRNbaFlX/t6HVLWdpsNDvmoWTw0g=
|
||||
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
|
|
@ -1707,8 +1717,9 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O
|
|||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
|
||||
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
|
|
@ -2120,7 +2131,7 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADbpZtnU=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
|
|
@ -2136,8 +2147,8 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
|||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
|
@ -2495,6 +2506,7 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
@ -2537,8 +2549,8 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
@ -2882,8 +2894,9 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5
|
|||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
|
||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
|
@ -2930,8 +2943,9 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
|
||||
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/google/go-github/v32/github"
|
||||
"github.com/imdario/mergo"
|
||||
|
|
@ -62,7 +61,8 @@ import (
|
|||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
utils2 "github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
|
@ -100,6 +100,9 @@ const (
|
|||
// DefinitionsDirName is the addon definitions/ dir name
|
||||
DefinitionsDirName string = "definitions"
|
||||
|
||||
// ConfigTemplateDirName is the addon config-templates/ dir name
|
||||
ConfigTemplateDirName string = "config-templates"
|
||||
|
||||
// DefSchemaName is the addon definition schemas dir name
|
||||
DefSchemaName string = "schemas"
|
||||
|
||||
|
|
@ -118,17 +121,18 @@ var ParameterFileName = strings.Join([]string{"resources", "parameter.cue"}, "/"
|
|||
|
||||
// ListOptions contains flags mark what files should be read in an addon directory
|
||||
type ListOptions struct {
|
||||
GetDetail bool
|
||||
GetDefinition bool
|
||||
GetResource bool
|
||||
GetParameter bool
|
||||
GetTemplate bool
|
||||
GetDefSchema bool
|
||||
GetDetail bool
|
||||
GetDefinition bool
|
||||
GetConfigTemplate bool
|
||||
GetResource bool
|
||||
GetParameter bool
|
||||
GetTemplate bool
|
||||
GetDefSchema bool
|
||||
}
|
||||
|
||||
var (
|
||||
// UIMetaOptions get Addon metadata for UI display
|
||||
UIMetaOptions = ListOptions{GetDetail: true, GetDefinition: true, GetParameter: true}
|
||||
UIMetaOptions = ListOptions{GetDetail: true, GetDefinition: true, GetParameter: true, GetConfigTemplate: true}
|
||||
|
||||
// CLIMetaOptions get Addon metadata for CLI display
|
||||
CLIMetaOptions = ListOptions{}
|
||||
|
|
@ -206,6 +210,7 @@ type Pattern struct {
|
|||
|
||||
// Patterns is the file pattern that the addon should be in
|
||||
var Patterns = []Pattern{
|
||||
{IsDir: true, Value: ConfigTemplateDirName},
|
||||
{Value: ReadmeFileName}, {Value: MetadataFileName}, {Value: TemplateFileName},
|
||||
{Value: ParameterFileName}, {IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName},
|
||||
{IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName}, {Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}, {Value: LegacyReadmeFileName}}
|
||||
|
|
@ -291,6 +296,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID
|
|||
LegacyReadmeFileName: {!opt.GetDetail, readReadme},
|
||||
MetadataFileName: {false, readMetadata},
|
||||
DefinitionsDirName: {!opt.GetDefinition, readDefFile},
|
||||
ConfigTemplateDirName: {!opt.GetConfigTemplate, readConfigTemplateFile},
|
||||
ParameterFileName: {!opt.GetParameter, readParamFile},
|
||||
GlobalParameterFileName: {!opt.GetParameter, readGlobalParamFile},
|
||||
}
|
||||
|
|
@ -318,7 +324,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID
|
|||
}
|
||||
err := genAddonAPISchema(addon)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to generate openAPIschema for addon %s : %w", meta.Name, err)
|
||||
return nil, fmt.Errorf("fail to generate openAPIschema for addon %s : %w (parameter: %s)", meta.Name, err, addon.Parameters)
|
||||
}
|
||||
}
|
||||
addon.AvailableVersions = []string{addon.Version}
|
||||
|
|
@ -338,10 +344,11 @@ func GetInstallPackageFromReader(r AsyncReader, meta *SourceMeta, uiData *UIData
|
|||
|
||||
// Read the installed data from UI metadata object to reduce network payload
|
||||
var addon = &InstallPackage{
|
||||
Meta: uiData.Meta,
|
||||
Definitions: uiData.Definitions,
|
||||
CUEDefinitions: uiData.CUEDefinitions,
|
||||
Parameters: uiData.Parameters,
|
||||
Meta: uiData.Meta,
|
||||
Definitions: uiData.Definitions,
|
||||
CUEDefinitions: uiData.CUEDefinitions,
|
||||
Parameters: uiData.Parameters,
|
||||
ConfigTemplates: uiData.ConfigTemplates,
|
||||
}
|
||||
|
||||
for contentType, method := range addonContentsReader {
|
||||
|
|
@ -454,6 +461,21 @@ func readDefFile(a *UIData, reader AsyncReader, readPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// readConfigTemplateFile read single template file of the config
|
||||
func readConfigTemplateFile(a *UIData, reader AsyncReader, readPath string) error {
|
||||
b, err := reader.ReadFile(readPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := path.Base(readPath)
|
||||
if filepath.Ext(filename) != ".cue" {
|
||||
return nil
|
||||
}
|
||||
file := ElementFile{Data: b, Name: filepath.Base(readPath)}
|
||||
a.ConfigTemplates = append(a.ConfigTemplates, file)
|
||||
return nil
|
||||
}
|
||||
|
||||
// readViewFile read single view file
|
||||
func readViewFile(a *InstallPackage, reader AsyncReader, readPath string) error {
|
||||
b, err := reader.ReadFile(readPath)
|
||||
|
|
@ -590,23 +612,14 @@ func unmarshalToContent(content []byte) (fileContent *github.RepositoryContent,
|
|||
}
|
||||
|
||||
func genAddonAPISchema(addonRes *UIData) error {
|
||||
param, err := utils2.PrepareParameterCue(addonRes.Name, addonRes.Parameters)
|
||||
cueScript, err := script.PrepareTemplateCUEScript([]byte(addonRes.Parameters))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val := cuecontext.New().CompileString(param)
|
||||
schema, err := cueScript.ParsePropertiesToSchema()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := common.GenOpenAPI(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schema, err := utils2.ConvertOpenAPISchema2SwaggerObject(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils2.FixOpenAPISchema("", schema)
|
||||
addonRes.APISchema = schema
|
||||
return nil
|
||||
}
|
||||
|
|
@ -688,7 +701,6 @@ func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructu
|
|||
obj.SetNamespace(types.DefaultKubeVelaNS)
|
||||
defObjs = append(defObjs, obj)
|
||||
}
|
||||
|
||||
for _, cueDef := range addon.CUEDefinitions {
|
||||
def := definition.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
err := def.FromCUEString(cueDef.Data, config)
|
||||
|
|
@ -703,6 +715,29 @@ func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructu
|
|||
return defObjs, nil
|
||||
}
|
||||
|
||||
// RenderConfigTemplates render the config template
|
||||
func RenderConfigTemplates(addon *InstallPackage, cli client.Client) ([]*unstructured.Unstructured, error) {
|
||||
templates := make([]*unstructured.Unstructured, 0)
|
||||
|
||||
factory := config.NewConfigFactory(cli)
|
||||
for _, templateFile := range addon.ConfigTemplates {
|
||||
t, err := factory.ParseTemplate("", []byte(templateFile.Data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ConfigMap.Namespace = types.DefaultKubeVelaNS
|
||||
obj, err := util.Object2Unstructured(t.ConfigMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj.SetKind("ConfigMap")
|
||||
obj.SetAPIVersion("v1")
|
||||
templates = append(templates, obj)
|
||||
}
|
||||
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
// RenderDefinitionSchema will render definitions' schema in addons.
|
||||
func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured, error) {
|
||||
schemaConfigmaps := make([]*unstructured.Unstructured, 0)
|
||||
|
|
@ -1081,6 +1116,7 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
|||
|
||||
app.SetLabels(util.MergeMapOverrideWithDst(app.GetLabels(), map[string]string{oam.LabelAddonRegistry: h.r.Name}))
|
||||
|
||||
// Step1: Render the definitions
|
||||
defs, err := RenderDefinitions(addon, h.config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render addon definitions fail")
|
||||
|
|
@ -1096,11 +1132,19 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Step2: Render the config templates
|
||||
templates, err := RenderConfigTemplates(addon, h.cli)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render the config template fail")
|
||||
}
|
||||
|
||||
// Step3: Render the definition schemas
|
||||
schemas, err := RenderDefinitionSchema(addon)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render addon definitions' schema fail")
|
||||
}
|
||||
|
||||
// Step4: Render the velaQL views
|
||||
views, err := RenderViews(addon)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render addon views fail")
|
||||
|
|
@ -1109,6 +1153,7 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
|||
if err := passDefInAppAnnotation(defs, app); err != nil {
|
||||
return errors.Wrapf(err, "cannot pass definition to addon app's annotation")
|
||||
}
|
||||
|
||||
if h.dryRun {
|
||||
result, err := yaml.Marshal(app)
|
||||
if err != nil {
|
||||
|
|
@ -1124,6 +1169,7 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
|||
}
|
||||
|
||||
auxiliaryOutputs = append(auxiliaryOutputs, defs...)
|
||||
auxiliaryOutputs = append(auxiliaryOutputs, templates...)
|
||||
auxiliaryOutputs = append(auxiliaryOutputs, schemas...)
|
||||
auxiliaryOutputs = append(auxiliaryOutputs, views...)
|
||||
|
||||
|
|
|
|||
|
|
@ -165,6 +165,8 @@ func testReaderFunc(t *testing.T, reader AsyncReader) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, uiData.Name, testAddonName)
|
||||
assert.True(t, uiData.Parameters != "")
|
||||
assert.Equal(t, len(uiData.APISchema.Properties), 1)
|
||||
assert.Equal(t, uiData.APISchema.Properties["example"].Value.Description, "the example field")
|
||||
assert.True(t, len(uiData.Definitions) > 0)
|
||||
|
||||
testAddonName = "example-legacy"
|
||||
|
|
@ -184,6 +186,7 @@ func testReaderFunc(t *testing.T, reader AsyncReader) {
|
|||
// test get ui data
|
||||
rName := "KubeVela"
|
||||
uiDataList, err := ListAddonUIDataFromReader(reader, registryMeta, rName, UIMetaOptions)
|
||||
fmt.Println(err.Error())
|
||||
assert.True(t, strings.Contains(err.Error(), "preference mark not allowed at this position"))
|
||||
assert.Equal(t, 5, len(uiDataList))
|
||||
assert.Equal(t, uiDataList[0].RegistryName, rName)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
parameter: {
|
||||
example: string
|
||||
}
|
||||
//+usage=the example field
|
||||
example: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ type UIData struct {
|
|||
|
||||
Definitions []ElementFile `json:"definitions"`
|
||||
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
|
||||
ConfigTemplates []ElementFile `json:"configTemplates"`
|
||||
Parameters string `json:"parameters"`
|
||||
GlobalParameters string `json:"globalParameters"`
|
||||
RegistryName string `json:"registryName"`
|
||||
|
|
@ -49,6 +50,9 @@ type InstallPackage struct {
|
|||
// Definitions and CUEDefinitions are converted as OAM X-Definitions, they will only in control plane cluster
|
||||
Definitions []ElementFile `json:"definitions"`
|
||||
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
|
||||
|
||||
ConfigTemplates []ElementFile `json:"configTemplates"`
|
||||
|
||||
// YAMLViews and CUEViews are the instances of velaql, they will only in control plane cluster
|
||||
YAMLViews []ElementFile `json:"YAMLViews"`
|
||||
CUEViews []ElementFile `json:"CUEViews"`
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
|||
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
func init() {
|
||||
RegisterModel(&Project{})
|
||||
}
|
||||
|
|
@ -29,6 +31,11 @@ type Project struct {
|
|||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// GetNamespace get the namespace name of this project.
|
||||
func (p *Project) GetNamespace() string {
|
||||
return fmt.Sprintf("project-%s", p.Name)
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
func (p *Project) TableName() string {
|
||||
return tableNamePrefix + "project"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
|
|
@ -669,11 +668,6 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// sync configs to clusters
|
||||
if err := c.syncConfigs4Application(ctx, oamApp, app.Project, workflow.EnvName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// step2: check and create deploy event
|
||||
if !req.Force {
|
||||
var lastVersion = model.ApplicationRevision{
|
||||
|
|
@ -771,44 +765,6 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
|
|||
}, nil
|
||||
}
|
||||
|
||||
// sync configs to clusters
|
||||
func (c *applicationServiceImpl) syncConfigs4Application(ctx context.Context, app *v1beta1.Application, projectName, envName string) error {
|
||||
var areTerraformComponents = true
|
||||
for _, m := range app.Spec.Components {
|
||||
d := &v1beta1.ComponentDefinition{}
|
||||
if err := c.KubeClient.Get(ctx, client.ObjectKey{Namespace: velatypes.DefaultKubeVelaNS, Name: m.Type}, d); err != nil {
|
||||
klog.ErrorS(err, "failed to get config type", "ComponentDefinition", m.Type)
|
||||
}
|
||||
// check the type of the componentDefinition is Terraform
|
||||
if d.Spec.Schematic != nil && d.Spec.Schematic.Terraform == nil {
|
||||
areTerraformComponents = false
|
||||
}
|
||||
}
|
||||
// skip configs sync
|
||||
if areTerraformComponents {
|
||||
return nil
|
||||
}
|
||||
env, err := c.EnvService.GetEnv(ctx, envName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var clusterTargets []*model.ClusterTarget
|
||||
for _, t := range env.Targets {
|
||||
target, err := c.TargetService.GetTarget(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Cluster != nil {
|
||||
clusterTargets = append(clusterTargets, target.Cluster)
|
||||
}
|
||||
}
|
||||
|
||||
if err := SyncConfigs(ctx, c.KubeClient, projectName, clusterTargets); err != nil {
|
||||
return fmt.Errorf("sync config failure %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationServiceImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, envName, version string) (*v1beta1.Application, error) {
|
||||
// Priority 1 uses the requested workflow as release .
|
||||
// Priority 2 uses the default workflow as release .
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
kubevelatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
|
|
@ -208,8 +209,8 @@ var _ = Describe("Test authentication service functions", func() {
|
|||
Namespace: "vela-system",
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/source-of-truth": "from-inner-system",
|
||||
"config.oam.dev/catalog": "velacore-config",
|
||||
"config.oam.dev/type": "config-dex-connector",
|
||||
"config.oam.dev/catalog": kubevelatypes.VelaCoreConfig,
|
||||
"config.oam.dev/type": "dex-connector",
|
||||
"config.oam.dev/sub-type": "ldap",
|
||||
"project": "abc",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,43 +19,33 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sort"
|
||||
|
||||
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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"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/domain/model"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
const (
|
||||
configIsReady = "Ready"
|
||||
configIsNotReady = "Not ready"
|
||||
terraformProviderAlias = "Terraform Cloud Provider"
|
||||
configSyncProjectPrefix = "config-sync"
|
||||
)
|
||||
|
||||
// ConfigService handle CRUD of configs
|
||||
// ConfigService handle CRUD of config and template
|
||||
type ConfigService interface {
|
||||
ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error)
|
||||
GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error)
|
||||
CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error
|
||||
GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error)
|
||||
GetConfig(ctx context.Context, configType, name string) (*apis.Config, error)
|
||||
DeleteConfig(ctx context.Context, configType, name string) error
|
||||
ListTemplates(ctx context.Context, project, scope string) ([]*apis.ConfigTemplate, error)
|
||||
GetTemplate(ctx context.Context, tem config.NamespacedName) (*apis.ConfigTemplateDetail, error)
|
||||
CreateConfig(ctx context.Context, project string, req apis.CreateConfigRequest) (*apis.Config, error)
|
||||
UpdateConfig(ctx context.Context, project string, name string, req apis.UpdateConfigRequest) (*apis.Config, error)
|
||||
ListConfigs(ctx context.Context, project, template string, withProperties bool) ([]*apis.Config, error)
|
||||
GetConfig(ctx context.Context, project, name string) (*apis.Config, error)
|
||||
DeleteConfig(ctx context.Context, project, name string) error
|
||||
CreateConfigDistribution(ctx context.Context, project string, req apis.CreateConfigDistributionRequest) error
|
||||
DeleteConfigDistribution(ctx context.Context, project, name string) error
|
||||
ListConfigDistributions(ctx context.Context, project string) ([]*config.Distribution, error)
|
||||
}
|
||||
|
||||
// NewConfigService returns a config use case
|
||||
|
|
@ -64,425 +54,299 @@ func NewConfigService() ConfigService {
|
|||
}
|
||||
|
||||
type configServiceImpl struct {
|
||||
KubeClient client.Client `inject:"kubeClient"`
|
||||
KubeClient client.Client `inject:"kubeClient"`
|
||||
ProjectService ProjectService `inject:""`
|
||||
Factory config.Factory `inject:"configFactory"`
|
||||
Apply apply.Applicator `inject:"apply"`
|
||||
}
|
||||
|
||||
// ListConfigTypes returns all config types
|
||||
func (u *configServiceImpl) 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.ConfigCatalog: types.VelaCoreConfig,
|
||||
}); err != nil {
|
||||
// ListTemplates list the config templates
|
||||
func (u *configServiceImpl) ListTemplates(ctx context.Context, project, scope string) ([]*apis.ConfigTemplate, error) {
|
||||
queryTemplates, err := u.Factory.ListTemplates(ctx, types.DefaultKubeVelaNS, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []v1beta1.ComponentDefinition
|
||||
items = append(items, defs.Items...)
|
||||
|
||||
// for compatibility of the config catalog key
|
||||
defsLegacy := &v1beta1.ComponentDefinitionList{}
|
||||
if err := u.KubeClient.List(ctx, defsLegacy, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
// leave here as the legacy format to test the compatibility
|
||||
definition.UserPrefix + definition.ConfigCatalog: types.VelaCoreConfig,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// filter repeated config,due to new labels that exist at the same time
|
||||
for _, legacy := range defsLegacy.Items {
|
||||
if legacy.Labels[definition.ConfigCatalog] == types.VelaCoreConfig {
|
||||
continue
|
||||
if scope == "project" && project != "" {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, legacy)
|
||||
}
|
||||
var tfDefs []v1beta1.ComponentDefinition
|
||||
var configTypes []*apis.ConfigType
|
||||
|
||||
for _, d := range items {
|
||||
if DefinitionType(d.Labels) == types.TerraformProvider {
|
||||
tfDefs = append(tfDefs, d)
|
||||
continue
|
||||
templates, err := u.Factory.ListTemplates(ctx, pro.GetNamespace(), scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configTypes = append(configTypes, &apis.ConfigType{
|
||||
Alias: DefinitionAlias(d.Annotations),
|
||||
Name: d.Name,
|
||||
Definitions: []string{d.Name},
|
||||
Description: d.Annotations[types.AnnoDefinitionDescription],
|
||||
queryTemplates = append(queryTemplates, templates...)
|
||||
}
|
||||
var templates []*apis.ConfigTemplate
|
||||
for _, t := range queryTemplates {
|
||||
templates = append(templates, &apis.ConfigTemplate{
|
||||
Alias: t.Alias,
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
Namespace: t.Namespace,
|
||||
Scope: t.Scope,
|
||||
Sensitive: t.Sensitive,
|
||||
CreateTime: t.CreateTime,
|
||||
})
|
||||
}
|
||||
|
||||
if len(tfDefs) > 0 {
|
||||
tfType := &apis.ConfigType{
|
||||
Alias: terraformProviderAlias,
|
||||
Name: types.TerraformProvider,
|
||||
}
|
||||
definitions := make([]string, len(tfDefs))
|
||||
|
||||
for i, tf := range tfDefs {
|
||||
definitions[i] = tf.Name
|
||||
}
|
||||
tfType.Definitions = definitions
|
||||
|
||||
return append(configTypes, tfType), nil
|
||||
}
|
||||
return configTypes, nil
|
||||
sort.SliceStable(templates, func(i, j int) bool {
|
||||
return templates[i].Alias < templates[j].Alias
|
||||
})
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
// GetConfigType returns a config type
|
||||
func (u *configServiceImpl) GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error) {
|
||||
d := &v1beta1.ComponentDefinition{}
|
||||
if err := u.KubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: configType}, d); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get config type")
|
||||
// GetTemplate detail a template
|
||||
func (u *configServiceImpl) GetTemplate(ctx context.Context, tem config.NamespacedName) (*apis.ConfigTemplateDetail, error) {
|
||||
if tem.Namespace == "" {
|
||||
tem.Namespace = types.DefaultKubeVelaNS
|
||||
}
|
||||
|
||||
t := &apis.ConfigType{
|
||||
Alias: DefinitionAlias(d.Annotations),
|
||||
Name: configType,
|
||||
Description: d.Annotations[types.AnnoDefinitionDescription],
|
||||
template, err := u.Factory.LoadTemplate(ctx, tem.Name, tem.Namespace)
|
||||
if err != nil {
|
||||
if errors.Is(err, config.ErrTemplateNotFound) {
|
||||
return nil, bcode.ErrTemplateNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defaultUISchema := renderDefaultUISchema(template.Schema)
|
||||
t := &apis.ConfigTemplateDetail{
|
||||
ConfigTemplate: apis.ConfigTemplate{
|
||||
Alias: template.Alias,
|
||||
Name: template.Name,
|
||||
Description: template.Description,
|
||||
Namespace: template.Namespace,
|
||||
Scope: template.Scope,
|
||||
Sensitive: template.Sensitive,
|
||||
CreateTime: template.CreateTime,
|
||||
},
|
||||
APISchema: template.Schema,
|
||||
// TODO: Support to define the custom UI schema in the template cue script.
|
||||
UISchema: renderCustomUISchema(ctx, u.KubeClient, template.Name, "config", defaultUISchema),
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (u *configServiceImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error {
|
||||
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)
|
||||
}
|
||||
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 *configServiceImpl) GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error) {
|
||||
switch configType {
|
||||
case types.TerraformProvider:
|
||||
providers, err := config.ListTerraformProviders(ctx, u.KubeClient)
|
||||
func (u *configServiceImpl) CreateConfig(ctx context.Context, project string, req apis.CreateConfigRequest) (*apis.Config, error) {
|
||||
ns := types.DefaultKubeVelaNS
|
||||
if project != "" {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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])
|
||||
ns = pro.GetNamespace()
|
||||
if err := utils.CreateNamespace(ctx, u.KubeClient, ns); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
|
||||
default:
|
||||
return u.getConfigsByConfigType(ctx, configType)
|
||||
|
||||
}
|
||||
var properties = make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(req.Properties), &properties); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Template.Namespace == "" {
|
||||
req.Template.Namespace = types.DefaultKubeVelaNS
|
||||
}
|
||||
configItem, err := u.Factory.ParseConfig(ctx, config.NamespacedName(req.Template), config.Metadata{
|
||||
NamespacedName: config.NamespacedName{Name: req.Name, Namespace: ns},
|
||||
Properties: properties,
|
||||
Alias: req.Alias, Description: req.Description,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, config.ErrTemplateNotFound) {
|
||||
return nil, bcode.ErrTemplateNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := u.Factory.CreateOrUpdateConfig(ctx, configItem, ns); err != nil {
|
||||
if errors.Is(err, config.ErrConfigExist) {
|
||||
return nil, bcode.ErrConfigExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return convertConfig(project, *configItem), nil
|
||||
}
|
||||
|
||||
func (u *configServiceImpl) getConfigsByConfigType(ctx context.Context, configType string) ([]*apis.Config, error) {
|
||||
var apps = &v1beta1.ApplicationList{}
|
||||
if err := u.KubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: configType,
|
||||
}); err != nil {
|
||||
func (u *configServiceImpl) UpdateConfig(ctx context.Context, project string, name string, req apis.UpdateConfigRequest) (*apis.Config, error) {
|
||||
ns := types.DefaultKubeVelaNS
|
||||
if project != "" {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns = pro.GetNamespace()
|
||||
}
|
||||
|
||||
it, err := u.Factory.GetConfig(ctx, ns, name, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, config.ErrSensitiveConfig) {
|
||||
return nil, bcode.ErrSensitiveConfig
|
||||
}
|
||||
if errors.Is(err, config.ErrConfigNotFound) {
|
||||
return nil, bcode.ErrConfigNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configs := make([]*apis.Config, len(apps.Items))
|
||||
for i, a := range apps.Items {
|
||||
configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject])
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (u *configServiceImpl) GetConfig(ctx context.Context, configType, name string) (*apis.Config, error) {
|
||||
var a = &v1beta1.Application{}
|
||||
if err := u.KubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
|
||||
var properties = make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(req.Properties), &properties); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &apis.Config{
|
||||
ConfigType: a.Labels[types.LabelConfigType],
|
||||
Name: a.Name,
|
||||
Project: a.Labels[types.LabelConfigProject],
|
||||
CreatedTime: &a.CreationTimestamp.Time,
|
||||
configItem, err := u.Factory.ParseConfig(ctx,
|
||||
it.Template.NamespacedName,
|
||||
config.Metadata{NamespacedName: config.NamespacedName{Name: it.Name, Namespace: ns}, Alias: req.Alias, Description: req.Description, Properties: properties})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
if err := u.Factory.CreateOrUpdateConfig(ctx, configItem, ns); err != nil {
|
||||
if errors.Is(err, config.ErrConfigExist) {
|
||||
return nil, bcode.ErrChangeTemplate
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return convertConfig(project, *configItem), nil
|
||||
}
|
||||
|
||||
func (u *configServiceImpl) DeleteConfig(ctx context.Context, configType, name string) error {
|
||||
var isTerraformProvider bool
|
||||
if strings.HasPrefix(configType, types.TerraformComponentPrefix) {
|
||||
isTerraformProvider = true
|
||||
}
|
||||
return config.DeleteApplication(ctx, u.KubeClient, name, isTerraformProvider)
|
||||
}
|
||||
|
||||
// ApplicationDeployTarget is the struct of application deploy target
|
||||
type ApplicationDeployTarget struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Clusters []string `json:"clusters"`
|
||||
}
|
||||
|
||||
// SyncConfigs will sync configs to working clusters
|
||||
func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, targets []*model.ClusterTarget) error {
|
||||
name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project)
|
||||
// get all configs which can be synced to working clusters in the project
|
||||
var secrets v1.SecretList
|
||||
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigSyncToMultiCluster: "true",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(secrets.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
var objects []map[string]string
|
||||
for _, s := range secrets.Items {
|
||||
if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project {
|
||||
objects = append(objects, map[string]string{
|
||||
"name": s.Name,
|
||||
"resource": "secret",
|
||||
})
|
||||
// ListConfigs query the available configs.
|
||||
// If the project is not empty, it means query all usable configs for this project.
|
||||
func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, template string, withProperties bool) ([]*apis.Config, error) {
|
||||
var list []*apis.Config
|
||||
scope := ""
|
||||
var projectNamespace string
|
||||
if project != "" {
|
||||
scope = "project"
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectNamespace = pro.GetNamespace()
|
||||
// query the configs belong to the project scope from the system namespace
|
||||
configs, err := u.Factory.ListConfigs(ctx, pro.GetNamespace(), template, "", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range configs {
|
||||
list = append(list, convertConfig(project, *configs[i]))
|
||||
}
|
||||
}
|
||||
if len(objects) == 0 {
|
||||
klog.InfoS("no configs need to sync to working clusters", "project", project)
|
||||
return nil
|
||||
|
||||
configs, err := u.Factory.ListConfigs(ctx, types.DefaultKubeVelaNS, template, scope, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
|
||||
for i := range configs {
|
||||
if projectNamespace != "" {
|
||||
if err := u.Factory.MergeDistributionStatus(ctx, configs[i], projectNamespace); err != nil && !errors.Is(err, config.ErrNotFoundDistribution) {
|
||||
log.Logger.Warnf("fail to merge the status %s:%s", configs[i].Name, err.Error())
|
||||
}
|
||||
}
|
||||
item := convertConfig(project, *configs[i])
|
||||
item.Shared = true
|
||||
if !withProperties {
|
||||
item.Properties = nil
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// CreateConfigDistribution distribute the configs to the target namespaces.
|
||||
func (u *configServiceImpl) CreateConfigDistribution(ctx context.Context, project string, req apis.CreateConfigDistributionRequest) error {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var app = &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
// config sync application doesn't exist, create one
|
||||
clusterTargets := convertClusterTargets(targets)
|
||||
if len(clusterTargets) == 0 {
|
||||
errMsg := "no policy (no targets found) to sync configs"
|
||||
klog.InfoS(errMsg, "project", project)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
policies := make([]v1beta1.AppPolicy, len(clusterTargets))
|
||||
for i, t := range clusterTargets {
|
||||
properties, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policies[i] = v1beta1.AppPolicy{
|
||||
Type: "topology",
|
||||
Name: t.Namespace,
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
scratch := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigProject: project,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: name,
|
||||
Type: "ref-objects",
|
||||
Properties: &runtime.RawExtension{Raw: objectsBytes},
|
||||
},
|
||||
},
|
||||
Policies: policies,
|
||||
},
|
||||
}
|
||||
return k8sClient.Create(ctx, scratch)
|
||||
if len(req.Configs) == 0 || len(req.Targets) == 0 {
|
||||
return bcode.ErrNoConfigOrTarget
|
||||
}
|
||||
// config sync application exists, update it
|
||||
app.Spec.Components = []common.ApplicationComponent{
|
||||
{
|
||||
Name: name,
|
||||
Type: "ref-objects",
|
||||
Properties: &runtime.RawExtension{Raw: objectsBytes},
|
||||
},
|
||||
}
|
||||
currentTargets := make([]ApplicationDeployTarget, len(app.Spec.Policies))
|
||||
for i, p := range app.Spec.Policies {
|
||||
var t ApplicationDeployTarget
|
||||
if err := json.Unmarshal(p.Properties.Raw, &t); err != nil {
|
||||
return err
|
||||
var targets []*config.ClusterTarget
|
||||
for _, t := range req.Targets {
|
||||
if t.Namespace != "" && t.ClusterName != "" {
|
||||
targets = append(targets, &config.ClusterTarget{Namespace: t.Namespace, ClusterName: t.ClusterName})
|
||||
}
|
||||
currentTargets[i] = t
|
||||
}
|
||||
|
||||
mergedTarget := mergeTargets(currentTargets, targets)
|
||||
if len(mergedTarget) == 0 {
|
||||
errMsg := "no policy (no targets found) to sync configs"
|
||||
klog.InfoS(errMsg, "project", project)
|
||||
return errors.New(errMsg)
|
||||
var configs []*config.NamespacedName
|
||||
for _, t := range req.Configs {
|
||||
if t.Name != "" {
|
||||
configs = append(configs, &config.NamespacedName{Namespace: t.Namespace, Name: t.Name})
|
||||
}
|
||||
}
|
||||
mergedPolicies := make([]v1beta1.AppPolicy, len(mergedTarget))
|
||||
for i, t := range mergedTarget {
|
||||
properties, err := json.Marshal(t)
|
||||
return u.Factory.CreateOrUpdateDistribution(ctx, pro.GetNamespace(), req.Name, &config.CreateDistributionSpec{
|
||||
Configs: configs,
|
||||
Targets: targets,
|
||||
})
|
||||
}
|
||||
|
||||
// ListDistributeConfigs list the all distributions
|
||||
func (u *configServiceImpl) ListConfigDistributions(ctx context.Context, project string) ([]*config.Distribution, error) {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u.Factory.ListDistributions(ctx, pro.GetNamespace())
|
||||
}
|
||||
|
||||
// DeleteConfigDistribution delete a distribution
|
||||
func (u *configServiceImpl) DeleteConfigDistribution(ctx context.Context, project, name string) error {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := u.Factory.DeleteDistribution(ctx, pro.GetNamespace(), name); err != nil {
|
||||
if errors.Is(err, config.ErrNotFoundDistribution) {
|
||||
return bcode.ErrNotFoundDistribution
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertConfig(project string, config config.Config) *apis.Config {
|
||||
return &apis.Config{
|
||||
Template: config.Template.NamespacedName,
|
||||
Sensitive: config.Template.Sensitive,
|
||||
Name: config.Name,
|
||||
Namespace: config.Namespace,
|
||||
Project: project,
|
||||
Alias: config.Alias,
|
||||
Description: config.Description,
|
||||
CreatedTime: &config.CreateTime,
|
||||
Properties: config.Properties,
|
||||
Secret: config.Secret,
|
||||
Targets: config.Targets,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *configServiceImpl) GetConfig(ctx context.Context, project, name string) (*apis.Config, error) {
|
||||
ns := types.DefaultKubeVelaNS
|
||||
if project != "" {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns = pro.GetNamespace()
|
||||
}
|
||||
|
||||
it, err := u.Factory.GetConfig(ctx, ns, name, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, config.ErrSensitiveConfig) {
|
||||
return nil, bcode.ErrSensitiveConfig
|
||||
}
|
||||
if errors.Is(err, config.ErrConfigNotFound) {
|
||||
return nil, bcode.ErrConfigNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return convertConfig(project, *it), nil
|
||||
}
|
||||
|
||||
func (u *configServiceImpl) DeleteConfig(ctx context.Context, project, name string) error {
|
||||
ns := types.DefaultKubeVelaNS
|
||||
if project != "" {
|
||||
pro, err := u.ProjectService.GetProject(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mergedPolicies[i] = v1beta1.AppPolicy{
|
||||
Type: "topology",
|
||||
Name: t.Namespace,
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: properties,
|
||||
},
|
||||
}
|
||||
ns = pro.GetNamespace()
|
||||
}
|
||||
app.Spec.Policies = mergedPolicies
|
||||
return k8sClient.Update(ctx, app)
|
||||
}
|
||||
|
||||
func mergeTargets(currentTargets []ApplicationDeployTarget, targets []*model.ClusterTarget) []ApplicationDeployTarget {
|
||||
var (
|
||||
mergedTargets []ApplicationDeployTarget
|
||||
// make sure the clusters of target with same namespace are merged
|
||||
clusterTargets = convertClusterTargets(targets)
|
||||
)
|
||||
|
||||
for _, c := range currentTargets {
|
||||
var hasSameNamespace bool
|
||||
for _, t := range clusterTargets {
|
||||
if c.Namespace == t.Namespace {
|
||||
hasSameNamespace = true
|
||||
clusters := set.NewSetFromSlice(stringToInterfaceSlice(t.Clusters))
|
||||
for _, cluster := range c.Clusters {
|
||||
clusters.Add(cluster)
|
||||
}
|
||||
mergedTargets = append(mergedTargets, ApplicationDeployTarget{Namespace: c.Namespace,
|
||||
Clusters: interfaceToStringSlice(clusters.ToSlice())})
|
||||
}
|
||||
}
|
||||
if !hasSameNamespace {
|
||||
mergedTargets = append(mergedTargets, c)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range clusterTargets {
|
||||
var hasSameNamespace bool
|
||||
for _, c := range currentTargets {
|
||||
if c.Namespace == t.Namespace {
|
||||
hasSameNamespace = true
|
||||
}
|
||||
}
|
||||
if !hasSameNamespace {
|
||||
mergedTargets = append(mergedTargets, t)
|
||||
}
|
||||
}
|
||||
|
||||
return mergedTargets
|
||||
}
|
||||
|
||||
func convertClusterTargets(targets []*model.ClusterTarget) []ApplicationDeployTarget {
|
||||
type Target struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Clusters []interface{} `json:"clusters"`
|
||||
}
|
||||
|
||||
var (
|
||||
clusterTargets []Target
|
||||
namespaceSet = set.NewSet()
|
||||
)
|
||||
|
||||
for i := 0; i < len(targets); i++ {
|
||||
clusters := set.NewSet(targets[i].ClusterName)
|
||||
for j := i + 1; j < len(targets); j++ {
|
||||
if targets[i].Namespace == targets[j].Namespace {
|
||||
clusters.Add(targets[j].ClusterName)
|
||||
}
|
||||
}
|
||||
if namespaceSet.Contains(targets[i].Namespace) {
|
||||
continue
|
||||
}
|
||||
clusterTargets = append(clusterTargets, Target{
|
||||
Namespace: targets[i].Namespace,
|
||||
Clusters: clusters.ToSlice(),
|
||||
})
|
||||
namespaceSet.Add(targets[i].Namespace)
|
||||
}
|
||||
|
||||
t := make([]ApplicationDeployTarget, len(clusterTargets))
|
||||
for i, ct := range clusterTargets {
|
||||
t[i] = ApplicationDeployTarget{
|
||||
Namespace: ct.Namespace,
|
||||
Clusters: interfaceToStringSlice(ct.Clusters),
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func interfaceToStringSlice(i []interface{}) []string {
|
||||
var s []string
|
||||
for _, v := range i {
|
||||
s = append(s, v.(string))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func stringToInterfaceSlice(i []string) []interface{} {
|
||||
var s []interface{}
|
||||
for _, v := range i {
|
||||
s = append(s, v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// destroySyncConfigsApp will delete the application which is used to sync configs
|
||||
func destroySyncConfigsApp(ctx context.Context, k8sClient client.Client, project string) error {
|
||||
name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project)
|
||||
var app = &v1beta1.Application{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
klog.InfoS("config sync application doesn't exist, no need destroy", "application", name)
|
||||
return nil
|
||||
}
|
||||
return k8sClient.Delete(ctx, app)
|
||||
return u.Factory.DeleteConfig(ctx, ns, name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,755 +18,183 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"errors"
|
||||
|
||||
. "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"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"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/domain/model"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
)
|
||||
|
||||
func TestListConfigTypes(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",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
definition.ConfigCatalog: types.VelaCoreConfig,
|
||||
definition.DefinitionType: types.TerraformProvider,
|
||||
},
|
||||
},
|
||||
}
|
||||
def2 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
definition.DefinitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.ConfigCatalog: types.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
|
||||
var alibabaTerraformTemplate = `
|
||||
import "strings"
|
||||
|
||||
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
|
||||
return k8sClient, nil, nil
|
||||
})
|
||||
defer patches.Reset()
|
||||
metadata: {
|
||||
name: "terraform-provider-alibaba"
|
||||
alias: "Alibaba Terraform Provider"
|
||||
sensitive: true
|
||||
scope: "system"
|
||||
description: "Terraform Provider for Alibaba Cloud"
|
||||
}
|
||||
|
||||
h := &configServiceImpl{
|
||||
KubeClient: k8sClient,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
h ConfigService
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configTypes []*apis.ConfigType
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configTypes: []*apis.ConfigType{
|
||||
{
|
||||
Name: "def2",
|
||||
Alias: "Def2",
|
||||
Definitions: []string{"def2"},
|
||||
},
|
||||
{
|
||||
Alias: "Terraform Cloud Provider",
|
||||
Name: types.TerraformProvider,
|
||||
Definitions: []string{
|
||||
"def1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.ListConfigTypes(ctx, "")
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
template: {
|
||||
outputs: {
|
||||
"provider": {
|
||||
apiVersion: "terraform.core.oam.dev/v1beta1"
|
||||
kind: "Provider"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
namespace: context.namespace
|
||||
labels: l
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configTypes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigType(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
def2 := &v1beta1.ComponentDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ComponentDefinition",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "def2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Annotations: map[string]string{
|
||||
definition.DefinitionAlias: "Def2",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
definition.UserPrefix + definition.ConfigCatalog: types.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def2).Build()
|
||||
|
||||
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
|
||||
return k8sClient, nil, nil
|
||||
})
|
||||
defer patches.Reset()
|
||||
|
||||
h := &configServiceImpl{KubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
h ConfigService
|
||||
name string
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configType *apis.ConfigType
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
h: h,
|
||||
name: "def2",
|
||||
},
|
||||
want: want{
|
||||
configType: &apis.ConfigType{
|
||||
Alias: "Def2",
|
||||
Name: "def2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
args: args{
|
||||
h: h,
|
||||
name: "def99",
|
||||
},
|
||||
want: want{
|
||||
errMsg: "failed to get config type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.GetConfigType(ctx, tc.args.name)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configType)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateConfig(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
|
||||
|
||||
h := &configServiceImpl{KubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
h ConfigService
|
||||
req apis.CreateConfigRequest
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
properties, err := json.Marshal(map[string]interface{}{
|
||||
"name": "default",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "delete config when it's not ready",
|
||||
args: args{
|
||||
h: h,
|
||||
req: apis.CreateConfigRequest{
|
||||
Name: "a",
|
||||
ComponentType: "b",
|
||||
Project: "c",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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 {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.args.h.CreateConfig(ctx, tc.args.req)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigs(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022")
|
||||
|
||||
provider1 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider1",
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsReady,
|
||||
},
|
||||
}
|
||||
|
||||
provider2 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider2",
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsNotReady,
|
||||
},
|
||||
}
|
||||
|
||||
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 := &configServiceImpl{KubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
configType string
|
||||
h ConfigService
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configs []*apis.Config
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
configType: types.TerraformProvider,
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.GetConfigs(ctx, tc.args.configType)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeTargets(t *testing.T) {
|
||||
currentTargets := []ApplicationDeployTarget{
|
||||
{
|
||||
Namespace: "n1",
|
||||
Clusters: []string{"c1", "c2"},
|
||||
}, {
|
||||
Namespace: "n2",
|
||||
Clusters: []string{"c3"},
|
||||
},
|
||||
}
|
||||
targets := []*model.ClusterTarget{
|
||||
{
|
||||
Namespace: "n3",
|
||||
ClusterName: "c4",
|
||||
}, {
|
||||
Namespace: "n1",
|
||||
ClusterName: "c5",
|
||||
},
|
||||
{
|
||||
Namespace: "n2",
|
||||
ClusterName: "c3",
|
||||
},
|
||||
}
|
||||
|
||||
expected := []ApplicationDeployTarget{
|
||||
{
|
||||
Namespace: "n1",
|
||||
Clusters: []string{"c1", "c2", "c5"},
|
||||
}, {
|
||||
Namespace: "n2",
|
||||
Clusters: []string{"c3"},
|
||||
}, {
|
||||
Namespace: "n3",
|
||||
Clusters: []string{"c4"},
|
||||
},
|
||||
}
|
||||
|
||||
got := mergeTargets(currentTargets, targets)
|
||||
|
||||
for i, g := range got {
|
||||
clusters := g.Clusters
|
||||
sort.SliceStable(clusters, func(i, j int) bool {
|
||||
return clusters[i] < clusters[j]
|
||||
})
|
||||
got[i].Clusters = clusters
|
||||
}
|
||||
assert.DeepEqual(t, expected, got)
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
targets := []*model.ClusterTarget{
|
||||
{
|
||||
Namespace: "n3",
|
||||
ClusterName: "c4",
|
||||
}, {
|
||||
Namespace: "n1",
|
||||
ClusterName: "c5",
|
||||
},
|
||||
{
|
||||
Namespace: "n2",
|
||||
ClusterName: "c3",
|
||||
},
|
||||
{
|
||||
Namespace: "n3",
|
||||
ClusterName: "c5",
|
||||
},
|
||||
}
|
||||
|
||||
expected := []ApplicationDeployTarget{
|
||||
{
|
||||
Namespace: "n3",
|
||||
Clusters: []string{"c4", "c5"},
|
||||
},
|
||||
{
|
||||
Namespace: "n1",
|
||||
Clusters: []string{"c5"},
|
||||
}, {
|
||||
Namespace: "n2",
|
||||
Clusters: []string{"c3"},
|
||||
},
|
||||
}
|
||||
|
||||
got := convertClusterTargets(targets)
|
||||
|
||||
for i, g := range got {
|
||||
clusters := g.Clusters
|
||||
sort.SliceStable(clusters, func(i, j int) bool {
|
||||
return clusters[i] < clusters[j]
|
||||
})
|
||||
got[i].Clusters = clusters
|
||||
}
|
||||
assert.DeepEqual(t, expected, got)
|
||||
}
|
||||
|
||||
func TestDestroySyncConfigsApp(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-p1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
}
|
||||
k8sClient1 := fake.NewClientBuilder().WithScheme(s).WithObjects(app1).Build()
|
||||
|
||||
k8sClient2 := fake.NewClientBuilder().Build()
|
||||
|
||||
type args struct {
|
||||
project string
|
||||
k8sClient client.Client
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"found": {
|
||||
args: args{
|
||||
project: "p1",
|
||||
k8sClient: k8sClient1,
|
||||
},
|
||||
},
|
||||
"not found": {
|
||||
args: args{
|
||||
project: "p1",
|
||||
k8sClient: k8sClient2,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "no kind is registered for the type v1beta1.Application",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := destroySyncConfigsApp(ctx, tc.args.k8sClient, tc.args.project)
|
||||
if err != nil || tc.want.errMsg != "" {
|
||||
if !strings.Contains(err.Error(), tc.want.errMsg) {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
spec: {
|
||||
provider: "alibaba"
|
||||
region: parameter.ALICLOUD_REGION
|
||||
credentials: {
|
||||
source: "Secret"
|
||||
secretRef: {
|
||||
namespace: "vela-system"
|
||||
name: context.name
|
||||
key: "credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
type: "Opaque"
|
||||
stringData: credentials: strings.Join([creds1, creds2], "\n")
|
||||
}
|
||||
|
||||
creds1: "accessKeyID: " + parameter.ALICLOUD_ACCESS_KEY
|
||||
creds2: "accessKeySecret: " + parameter.ALICLOUD_SECRET_KEY
|
||||
|
||||
l: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "terraform-provider"
|
||||
"config.oam.dev/provider": "terraform-alibaba"
|
||||
}
|
||||
|
||||
parameter: {
|
||||
//+usage=The name of Terraform Provider for Alibaba Cloud
|
||||
name: *"default" | string
|
||||
//+usage=Get ALICLOUD_ACCESS_KEY per this guide https://help.aliyun.com/knowledge_detail/38738.html
|
||||
ALICLOUD_ACCESS_KEY: string
|
||||
//+usage=Get ALICLOUD_SECRET_KEY per this guide https://help.aliyun.com/knowledge_detail/38738.html
|
||||
ALICLOUD_SECRET_KEY: string
|
||||
//+usage=Get ALICLOUD_REGION by picking one RegionId from Alibaba Cloud region list https://www.alibabacloud.com/help/doc-detail/72379.htm
|
||||
ALICLOUD_REGION: string
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestSyncConfigs(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
secret1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigProject: "p1",
|
||||
types.LabelConfigSyncToMultiCluster: "true",
|
||||
var _ = Describe("Test config service", func() {
|
||||
var factory config.Factory
|
||||
var ds datastore.DataStore
|
||||
var configService *configServiceImpl
|
||||
var projectService ProjectService
|
||||
BeforeEach(func() {
|
||||
factory = config.NewConfigFactory(k8sClient)
|
||||
Expect(factory).ToNot(BeNil())
|
||||
var err error
|
||||
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "config-test-kubevela"})
|
||||
Expect(ds).ToNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
projectService = NewTestProjectService(ds, k8sClient)
|
||||
configService = &configServiceImpl{
|
||||
KubeClient: k8sClient,
|
||||
ProjectService: projectService,
|
||||
Factory: factory,
|
||||
}
|
||||
})
|
||||
It("Test apply terraform template", func() {
|
||||
tem, err := factory.ParseTemplate("alibaba-provider", []byte(alibabaTerraformTemplate))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(factory.CreateOrUpdateConfigTemplate(context.Background(), types.DefaultKubeVelaNS, tem)).To(BeNil())
|
||||
})
|
||||
It("Test detail the template", func() {
|
||||
detail, err := configService.GetTemplate(context.TODO(), config.NamespacedName{Name: "alibaba-provider"})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(detail.UISchema)).To(Equal(4))
|
||||
})
|
||||
It("Test apply a new config", func() {
|
||||
_, err := configService.CreateConfig(context.TODO(), "", v1.CreateConfigRequest{
|
||||
Name: "alibaba-test-error-properties",
|
||||
Alias: "Alibaba Cloud",
|
||||
Description: "This is a terraform provider",
|
||||
Template: v1.NamespacedName{
|
||||
Name: "alibaba-provider",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
policies := []ApplicationDeployTarget{{
|
||||
Namespace: "n9",
|
||||
Clusters: []string{"c19"},
|
||||
}}
|
||||
properties, _ := json.Marshal(policies)
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-p2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "c19",
|
||||
Type: "topology",
|
||||
Properties: &runtime.RawExtension{Raw: properties},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(secret1, app1).Build()
|
||||
|
||||
type args struct {
|
||||
project string
|
||||
targets []*model.ClusterTarget
|
||||
}
|
||||
|
||||
type want struct {
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "create",
|
||||
args: args{
|
||||
project: "p1",
|
||||
targets: []*model.ClusterTarget{{
|
||||
ClusterName: "c1",
|
||||
Namespace: "n1",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update",
|
||||
args: args{
|
||||
project: "p2",
|
||||
targets: []*model.ClusterTarget{{
|
||||
ClusterName: "c1",
|
||||
Namespace: "n1",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "skip config sync",
|
||||
args: args{
|
||||
project: "p3",
|
||||
targets: []*model.ClusterTarget{{
|
||||
ClusterName: "c1",
|
||||
Namespace: "n1",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := SyncConfigs(ctx, k8sClient, tc.args.project, tc.args.targets)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
Properties: `{}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
Expect(err).ToNot(BeNil())
|
||||
var paramErr = &script.ParameterError{}
|
||||
Expect(errors.As(err, ¶mErr)).To(Equal(true))
|
||||
Expect(paramErr.Name).To(Equal("ALICLOUD_ACCESS_KEY"))
|
||||
Expect(paramErr.Message).To(Equal("This parameter is required"))
|
||||
|
||||
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",
|
||||
config, err := configService.CreateConfig(context.TODO(), "", v1.CreateConfigRequest{
|
||||
Name: "alibaba-test",
|
||||
Alias: "Alibaba Cloud",
|
||||
Description: "This is a terraform provider",
|
||||
Template: v1.NamespacedName{
|
||||
Name: "alibaba-provider",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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 := &configServiceImpl{KubeClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
configType string
|
||||
name string
|
||||
h ConfigService
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Properties: `{"ALICLOUD_ACCESS_KEY":"test", "ALICLOUD_SECRET_KEY": "test", "ALICLOUD_REGION": "test", "name": "test"}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
Expect(config.Sensitive).To(Equal(true))
|
||||
Expect(config.Description).To(Equal("This is a terraform provider"))
|
||||
|
||||
var provider terraformapi.Provider
|
||||
Expect(k8sClient.Get(context.TODO(), apitypes.NamespacedName{
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Name: "test",
|
||||
}, &provider)).To(BeNil())
|
||||
})
|
||||
|
||||
It("Test list configs", func() {
|
||||
list, err := configService.ListConfigs(context.TODO(), "", "alibaba-provider", false)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(list)).To(Equal(1))
|
||||
|
||||
_, err = projectService.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: "mysql-project"})
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// can not share the config that is the system scope
|
||||
list, err = configService.ListConfigs(context.TODO(), "mysql-project", "alibaba-provider", false)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(list)).To(Equal(0))
|
||||
|
||||
list, err = configService.ListConfigs(context.TODO(), "", "not-found", false)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(list)).To(Equal(0))
|
||||
})
|
||||
|
||||
It("Test detail a config", func() {
|
||||
_, err := configService.GetConfig(context.TODO(), "", "alibaba-test")
|
||||
Expect(err).To(Equal(bcode.ErrSensitiveConfig))
|
||||
})
|
||||
|
||||
It("Test delete a config", func() {
|
||||
Expect(configService.DeleteConfig(context.TODO(), "", "alibaba-test")).To(BeNil())
|
||||
var list terraformapi.ProviderList
|
||||
Expect(k8sClient.List(context.TODO(), &list)).To(BeNil())
|
||||
Expect(len(list.Items)).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -258,15 +258,15 @@ func (d *definitionServiceImpl) DetailDefinition(ctx context.Context, name, defT
|
|||
// render default ui schema
|
||||
defaultUISchema := renderDefaultUISchema(schema)
|
||||
// patch from custom ui schema
|
||||
definition.UISchema = d.renderCustomUISchema(ctx, name, defType, defaultUISchema)
|
||||
definition.UISchema = renderCustomUISchema(ctx, d.KubeClient, name, defType, defaultUISchema)
|
||||
}
|
||||
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
func (d *definitionServiceImpl) renderCustomUISchema(ctx context.Context, name, defType string, defaultSchema []*utils.UIParameter) []*utils.UIParameter {
|
||||
func renderCustomUISchema(ctx context.Context, cli client.Client, name, defType string, defaultSchema []*utils.UIParameter) []*utils.UIParameter {
|
||||
var cm v1.ConfigMap
|
||||
if err := d.KubeClient.Get(ctx, k8stypes.NamespacedName{
|
||||
if err := cli.Get(ctx, k8stypes.NamespacedName{
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Name: fmt.Sprintf("%s-uischema-%s", defType, name),
|
||||
}, &cm); err != nil {
|
||||
|
|
|
|||
|
|
@ -20,18 +20,14 @@ import (
|
|||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/config"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
types2 "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
|
|
@ -54,8 +50,9 @@ type HelmService interface {
|
|||
}
|
||||
|
||||
type defaultHelmImpl struct {
|
||||
helper *helm.Helper
|
||||
K8sClient client.Client `inject:"kubeClient"`
|
||||
helper *helm.Helper
|
||||
K8sClient client.Client `inject:"kubeClient"`
|
||||
ConfigService ConfigService `inject:""`
|
||||
}
|
||||
|
||||
func (d defaultHelmImpl) ListChartNames(ctx context.Context, repoURL string, secretName string, skipCache bool) ([]string, error) {
|
||||
|
|
@ -126,21 +123,20 @@ func (d defaultHelmImpl) GetChartValues(ctx context.Context, repoURL string, cha
|
|||
|
||||
func (d defaultHelmImpl) ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error) {
|
||||
var res []*v1.ChartRepoResponse
|
||||
var err error
|
||||
|
||||
projectSecrets := corev1.SecretList{}
|
||||
opts := []client.ListOption{
|
||||
client.MatchingLabels{oam.LabelConfigType: "config-helm-repository"},
|
||||
client.InNamespace(types.DefaultKubeVelaNS),
|
||||
}
|
||||
err = d.K8sClient.List(ctx, &projectSecrets, opts...)
|
||||
configs, err := d.ConfigService.ListConfigs(ctx, projectName, types.HelmRepository, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range projectSecrets.Items {
|
||||
if config.ProjectMatched(item.DeepCopy(), projectName) {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
|
||||
for _, item := range configs {
|
||||
if item.Properties != nil {
|
||||
url, ok := item.Properties["url"].(string)
|
||||
if ok {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: url, SecretName: item.Name})
|
||||
}
|
||||
}
|
||||
// Compatible with historical data
|
||||
if url, ok := item.Secret.Data["url"]; ok {
|
||||
res = append(res, &v1.ChartRepoResponse{URL: string(url), SecretName: item.Name})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@ import (
|
|||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
|
@ -53,11 +57,22 @@ func TestFlattenKeyFunc(t *testing.T) {
|
|||
}
|
||||
|
||||
// NewTestHelmService new helm service for test
|
||||
func NewTestHelmService() HelmService {
|
||||
func NewTestHelmService() (*defaultHelmImpl, ProjectService, error) {
|
||||
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "helm-test-kubevela"})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
projectService := NewTestProjectService(ds, k8sClient)
|
||||
return &defaultHelmImpl{
|
||||
helper: helm.NewHelperWithCache(),
|
||||
K8sClient: k8sClient,
|
||||
}
|
||||
ConfigService: &configServiceImpl{
|
||||
KubeClient: k8sClient,
|
||||
ProjectService: projectService,
|
||||
Factory: config.NewConfigFactory(k8sClient),
|
||||
Apply: apply.NewAPIApplicator(k8sClient),
|
||||
},
|
||||
}, projectService, nil
|
||||
}
|
||||
|
||||
var _ = Describe("Test helm repo list", func() {
|
||||
|
|
@ -68,6 +83,7 @@ var _ = Describe("Test helm repo list", func() {
|
|||
pSec = v1.Secret{}
|
||||
gSec = v1.Secret{}
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "project-my-project"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(yaml.Unmarshal([]byte(projectSecret), &pSec)).Should(BeNil())
|
||||
Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &pSec)).Should(BeNil())
|
||||
|
|
@ -80,7 +96,14 @@ var _ = Describe("Test helm repo list", func() {
|
|||
})
|
||||
|
||||
It("Test list with project ", func() {
|
||||
u := NewTestHelmService()
|
||||
u, p, err := NewTestHelmService()
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
_, err = p.CreateProject(context.TODO(), apis.CreateProjectRequest{
|
||||
Name: "my-project",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
list, err := u.ListChartRepo(ctx, "my-project")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(2))
|
||||
|
|
@ -99,7 +122,13 @@ var _ = Describe("Test helm repo list", func() {
|
|||
})
|
||||
|
||||
It("Test list func with not exist project", func() {
|
||||
u := NewTestHelmService()
|
||||
u, p, err := NewTestHelmService()
|
||||
Expect(err).Should(BeNil())
|
||||
_, err = p.CreateProject(context.TODO(), apis.CreateProjectRequest{
|
||||
Name: "not-exist-project",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
list, err := u.ListChartRepo(ctx, "not-exist-project")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
|
||||
|
|
@ -108,7 +137,8 @@ var _ = Describe("Test helm repo list", func() {
|
|||
})
|
||||
|
||||
It("Test list func without project", func() {
|
||||
u := NewTestHelmService()
|
||||
u, _, err := NewTestHelmService()
|
||||
Expect(err).Should(BeNil())
|
||||
list, err := u.ListChartRepo(ctx, "")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
|
||||
|
|
@ -171,7 +201,8 @@ var _ = Describe("test helm usecasae", func() {
|
|||
|
||||
defer mockServer.Close()
|
||||
|
||||
u := NewTestHelmService()
|
||||
u, _, err := NewTestHelmService()
|
||||
Expect(err).Should(BeNil())
|
||||
charts, err := u.ListChartNames(ctx, mockServer.URL, "repo-secret", false)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(charts)).Should(BeEquivalentTo(1))
|
||||
|
|
@ -189,8 +220,9 @@ var _ = Describe("test helm usecasae", func() {
|
|||
})
|
||||
|
||||
It("coverage not secret notExist error", func() {
|
||||
u := NewTestHelmService()
|
||||
_, err := u.ListChartNames(ctx, "http://127.0.0.1:8080", "repo-secret-notExist", false)
|
||||
u, _, err := NewTestHelmService()
|
||||
Expect(err).Should(BeNil())
|
||||
_, err = u.ListChartNames(ctx, "http://127.0.0.1:8080", "repo-secret-notExist", false)
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
|
||||
_, err = u.ListChartVersions(ctx, "http://127.0.0.1:8080", "mysql", "repo-secret-notExist", false)
|
||||
|
|
@ -345,11 +377,12 @@ stringData:
|
|||
url: https://charts.bitnami.com/bitnami
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/project: ""
|
||||
name: global-helm-repo
|
||||
namespace: vela-system
|
||||
labels:
|
||||
config.oam.dev/type: helm-repository
|
||||
config.oam.dev/scope: project
|
||||
config.oam.dev/catalog: velacore-config
|
||||
type: Opaque
|
||||
`
|
||||
projectSecret = `
|
||||
|
|
@ -357,10 +390,11 @@ apiVersion: v1
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: project-helm-repo
|
||||
namespace: vela-system
|
||||
namespace: project-my-project
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/project: my-project
|
||||
config.oam.dev/type: helm-repository
|
||||
config.oam.dev/catalog: velacore-config
|
||||
config.oam.dev/scope: project
|
||||
stringData:
|
||||
url: https://kedacore.github.io/charts
|
||||
type: Opaque
|
||||
|
|
@ -372,8 +406,9 @@ metadata:
|
|||
name: repo-secret
|
||||
namespace: vela-system
|
||||
labels:
|
||||
config.oam.dev/type: config-helm-repository
|
||||
config.oam.dev/type: helm-repository
|
||||
config.oam.dev/project: my-project-2
|
||||
config.oam.dev/catalog: velacore-config
|
||||
stringData:
|
||||
username: admin
|
||||
password: admin
|
||||
|
|
|
|||
|
|
@ -55,27 +55,28 @@ type ImageService interface {
|
|||
}
|
||||
|
||||
type imageImpl struct {
|
||||
K8sClient client.Client `inject:"kubeClient"`
|
||||
K8sClient client.Client `inject:"kubeClient"`
|
||||
ConfigService ConfigService `inject:""`
|
||||
}
|
||||
|
||||
// ListImageRepos list the image repositories via user configuration
|
||||
func (i *imageImpl) ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error) {
|
||||
var secrets corev1.SecretList
|
||||
if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
}); err != nil {
|
||||
configs, err := i.ConfigService.ListConfigs(ctx, project, types.ImageRegistry, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var repos []v1.ImageRegistry
|
||||
for _, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
repos = append(repos, v1.ImageRegistry{
|
||||
Name: secret.Name,
|
||||
SecretName: secret.Name,
|
||||
Domain: secret.Labels[types.LabelConfigIdentifier],
|
||||
})
|
||||
for _, item := range configs {
|
||||
if item.Properties != nil {
|
||||
registry, ok := item.Properties["registry"].(string)
|
||||
if ok {
|
||||
repos = append(repos, v1.ImageRegistry{
|
||||
Name: item.Name,
|
||||
SecretName: item.Name,
|
||||
Domain: registry,
|
||||
Secret: item.Secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return repos, nil
|
||||
|
|
@ -88,56 +89,49 @@ func (i *imageImpl) GetImageInfo(ctx context.Context, project, secretName, image
|
|||
}
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
imageInfo.Message = "The image name is invalid"
|
||||
imageInfo.Message = "The image name is invalid."
|
||||
return imageInfo
|
||||
}
|
||||
registryDomain := ref.Context().RegistryStr()
|
||||
imageInfo.Registry = registryDomain
|
||||
var secrets corev1.SecretList
|
||||
if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
types.LabelConfigIdentifier: registryDomain,
|
||||
}); err != nil {
|
||||
log.Logger.Warnf("fail to list the docker registries, %s", err.Error())
|
||||
|
||||
registries, err := i.ListImageRepos(ctx, project)
|
||||
if err != nil {
|
||||
log.Logger.Warnf("fail to list the image registries:%s", err.Error())
|
||||
imageInfo.Message = "There is no registry."
|
||||
return imageInfo
|
||||
}
|
||||
var selectSecret []*corev1.Secret
|
||||
var selectSecretNames []string
|
||||
var selectRegistry []v1.ImageRegistry
|
||||
var selectRegistryNames []string
|
||||
// get info with specified secret
|
||||
if secretName != "" {
|
||||
for i, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
if secretName == secret.Name {
|
||||
selectSecret = append(selectSecret, &secrets.Items[i])
|
||||
if secret.Type == corev1.SecretTypeDockerConfigJson {
|
||||
selectSecretNames = append(selectSecretNames, secret.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
for i, registry := range registries {
|
||||
if secretName == registry.SecretName {
|
||||
selectRegistry = append(selectRegistry, registries[i])
|
||||
selectRegistryNames = append(selectRegistryNames, registry.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get info with the secret which match the registry domain
|
||||
if selectSecret == nil {
|
||||
for i, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
if secret.Labels[types.LabelConfigIdentifier] == registryDomain {
|
||||
selectSecret = append(selectSecret, &secrets.Items[i])
|
||||
if secret.Type == corev1.SecretTypeDockerConfigJson {
|
||||
selectSecretNames = append(selectSecretNames, secret.Name)
|
||||
}
|
||||
}
|
||||
if selectRegistry == nil {
|
||||
for i, registry := range registries {
|
||||
if registry.Domain == registryDomain {
|
||||
selectRegistry = append(selectRegistry, registries[i])
|
||||
selectRegistryNames = append(selectRegistryNames, registry.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
var username, password string
|
||||
var insecure = false
|
||||
var useHTTP = false
|
||||
imageInfo.SecretNames = selectSecretNames
|
||||
if len(selectSecret) > 0 {
|
||||
insecure, useHTTP, username, password = getAccountFromSecret(*selectSecret[0], registryDomain)
|
||||
imageInfo.SecretNames = selectRegistryNames
|
||||
for _, registry := range selectRegistry {
|
||||
if registry.Secret != nil {
|
||||
insecure, useHTTP, username, password = getAccountFromSecret(*registry.Secret, registryDomain)
|
||||
break
|
||||
}
|
||||
}
|
||||
err = getImageInfo(imageName, insecure, useHTTP, username, password, &imageInfo)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,16 +20,10 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/klog/v2"
|
||||
"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/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
|
|
@ -53,7 +47,7 @@ type ProjectService interface {
|
|||
DeleteProjectUser(ctx context.Context, projectName string, userName string) error
|
||||
UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error)
|
||||
Init(ctx context.Context) error
|
||||
GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error)
|
||||
ListTerraformProviders(ctx context.Context, projectName string) ([]*apisv1.TerraformProvider, error)
|
||||
}
|
||||
|
||||
type projectServiceImpl struct {
|
||||
|
|
@ -297,8 +291,7 @@ func (p *projectServiceImpl) DeleteProject(ctx context.Context, name string) err
|
|||
if err := p.Store.Delete(ctx, &model.Project{Name: name}); err != nil {
|
||||
return err
|
||||
}
|
||||
// delete config-sync application
|
||||
return destroySyncConfigsApp(ctx, p.K8sClient, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateProject create project
|
||||
|
|
@ -507,95 +500,21 @@ func (p *projectServiceImpl) UpdateProjectUser(ctx context.Context, projectName
|
|||
return ConvertProjectUserModel2Base(&projectUser, user), nil
|
||||
}
|
||||
|
||||
func (p *projectServiceImpl) GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error) {
|
||||
var (
|
||||
configs []*apisv1.Config
|
||||
legacyTerraformProviders []*apisv1.Config
|
||||
apps = &v1beta1.ApplicationList{}
|
||||
)
|
||||
if err := p.K8sClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
}); err != nil {
|
||||
func (p *projectServiceImpl) ListTerraformProviders(ctx context.Context, projectName string) ([]*apisv1.TerraformProvider, error) {
|
||||
l := &terraformapi.ProviderList{}
|
||||
if err := p.K8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if configType == types.TerraformProvider || configType == "" {
|
||||
// legacy providers
|
||||
var providers = &terraformapi.ProviderList{}
|
||||
if err := p.K8sClient.List(ctx, providers, client.InNamespace(types.DefaultAppNamespace)); err != nil {
|
||||
// this logic depends on the terraform addon, ignore the no matches kind error before the terraform addon is installed.
|
||||
if !meta.IsNoMatchError(err) {
|
||||
return nil, err
|
||||
}
|
||||
log.Logger.Infof("terraform Provider CRD is not installed")
|
||||
}
|
||||
for _, p := range providers.Items {
|
||||
if p.Labels[types.LabelConfigCatalog] == types.VelaCoreConfig {
|
||||
continue
|
||||
}
|
||||
t := p.CreationTimestamp.Time
|
||||
var status = configIsNotReady
|
||||
if p.Status.State == terraformtypes.ProviderIsReady {
|
||||
status = configIsReady
|
||||
}
|
||||
legacyTerraformProviders = append(legacyTerraformProviders, &apisv1.Config{
|
||||
Name: p.Name,
|
||||
CreatedTime: &t,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
var res []*apisv1.TerraformProvider
|
||||
for _, provider := range l.Items {
|
||||
res = append(res, &apisv1.TerraformProvider{
|
||||
Name: provider.Name,
|
||||
Region: provider.Spec.Region,
|
||||
Provider: provider.Spec.Provider,
|
||||
CreateTime: provider.CreationTimestamp.Time,
|
||||
})
|
||||
}
|
||||
|
||||
switch configType {
|
||||
case types.TerraformProvider:
|
||||
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], types.TerraformComponentPrefix) {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
case "":
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if appProject != "" && appProject != projectName {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
configs = append(configs, legacyTerraformProviders...)
|
||||
case types.DexConnector, types.HelmRepository, types.ImageRegistry:
|
||||
t := strings.ReplaceAll(configType, "config-", "")
|
||||
for _, a := range apps.Items {
|
||||
appProject := a.Labels[types.LabelConfigProject]
|
||||
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) {
|
||||
continue
|
||||
}
|
||||
if a.Labels[types.LabelConfigType] == t {
|
||||
configs = append(configs, retrieveConfigFromApplication(a, appProject))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported config type")
|
||||
}
|
||||
|
||||
for i, c := range configs {
|
||||
if c.ConfigType != "" {
|
||||
d := &v1beta1.ComponentDefinition{}
|
||||
err := p.K8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: c.ConfigType}, d)
|
||||
if err != nil {
|
||||
klog.InfoS("failed to get component definition", "ComponentDefinition", configType, "err", err)
|
||||
} else {
|
||||
configs[i].ConfigTypeAlias = DefinitionAlias(d.Annotations)
|
||||
}
|
||||
}
|
||||
}
|
||||
return configs, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ConvertProjectModel2Base convert project model to base struct
|
||||
|
|
@ -628,28 +547,6 @@ func ConvertProjectUserModel2Base(user *model.ProjectUser, userModel *model.User
|
|||
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],
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestProjectService create the project service instance for testing
|
||||
func NewTestProjectService(ds datastore.DataStore, c client.Client) ProjectService {
|
||||
targetImpl := &targetServiceImpl{K8sClient: c, Store: ds}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,8 @@ import (
|
|||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
|
|
@ -158,18 +155,6 @@ var _ = Describe("Test project service functions", func() {
|
|||
Name: "test-project",
|
||||
Description: "this is a project description",
|
||||
}
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-test-project",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Type: "aaa",
|
||||
}},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.TODO(), app1)).Should(BeNil())
|
||||
_, err := projectService.CreateProject(context.TODO(), req)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
|
|
@ -264,19 +249,6 @@ var _ = Describe("Test project service functions", func() {
|
|||
Name: "test-project",
|
||||
Description: "this is a project description",
|
||||
}
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-sync-test-project",
|
||||
Namespace: "vela-system",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Type: "aaa",
|
||||
}},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.TODO(), app1)).Should(BeNil())
|
||||
|
||||
_, err := projectService.CreateProject(context.TODO(), req)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
|
|||
Resources: []string{
|
||||
"project:{projectName}",
|
||||
"project:{projectName}/config:*",
|
||||
"project:{projectName}/provider:*",
|
||||
"project:{projectName}/role:*",
|
||||
"project:{projectName}/projectUser:*",
|
||||
"project:{projectName}/permission:*",
|
||||
|
|
@ -90,7 +91,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
|
|||
{
|
||||
Name: "configuration-read",
|
||||
Alias: "Environment Management",
|
||||
Resources: []string{"project:{projectName}/config:*"},
|
||||
Resources: []string{"project:{projectName}/config:*", "project:{projectName}/provider:*"},
|
||||
Actions: []string{"list", "detail"},
|
||||
Effect: "Allow",
|
||||
Scope: "project",
|
||||
|
|
@ -163,9 +164,9 @@ var defaultPlatformPermission = []*model.PermissionTemplate{
|
|||
Scope: "platform",
|
||||
},
|
||||
{
|
||||
Name: "integration-management",
|
||||
Alias: "Integration Management",
|
||||
Resources: []string{"configType:*/*"},
|
||||
Name: "config-management",
|
||||
Alias: "Config Management",
|
||||
Resources: []string{"config:*/*"},
|
||||
Actions: []string{"*"},
|
||||
Effect: "Allow",
|
||||
Scope: "platform",
|
||||
|
|
@ -229,7 +230,10 @@ var ResourceMaps = map[string]resourceMetadata{
|
|||
pathName: "userName",
|
||||
},
|
||||
"applicationTemplate": {},
|
||||
"config": {},
|
||||
"config": {
|
||||
pathName: "configName",
|
||||
},
|
||||
"provider": {},
|
||||
},
|
||||
pathName: "projectName",
|
||||
},
|
||||
|
|
@ -267,7 +271,9 @@ var ResourceMaps = map[string]resourceMetadata{
|
|||
},
|
||||
},
|
||||
},
|
||||
"cloudshell": {},
|
||||
"cloudshell": {},
|
||||
"config": {},
|
||||
"configTemplate": {},
|
||||
}
|
||||
|
||||
var existResourcePaths = convert(ResourceMaps)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ var _ = BeforeSuite(func(done Done) {
|
|||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
CRDDirectoryPaths: []string{"../../../../charts/vela-core/crds"},
|
||||
CRDDirectoryPaths: []string{"../../../../charts/vela-core/crds", "./testdata/crds"},
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
|
@ -138,7 +139,7 @@ func (u systemInfoServiceImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
|
|||
VelaAddress: sysInfo.VelaAddress,
|
||||
Connectors: connectors,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("fail to generate the dex config: %w", err)
|
||||
}
|
||||
}
|
||||
err = u.Store.Put(ctx, &modifiedInfo)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.0
|
||||
creationTimestamp: null
|
||||
name: providers.terraform.core.oam.dev
|
||||
spec:
|
||||
group: terraform.core.oam.dev
|
||||
names:
|
||||
kind: Provider
|
||||
listKind: ProviderList
|
||||
plural: providers
|
||||
singular: provider
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.state
|
||||
name: STATE
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Provider is the Schema for the providers 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: ProviderSpec defines the desired state of Provider.
|
||||
properties:
|
||||
credentials:
|
||||
description: Credentials required to authenticate to this provider.
|
||||
properties:
|
||||
secretRef:
|
||||
description: A SecretRef is a reference to a secret key that contains
|
||||
the credentials that must be used to connect to the provider.
|
||||
properties:
|
||||
key:
|
||||
description: The key to select.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the secret.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the secret.
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- name
|
||||
type: object
|
||||
source:
|
||||
description: Source of the provider credentials.
|
||||
enum:
|
||||
- None
|
||||
- Secret
|
||||
- InjectedIdentity
|
||||
- Environment
|
||||
- Filesystem
|
||||
type: string
|
||||
required:
|
||||
- source
|
||||
type: object
|
||||
provider:
|
||||
description: Provider is the cloud service provider, like `alibaba`
|
||||
type: string
|
||||
region:
|
||||
description: Region is cloud provider's region
|
||||
type: string
|
||||
required:
|
||||
- credentials
|
||||
- provider
|
||||
type: object
|
||||
status:
|
||||
description: ProviderStatus defines the observed state of Provider.
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
state:
|
||||
description: ProviderState is the type for Provider state
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
)
|
||||
|
||||
// ConfigAPIInterface returns config web service
|
||||
|
|
@ -37,67 +38,55 @@ type configAPIInterface struct {
|
|||
|
||||
func (s *configAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/config_types").
|
||||
ws.Path(versionPrefix+"/configs").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for configuration management")
|
||||
Doc("api for config management")
|
||||
|
||||
tags := []string{"config"}
|
||||
|
||||
ws.Route(ws.GET("/").To(s.listConfigTypes).
|
||||
Doc("list all config types").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("configType", "list")).
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
|
||||
Returns(200, "OK", []apis.ConfigType{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]apis.ConfigType{}))
|
||||
|
||||
ws.Route(ws.GET("/{configType}").To(s.getConfigType).
|
||||
Doc("get a config type").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("configType", "get")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Returns(200, "OK", apis.ConfigType{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigType{}))
|
||||
|
||||
ws.Route(ws.POST("/{configType}").To(s.createConfig).
|
||||
ws.Route(ws.POST("/").To(s.createConfig).
|
||||
Doc("create or update a config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("configType/config", "create")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Filter(s.RbacService.CheckPerm("config", "create")).
|
||||
Reads(apis.CreateConfigRequest{}).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(200, "OK", apis.Config{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Returns(404, "Not Found", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
Writes(apis.Config{}))
|
||||
|
||||
ws.Route(ws.GET("/{configType}/configs").To(s.getConfigs).
|
||||
Doc("get configs from a config type").
|
||||
ws.Route(ws.GET("/").To(s.getConfigs).
|
||||
Doc("list all configs that belong to the system scope").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("configType/config", "list")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config").DataType("string")).
|
||||
Filter(s.RbacService.CheckPerm("config", "list")).
|
||||
Param(ws.QueryParameter("template", "the name of the template").DataType("string")).
|
||||
Returns(200, "OK", apis.ListConfigResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListConfigResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{configName}").To(s.getConfig).
|
||||
Doc("detail a config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("config", "get")).
|
||||
Param(ws.PathParameter("configName", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", []*apis.Config{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigType{}))
|
||||
Writes(apis.Config{}))
|
||||
|
||||
ws.Route(ws.GET("/{configType}/configs/{name}").To(s.getConfig).
|
||||
Doc("get a config from a config type").
|
||||
ws.Route(ws.PUT("/{configName}").To(s.updateConfig).
|
||||
Doc("update a config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("configType/config", "get")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", []*apis.Config{}).
|
||||
Filter(s.RbacService.CheckPerm("config", "update")).
|
||||
Param(ws.PathParameter("configName", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", []*apis.UpdateConfigRequest{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigType{}))
|
||||
Writes(apis.UpdateConfigRequest{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{configType}/configs/{name}").To(s.deleteConfig).
|
||||
ws.Route(ws.DELETE("/{configName}").To(s.deleteConfig).
|
||||
Doc("delete a config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("configType/config", "delete")).
|
||||
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
|
||||
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
|
||||
Filter(s.RbacService.CheckPerm("config", "delete")).
|
||||
Param(ws.PathParameter("configName", "identifier of the config").DataType("string")).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Returns(404, "Not Found", bcode.Bcode{}).
|
||||
|
|
@ -107,21 +96,65 @@ func (s *configAPIInterface) GetWebServiceRoute() *restful.WebService {
|
|||
return ws
|
||||
}
|
||||
|
||||
func (s *configAPIInterface) listConfigTypes(req *restful.Request, res *restful.Response) {
|
||||
types, err := s.ConfigService.ListConfigTypes(req.Request.Context(), req.QueryParameter("query"))
|
||||
if len(types) == 0 && err != nil {
|
||||
// ConfigTemplateAPIInterface returns config web service
|
||||
func ConfigTemplateAPIInterface() Interface {
|
||||
return &configTemplateAPIInterface{}
|
||||
}
|
||||
|
||||
type configTemplateAPIInterface struct {
|
||||
ConfigService service.ConfigService `inject:""`
|
||||
RbacService service.RBACService `inject:""`
|
||||
}
|
||||
|
||||
func (s *configTemplateAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/config_templates").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for config management")
|
||||
|
||||
tags := []string{"config"}
|
||||
|
||||
ws.Route(ws.GET("/").To(s.listConfigTemplates).
|
||||
Doc("List all config templates from the system namespace").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("config", "list")).
|
||||
Returns(200, "OK", apis.ListConfigTemplateResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]apis.ListConfigTemplateResponse{}))
|
||||
|
||||
ws.Route(ws.GET("{templateName}").To(s.getConfigTemplate).
|
||||
Doc("Detail a template").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(s.RbacService.CheckPerm("config", "get")).
|
||||
Param(ws.PathParameter("templateName", "identifier of the config template").DataType("string")).
|
||||
Param(ws.QueryParameter("namespace", "the name of the namespace").DataType("string")).
|
||||
Returns(200, "OK", apis.ConfigTemplateDetail{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigTemplateDetail{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
|
||||
func (s *configTemplateAPIInterface) listConfigTemplates(req *restful.Request, res *restful.Response) {
|
||||
templates, err := s.ConfigService.ListTemplates(req.Request.Context(), "", "")
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(types)
|
||||
err = res.WriteEntity(apis.ListConfigTemplateResponse{Templates: templates})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configAPIInterface) getConfigType(req *restful.Request, res *restful.Response) {
|
||||
t, err := s.ConfigService.GetConfigType(req.Request.Context(), req.PathParameter("configType"))
|
||||
func (s *configTemplateAPIInterface) getConfigTemplate(req *restful.Request, res *restful.Response) {
|
||||
t, err := s.ConfigService.GetTemplate(req.Request.Context(), config.NamespacedName{
|
||||
Name: req.PathParameter("templateName"),
|
||||
Namespace: req.QueryParameter("namespace"),
|
||||
})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
|
|
@ -145,24 +178,47 @@ func (s *configAPIInterface) createConfig(req *restful.Request, res *restful.Res
|
|||
return
|
||||
}
|
||||
|
||||
err := s.ConfigService.CreateConfig(req.Request.Context(), createReq)
|
||||
config, err := s.ConfigService.CreateConfig(req.Request.Context(), "", createReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
if err := res.WriteEntity(config); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configAPIInterface) updateConfig(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var updateReq apis.UpdateConfigRequest
|
||||
if err := req.ReadEntity(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := s.ConfigService.UpdateConfig(req.Request.Context(), "", req.PathParameter("configName"), updateReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(config); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *configAPIInterface) getConfigs(req *restful.Request, res *restful.Response) {
|
||||
configs, err := s.ConfigService.GetConfigs(req.Request.Context(), req.PathParameter("configType"))
|
||||
configs, err := s.ConfigService.ListConfigs(req.Request.Context(), "", req.QueryParameter("template"), true)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(configs)
|
||||
err = res.WriteEntity(apis.ListConfigResponse{Configs: configs})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
|
|
@ -170,7 +226,7 @@ func (s *configAPIInterface) getConfigs(req *restful.Request, res *restful.Respo
|
|||
}
|
||||
|
||||
func (s *configAPIInterface) getConfig(req *restful.Request, res *restful.Response) {
|
||||
t, err := s.ConfigService.GetConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
|
||||
t, err := s.ConfigService.GetConfig(req.Request.Context(), "", req.PathParameter("configName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
|
|
@ -183,7 +239,7 @@ func (s *configAPIInterface) getConfig(req *restful.Request, res *restful.Respon
|
|||
}
|
||||
|
||||
func (s *configAPIInterface) deleteConfig(req *restful.Request, res *restful.Response) {
|
||||
err := s.ConfigService.DeleteConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
|
||||
err := s.ConfigService.DeleteConfig(req.Request.Context(), "", req.PathParameter("configName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
registryv1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
|
|
@ -31,6 +32,7 @@ import (
|
|||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/cloudprovider"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -190,27 +192,64 @@ type AddonArgsResponse struct {
|
|||
Args map[string]string `json:"args"`
|
||||
}
|
||||
|
||||
// ConfigType define the format for listing configuration types
|
||||
type ConfigType struct {
|
||||
Definitions []string `json:"definitions"`
|
||||
Alias string `json:"alias"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
// CreateConfigRequest is the request body to creates a config
|
||||
type CreateConfigRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
Template NamespacedName `json:"template"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateConfigRequest is the request body to update a config
|
||||
type UpdateConfigRequest struct {
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigTemplate define the format for listing configuration types
|
||||
type ConfigTemplate struct {
|
||||
Alias string `json:"alias"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Description string `json:"description"`
|
||||
Scope string `json:"scope"`
|
||||
Sensitive bool `json:"sensitive"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
}
|
||||
|
||||
// ConfigTemplateDetail define the format for detail the config template
|
||||
type ConfigTemplateDetail struct {
|
||||
ConfigTemplate
|
||||
APISchema *openapi3.Schema `json:"schema"`
|
||||
UISchema utils.UISchema `json:"uiSchema"`
|
||||
}
|
||||
|
||||
// Config define the metadata of a config
|
||||
type Config struct {
|
||||
ConfigType string `json:"configType"`
|
||||
ConfigTypeAlias string `json:"configTypeAlias"`
|
||||
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"`
|
||||
ApplicationStatus common.ApplicationPhase `json:"applicationStatus"`
|
||||
Status string `json:"status"`
|
||||
Template config.NamespacedName `json:"template"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Sensitive bool `json:"sensitive"`
|
||||
Project string `json:"project"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
CreatedTime *time.Time `json:"createdTime"`
|
||||
Properties map[string]interface{} `json:"properties,omitempty"`
|
||||
Shared bool `json:"shared"`
|
||||
Secret *corev1.Secret `json:"-"`
|
||||
Targets []*config.ClusterTargetStatus `json:"targets"`
|
||||
}
|
||||
|
||||
// ListConfigResponse is the response body for listing the configs
|
||||
type ListConfigResponse struct {
|
||||
Configs []*Config `json:"configs"`
|
||||
}
|
||||
|
||||
// ListConfigTemplateResponse is the response body for listing the config templates
|
||||
type ListConfigTemplateResponse struct {
|
||||
Templates []*ConfigTemplate `json:"templates"`
|
||||
}
|
||||
|
||||
// ImageResponse is the response for checking image
|
||||
|
|
@ -438,16 +477,6 @@ type CreateApplicationRequest struct {
|
|||
Component *CreateComponentRequest `json:"component"`
|
||||
}
|
||||
|
||||
// CreateConfigRequest is the request body to creates a config
|
||||
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"`
|
||||
}
|
||||
|
||||
// UpdateApplicationRequest update application base config
|
||||
type UpdateApplicationRequest struct {
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
|
|
@ -1443,9 +1472,10 @@ type ImageInfo struct {
|
|||
|
||||
// ImageRegistry the image repository info
|
||||
type ImageRegistry struct {
|
||||
Name string `json:"name"`
|
||||
SecretName string `json:"secretName"`
|
||||
Domain string `json:"domain"`
|
||||
Name string `json:"name"`
|
||||
SecretName string `json:"secretName"`
|
||||
Domain string `json:"domain"`
|
||||
Secret *corev1.Secret `json:"-"`
|
||||
}
|
||||
|
||||
// ListImageRegistryResponse the response struct of listing the image registries
|
||||
|
|
@ -1458,3 +1488,42 @@ type CloudShellPrepareResponse struct {
|
|||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ConfigType define the format for listing configuration types
|
||||
type ConfigType struct {
|
||||
Definitions []string `json:"definitions"`
|
||||
Alias string `json:"alias"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// TerraformProvider define the metadata of a terraform provider
|
||||
type TerraformProvider struct {
|
||||
Name string `json:"name"`
|
||||
Region string `json:"region"`
|
||||
Provider string `json:"provider"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
}
|
||||
|
||||
// ListTerraformProviderResponse is the response body for listing the terraform provider
|
||||
type ListTerraformProviderResponse struct {
|
||||
Providers []*TerraformProvider `json:"providers"`
|
||||
}
|
||||
|
||||
// NamespacedName the name is required and the namespace is optional
|
||||
type NamespacedName struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace" optional:"true"`
|
||||
}
|
||||
|
||||
// CreateConfigDistributionRequest the request body of applying the distribution job.
|
||||
type CreateConfigDistributionRequest struct {
|
||||
Name string `json:"name"`
|
||||
Configs []*NamespacedName `json:"configs"`
|
||||
Targets []*ClusterTarget `json:"targets"`
|
||||
}
|
||||
|
||||
// ListConfigDistributionResponse is the response body for listing the distribution
|
||||
type ListConfigDistributionResponse struct {
|
||||
Distributions []*config.Distribution `json:"distributions"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ func InitAPIBean() []interface{} {
|
|||
|
||||
// Config management
|
||||
RegisterAPIInterface(ConfigAPIInterface())
|
||||
RegisterAPIInterface(ConfigTemplateAPIInterface())
|
||||
|
||||
// Resources
|
||||
RegisterAPIInterface(NewClusterAPIInterface())
|
||||
|
|
|
|||
|
|
@ -23,5 +23,5 @@ import (
|
|||
)
|
||||
|
||||
func TestInitAPIBean(t *testing.T) {
|
||||
assert.Equal(t, len(InitAPIBean()), 22)
|
||||
assert.Equal(t, len(InitAPIBean()), 23)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,12 +25,14 @@ import (
|
|||
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
)
|
||||
|
||||
type projectAPIInterface struct {
|
||||
RbacService service.RBACService `inject:""`
|
||||
ProjectService service.ProjectService `inject:""`
|
||||
TargetService service.TargetService `inject:""`
|
||||
ConfigService service.ConfigService `inject:""`
|
||||
}
|
||||
|
||||
// NewProjectAPIInterface new project APIInterface
|
||||
|
|
@ -193,15 +195,115 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService {
|
|||
Returns(200, "OK", []apis.PermissionBase{}).
|
||||
Writes([]apis.PermissionBase{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/config_templates").To(n.getConfigTemplates).
|
||||
Doc("get the templates which are in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "list")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Param(ws.QueryParameter("namespace", "the namespace of the template").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.ListConfigTemplateResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListConfigTemplateResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/config_templates/{templateName}").To(n.getConfigTemplate).
|
||||
Doc("Detail a template").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "get")).
|
||||
Param(ws.PathParameter("templateName", "identifier of the config template").DataType("string")).
|
||||
Param(ws.QueryParameter("namespace", "the name of the namespace").DataType("string")).
|
||||
Returns(200, "OK", apis.ConfigTemplateDetail{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ConfigTemplateDetail{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/configs").To(n.getConfigs).
|
||||
Doc("get configs which are in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "list")).
|
||||
Param(ws.QueryParameter("configType", "config type").DataType("string")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
|
||||
Returns(200, "OK", []*apis.Config{}).
|
||||
Param(ws.QueryParameter("template", "the template name").DataType("string")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.ListConfigResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]*apis.Config{}))
|
||||
Writes(apis.ListConfigResponse{}))
|
||||
|
||||
ws.Route(ws.POST("/{projectName}/configs").To(n.createConfig).
|
||||
Doc("create a config in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "list")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Reads(apis.CreateConfigRequest{}).
|
||||
Returns(200, "OK", apis.Config{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.Config{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{projectName}/configs/{configName}").To(n.deleteConfig).
|
||||
Doc("delete a config from a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "list")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Param(ws.PathParameter("configName", "identifier of the config").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Route(ws.PUT("/{projectName}/configs/{configName}").To(n.updateConfig).
|
||||
Doc("update a config in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "list")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Param(ws.PathParameter("configName", "identifier of the config").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.Config{}).
|
||||
Reads(apis.UpdateConfigRequest{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.Config{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/configs/{configName}").To(n.detailConfig).
|
||||
Doc("detail a config in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "list")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Param(ws.PathParameter("configName", "identifier of the config").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.Config{}).
|
||||
Reads(apis.UpdateConfigRequest{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.Config{}))
|
||||
|
||||
ws.Route(ws.POST("/{projectName}/distributions").To(n.applyDistribution).
|
||||
Doc("apply the distribution job of the config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "distribute")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Reads(apis.CreateConfigDistributionRequest{}).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/distributions").To(n.listDistributions).
|
||||
Doc("list the distribution jobs of the config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "distribute")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.ListConfigDistributionResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListConfigDistributionResponse{}))
|
||||
|
||||
ws.Route(ws.DELETE("/{projectName}/distributions/{distributionName}").To(n.deleteDistribution).
|
||||
Doc("delete a distribution job of the config").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/config", "distribute")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Param(ws.PathParameter("distributionName", "identifier of the distribution").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.EmptyResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.EmptyResponse{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/providers").To(n.getProviders).
|
||||
Doc("get providers which are in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/provider", "list")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.ListTerraformProviderResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListTerraformProviderResponse{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
|
|
@ -570,20 +672,179 @@ func (n *projectAPIInterface) deleteProjectPermission(req *restful.Request, res
|
|||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Response) {
|
||||
configs, err := n.ProjectService.GetConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("configType"))
|
||||
func (n *projectAPIInterface) getConfigTemplates(req *restful.Request, res *restful.Response) {
|
||||
templates, err := n.ConfigService.ListTemplates(req.Request.Context(), req.PathParameter("projectName"), "project")
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if configs == nil {
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(configs)
|
||||
err = res.WriteEntity(apis.ListConfigTemplateResponse{Templates: templates})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) getConfigTemplate(req *restful.Request, res *restful.Response) {
|
||||
t, err := n.ConfigService.GetTemplate(req.Request.Context(), config.NamespacedName{
|
||||
Name: req.PathParameter("templateName"),
|
||||
Namespace: req.QueryParameter("namespace"),
|
||||
})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(t)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Response) {
|
||||
configs, err := n.ConfigService.ListConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("template"), false)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(apis.ListConfigResponse{Configs: configs})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) createConfig(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var createReq apis.CreateConfigRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
config, err := n.ConfigService.CreateConfig(req.Request.Context(), req.PathParameter("projectName"), createReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(config)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) updateConfig(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var updateReq apis.UpdateConfigRequest
|
||||
if err := req.ReadEntity(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
config, err := n.ConfigService.UpdateConfig(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("configName"), updateReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(config)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) detailConfig(req *restful.Request, res *restful.Response) {
|
||||
config, err := n.ConfigService.GetConfig(req.Request.Context(),
|
||||
req.PathParameter("projectName"), req.PathParameter("configName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(config)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) deleteConfig(req *restful.Request, res *restful.Response) {
|
||||
err := n.ConfigService.DeleteConfig(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("configName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(apis.EmptyResponse{})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) getProviders(req *restful.Request, res *restful.Response) {
|
||||
providers, err := n.ProjectService.ListTerraformProviders(req.Request.Context(), req.PathParameter("projectName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(apis.ListTerraformProviderResponse{Providers: providers})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) applyDistribution(req *restful.Request, res *restful.Response) {
|
||||
// Verify the validity of parameters
|
||||
var createReq apis.CreateConfigDistributionRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := validate.Struct(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
// Call the domain layer code
|
||||
err := n.ConfigService.CreateConfigDistribution(req.Request.Context(), req.PathParameter("projectName"), createReq)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write back response data
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) listDistributions(req *restful.Request, res *restful.Response) {
|
||||
distributions, err := n.ConfigService.ListConfigDistributions(req.Request.Context(), req.PathParameter("projectName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(apis.ListConfigDistributionResponse{Distributions: distributions})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) deleteDistribution(req *restful.Request, res *restful.Response) {
|
||||
err := n.ConfigService.DeleteConfigDistribution(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("distributionName"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(apis.EmptyResponse{})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import (
|
|||
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/container"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
pkgconfig "github.com/oam-dev/kubevela/pkg/config"
|
||||
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
|
@ -116,6 +117,11 @@ func (s *restServer) buildIoCContainer() error {
|
|||
return fmt.Errorf("fail to provides the apply bean to the container: %w", err)
|
||||
}
|
||||
|
||||
factory := pkgconfig.NewConfigFactory(kubeClient)
|
||||
if err := s.beanContainer.ProvideWithName("configFactory", factory); err != nil {
|
||||
return fmt.Errorf("fail to provides the config factory bean to the container: %w", err)
|
||||
}
|
||||
|
||||
// domain
|
||||
if err := s.beanContainer.Provides(service.InitServiceBean(s.cfg)...); err != nil {
|
||||
return fmt.Errorf("fail to provides the service bean to the container: %w", err)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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 bcode
|
||||
|
||||
var (
|
||||
// ErrSensitiveConfig means the config can not be read
|
||||
ErrSensitiveConfig = NewBcode(400, 16001, "the config is sensitive")
|
||||
|
||||
// ErrNoConfigOrTarget means there is no target or config when creating the distribution.
|
||||
ErrNoConfigOrTarget = NewBcode(400, 16002, "you must specify the config name and destination to distribute")
|
||||
|
||||
// ErrConfigExist means the config is exist
|
||||
ErrConfigExist = NewBcode(400, 16003, "the config name is exist")
|
||||
|
||||
// ErrChangeTemplate the template of the config can not be change
|
||||
ErrChangeTemplate = NewBcode(400, 16004, "the template of the config can not be change")
|
||||
|
||||
// ErrTemplateNotFound means the template is not exist
|
||||
ErrTemplateNotFound = NewBcode(404, 16005, "the template is not exist")
|
||||
|
||||
// ErrConfigNotFound means the config is not exist
|
||||
ErrConfigNotFound = NewBcode(404, 16006, "the config is not exist")
|
||||
|
||||
// ErrNotFoundDistribution means the distribution is not exist
|
||||
ErrNotFoundDistribution = NewBcode(404, 16007, "the distribution is not exist")
|
||||
)
|
||||
|
|
@ -23,6 +23,8 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
)
|
||||
|
||||
// GetDexConnectors returns the dex connectors for Dex connector controller
|
||||
|
|
@ -36,15 +38,18 @@ func GetDexConnectors(ctx context.Context, k8sClient client.Client) ([]map[strin
|
|||
for i, s := range secrets.Items {
|
||||
var data map[string]interface{}
|
||||
key := s.Labels[types.LabelConfigSubType]
|
||||
err := json.Unmarshal(s.Data[key], &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connectors[i] = map[string]interface{}{
|
||||
"type": s.Labels[types.LabelConfigSubType],
|
||||
"id": s.Name,
|
||||
"name": s.Name,
|
||||
"config": data,
|
||||
if _, ok := s.Data[key]; ok {
|
||||
err := json.Unmarshal(s.Data[key], &data)
|
||||
if err != nil {
|
||||
log.Logger.Warnf("the dex connector %s is invalid", s.Name)
|
||||
continue
|
||||
}
|
||||
connectors[i] = map[string]interface{}{
|
||||
"type": s.Labels[types.LabelConfigSubType],
|
||||
"id": s.Name,
|
||||
"name": s.Name,
|
||||
"config": data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func TestGetDexConnectors(t *testing.T) {
|
|||
Labels: map[string]string{
|
||||
"app.oam.dev/source-of-truth": "from-inner-system",
|
||||
"config.oam.dev/catalog": "velacore-config",
|
||||
"config.oam.dev/type": "config-dex-connector",
|
||||
"config.oam.dev/type": "dex-connector",
|
||||
"config.oam.dev/sub-type": "ldap",
|
||||
"project": "abc",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -137,3 +137,21 @@ func (f *deferredFactory) Client() client.Client {
|
|||
}
|
||||
return f.Factory.Client()
|
||||
}
|
||||
|
||||
type testFactory struct {
|
||||
cfg *rest.Config
|
||||
cli client.Client
|
||||
}
|
||||
|
||||
// NewTestFactory new a factory for the testing
|
||||
func NewTestFactory(cfg *rest.Config,
|
||||
cli client.Client) Factory {
|
||||
return &testFactory{cli: cli, cfg: cfg}
|
||||
}
|
||||
|
||||
func (t *testFactory) Client() client.Client {
|
||||
return t.cli
|
||||
}
|
||||
func (t *testFactory) Config() *rest.Config {
|
||||
return t.cfg
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
. "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/utils/common"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var ctl *gomock.Controller
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
By("Bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{"../../charts/vela-core/crds"},
|
||||
}
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
By("Create the k8s client")
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("Tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
ctl = gomock.NewController(t)
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Config Suite")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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 context
|
||||
|
||||
import "context"
|
||||
|
||||
// DefaultContext the default context template
|
||||
var DefaultContext = []byte(`
|
||||
context: {
|
||||
name: string
|
||||
namespace: string
|
||||
}
|
||||
`)
|
||||
|
||||
// ConfigRenderContext the default context values for render the config
|
||||
type ConfigRenderContext struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// ReadConfigProvider the provide function for reading the config properties
|
||||
type ReadConfigProvider func(ctx context.Context, namespace string, name string) (map[string]interface{}, error)
|
||||
|
|
@ -0,0 +1,889 @@
|
|||
/*
|
||||
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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "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/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
pkgtypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"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/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
|
||||
icontext "github.com/oam-dev/kubevela/pkg/config/context"
|
||||
"github.com/oam-dev/kubevela/pkg/config/writer"
|
||||
"github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
// SaveInputPropertiesKey define the key name for saving the input properties in the secret.
|
||||
const SaveInputPropertiesKey = "input-properties"
|
||||
|
||||
// SaveObjectReferenceKey define the key name for saving the outputs objects reference metadata in the secret.
|
||||
const SaveObjectReferenceKey = "objects-reference"
|
||||
|
||||
// SaveExpandedWriterKey define the key name for saving the expanded writer config
|
||||
const SaveExpandedWriterKey = "expanded-writer"
|
||||
|
||||
// SaveSchemaKey define the key name for saving the API schema
|
||||
const SaveSchemaKey = "schema"
|
||||
|
||||
// SaveTemplateKey define the key name for saving the config-template
|
||||
const SaveTemplateKey = "template"
|
||||
|
||||
// TemplateConfigMapNamePrefix the prefix of the configmap name.
|
||||
const TemplateConfigMapNamePrefix = "config-template-"
|
||||
|
||||
// ErrSensitiveConfig means this config can not be read directly.
|
||||
var ErrSensitiveConfig = errors.New("the config is sensitive")
|
||||
|
||||
// ErrNoConfigOrTarget means the config or the target is empty.
|
||||
var ErrNoConfigOrTarget = errors.New("you must specify the config name and destination to distribute")
|
||||
|
||||
// ErrNotFoundDistribution means the app of the distribution is not exist.
|
||||
var ErrNotFoundDistribution = errors.New("the distribution is not found")
|
||||
|
||||
// ErrConfigExist means the config is exist.
|
||||
var ErrConfigExist = errors.New("the config is exist")
|
||||
|
||||
// ErrConfigNotFound means the config is not exist
|
||||
var ErrConfigNotFound = errors.New("the config is not exist")
|
||||
|
||||
// ErrTemplateNotFound means the template is not exist
|
||||
var ErrTemplateNotFound = errors.New("the template is not exist")
|
||||
|
||||
// NamespacedName the namespace and name model
|
||||
type NamespacedName struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// Template This is the spec of the config template, parse from the cue script.
|
||||
type Template struct {
|
||||
NamespacedName
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
// Scope defines the usage scope of the configuration template. Provides two options: System or Namespace
|
||||
// System: The system users could use this template, and the config secret will save in the vela-system namespace.
|
||||
// Namespace: The config secret will save in the target namespace, such as this namespace belonging to one project.
|
||||
Scope string `json:"scope"`
|
||||
// Sensitive means this config config can not be read from the API or the workflow step, only support the safe way, such as Secret.
|
||||
Sensitive bool `json:"sensitive"`
|
||||
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
|
||||
Template script.CUE `json:"template"`
|
||||
|
||||
ExpandedWriter writer.ExpandedWriterConfig `json:"expandedWriter"`
|
||||
|
||||
Schema *openapi3.Schema `json:"schema"`
|
||||
|
||||
ConfigMap *v1.ConfigMap `json:"-"`
|
||||
}
|
||||
|
||||
// Metadata users should provide this model.
|
||||
type Metadata struct {
|
||||
NamespacedName
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Properties map[string]interface{} `json:"properties"`
|
||||
}
|
||||
|
||||
// Config this is the config model, generated from the template and properties.
|
||||
type Config struct {
|
||||
Metadata
|
||||
CreateTime time.Time
|
||||
Template Template `json:"template"`
|
||||
// Secret this is default output way.
|
||||
Secret *v1.Secret `json:"secret"`
|
||||
|
||||
// ExpandedWriterData
|
||||
ExpandedWriterData *writer.ExpandedWriterData `json:"expandedWriterData"`
|
||||
|
||||
// OutputObjects this means users could define other objects.
|
||||
// This field assign value only on config render stage.
|
||||
OutputObjects map[string]*unstructured.Unstructured
|
||||
|
||||
// ObjectReferences correspond OutputObjects
|
||||
ObjectReferences []v1.ObjectReference
|
||||
|
||||
Targets []*ClusterTargetStatus
|
||||
}
|
||||
|
||||
// ClusterTargetStatus merge the status of the distribution
|
||||
type ClusterTargetStatus struct {
|
||||
ClusterTarget
|
||||
Status string `json:"status"`
|
||||
Application NamespacedName `json:"application"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ClusterTarget kubernetes delivery target
|
||||
type ClusterTarget struct {
|
||||
ClusterName string `json:"clusterName"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// Distribution the config distribution model
|
||||
type Distribution struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
CreatedTime time.Time `json:"createdTime"`
|
||||
Configs []*NamespacedName `json:"configs"`
|
||||
Targets []*ClusterTarget `json:"targets"`
|
||||
Application pkgtypes.NamespacedName `json:"application"`
|
||||
Status common.AppStatus `json:"status"`
|
||||
}
|
||||
|
||||
// CreateDistributionSpec the spec of the distribution
|
||||
type CreateDistributionSpec struct {
|
||||
Configs []*NamespacedName
|
||||
Targets []*ClusterTarget
|
||||
}
|
||||
|
||||
// Factory handle the config
|
||||
type Factory interface {
|
||||
ParseTemplate(defaultName string, content []byte) (*Template, error)
|
||||
ParseConfig(ctx context.Context, template NamespacedName, meta Metadata) (*Config, error)
|
||||
|
||||
LoadTemplate(ctx context.Context, name, ns string) (*Template, error)
|
||||
CreateOrUpdateConfigTemplate(ctx context.Context, ns string, it *Template) error
|
||||
DeleteTemplate(ctx context.Context, ns, name string) error
|
||||
ListTemplates(ctx context.Context, ns, scope string) ([]*Template, error)
|
||||
|
||||
ReadConfig(ctx context.Context, namespace, name string) (map[string]interface{}, error)
|
||||
GetConfig(ctx context.Context, namespace, name string, withStatus bool) (*Config, error)
|
||||
ListConfigs(ctx context.Context, namespace, template, scope string, withStatus bool) ([]*Config, error)
|
||||
DeleteConfig(ctx context.Context, namespace, name string) error
|
||||
CreateOrUpdateConfig(ctx context.Context, i *Config, ns string) error
|
||||
|
||||
CreateOrUpdateDistribution(ctx context.Context, ns, name string, ads *CreateDistributionSpec) error
|
||||
ListDistributions(ctx context.Context, ns string) ([]*Distribution, error)
|
||||
DeleteDistribution(ctx context.Context, ns, name string) error
|
||||
MergeDistributionStatus(ctx context.Context, config *Config, namespace string) error
|
||||
}
|
||||
|
||||
// NewConfigFactory create a config factory instance
|
||||
func NewConfigFactory(cli client.Client) Factory {
|
||||
return &kubeConfigFactory{cli: cli, apiApply: apply.NewAPIApplicator(cli)}
|
||||
}
|
||||
|
||||
type kubeConfigFactory struct {
|
||||
cli client.Client
|
||||
apiApply *apply.APIApplicator
|
||||
}
|
||||
|
||||
// ParseTemplate parse a config template instance form the cue script
|
||||
func (k *kubeConfigFactory) ParseTemplate(defaultName string, content []byte) (*Template, error) {
|
||||
cueScript := script.BuildCUEScriptWithDefaultContext(icontext.DefaultContext, content)
|
||||
value, err := cueScript.ParseToValue(false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the cue script is invalid:%w", err)
|
||||
}
|
||||
name, err := value.GetString("metadata", "name")
|
||||
if err != nil {
|
||||
if defaultName == "" {
|
||||
return nil, fmt.Errorf("fail to get the name from the template metadata: %w", err)
|
||||
}
|
||||
}
|
||||
if defaultName != "" {
|
||||
name = defaultName
|
||||
}
|
||||
schema, err := cueScript.ParsePropertiesToSchema()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the properties of the cue script is invalid:%w", err)
|
||||
}
|
||||
alias, err := value.GetString("metadata", "alias")
|
||||
if err != nil && !IsFieldNotExist(err) {
|
||||
klog.Warningf("fail to get the alias from the template metadata: %s", err.Error())
|
||||
}
|
||||
scope, err := value.GetString("metadata", "scope")
|
||||
if err != nil && !IsFieldNotExist(err) {
|
||||
klog.Warningf("fail to get the scope from the template metadata: %s", err.Error())
|
||||
}
|
||||
sensitive, err := value.GetBool("metadata", "sensitive")
|
||||
if err != nil && !IsFieldNotExist(err) {
|
||||
klog.Warningf("fail to get the sensitive from the template metadata: %s", err.Error())
|
||||
}
|
||||
templateValue, err := value.LookupValue("template")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template := &Template{
|
||||
NamespacedName: NamespacedName{
|
||||
Name: name,
|
||||
},
|
||||
Alias: alias,
|
||||
Scope: scope,
|
||||
Sensitive: sensitive,
|
||||
Template: cueScript,
|
||||
Schema: schema,
|
||||
ExpandedWriter: writer.ParseExpandedWriterConfig(templateValue),
|
||||
}
|
||||
|
||||
var configmap v1.ConfigMap
|
||||
configmap.Name = TemplateConfigMapNamePrefix + template.Name
|
||||
|
||||
configmap.Data = map[string]string{
|
||||
SaveTemplateKey: string(template.Template),
|
||||
}
|
||||
if template.Schema != nil {
|
||||
data, err := yaml.Marshal(template.Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configmap.Data[SaveSchemaKey] = string(data)
|
||||
}
|
||||
data, err := yaml.Marshal(template.ExpandedWriter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configmap.Data[SaveExpandedWriterKey] = string(data)
|
||||
configmap.Labels = map[string]string{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigScope: template.Scope,
|
||||
}
|
||||
configmap.Annotations = map[string]string{
|
||||
types.AnnotationConfigDescription: template.Description,
|
||||
types.AnnotationConfigAlias: template.Alias,
|
||||
types.AnnotationConfigSensitive: fmt.Sprintf("%t", template.Sensitive),
|
||||
}
|
||||
template.ConfigMap = &configmap
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// IsFieldNotExist check whether the error type is the field not found
|
||||
func IsFieldNotExist(err error) bool {
|
||||
return strings.Contains(err.Error(), "not exist")
|
||||
}
|
||||
|
||||
// CreateOrUpdateConfigTemplate parse and update the config template
|
||||
func (k *kubeConfigFactory) CreateOrUpdateConfigTemplate(ctx context.Context, ns string, it *Template) error {
|
||||
if ns != "" {
|
||||
it.ConfigMap.Namespace = ns
|
||||
}
|
||||
return k.apiApply.Apply(ctx, it.ConfigMap, apply.DisableUpdateAnnotation(), apply.Quiet())
|
||||
}
|
||||
|
||||
func convertConfigMap2Template(cm v1.ConfigMap) (*Template, error) {
|
||||
if cm.Labels == nil || cm.Annotations == nil {
|
||||
return nil, fmt.Errorf("this configmap is not a valid config-template")
|
||||
}
|
||||
it := &Template{
|
||||
NamespacedName: NamespacedName{
|
||||
Name: strings.Replace(cm.Name, TemplateConfigMapNamePrefix, "", 1),
|
||||
Namespace: cm.Namespace,
|
||||
},
|
||||
Alias: cm.Annotations[types.AnnotationConfigAlias],
|
||||
Description: cm.Annotations[types.AnnotationConfigDescription],
|
||||
Sensitive: cm.Annotations[types.AnnotationConfigSensitive] == "true",
|
||||
Scope: cm.Labels[types.LabelConfigScope],
|
||||
CreateTime: cm.CreationTimestamp.Time,
|
||||
Template: script.CUE(cm.Data[SaveTemplateKey]),
|
||||
}
|
||||
if cm.Data[SaveSchemaKey] != "" {
|
||||
var schema openapi3.Schema
|
||||
err := yaml.Unmarshal([]byte(cm.Data[SaveSchemaKey]), &schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to parse the schema: %w", err)
|
||||
}
|
||||
it.Schema = &schema
|
||||
}
|
||||
if cm.Data[SaveExpandedWriterKey] != "" {
|
||||
var config writer.ExpandedWriterConfig
|
||||
err := yaml.Unmarshal([]byte(cm.Data[SaveExpandedWriterKey]), &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to parse the schema: %w", err)
|
||||
}
|
||||
it.ExpandedWriter = config
|
||||
}
|
||||
return it, nil
|
||||
}
|
||||
|
||||
// DeleteTemplate delete the config template
|
||||
func (k *kubeConfigFactory) DeleteTemplate(ctx context.Context, ns, name string) error {
|
||||
var configmap v1.ConfigMap
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: ns, Name: TemplateConfigMapNamePrefix + name}, &configmap); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("the config template %s not found", name)
|
||||
}
|
||||
return fmt.Errorf("fail to delete the config template %s:%w", name, err)
|
||||
}
|
||||
return k.cli.Delete(ctx, &configmap)
|
||||
}
|
||||
|
||||
// ListTemplates list the config templates
|
||||
func (k *kubeConfigFactory) ListTemplates(ctx context.Context, ns, scope string) ([]*Template, error) {
|
||||
var list = &v1.ConfigMapList{}
|
||||
selector, err := labels.Parse(fmt.Sprintf("%s=%s", types.LabelConfigCatalog, types.VelaCoreConfig))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := k.cli.List(ctx, list,
|
||||
client.MatchingLabelsSelector{Selector: selector},
|
||||
client.InNamespace(ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var templates []*Template
|
||||
for _, item := range list.Items {
|
||||
it, err := convertConfigMap2Template(item)
|
||||
if err != nil {
|
||||
klog.Warningf("fail to parse the configmap %s:%s", item.Name, err.Error())
|
||||
}
|
||||
if it != nil {
|
||||
if scope == "" || it.Scope == scope {
|
||||
templates = append(templates, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
// LoadTemplate load the template
|
||||
func (k *kubeConfigFactory) LoadTemplate(ctx context.Context, name, ns string) (*Template, error) {
|
||||
var cm v1.ConfigMap
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: ns, Name: TemplateConfigMapNamePrefix + name}, &cm); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, ErrTemplateNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return convertConfigMap2Template(cm)
|
||||
}
|
||||
|
||||
// ParseConfig merge the properties to template and build a config instance
|
||||
// If the templateName is empty, means creating a secret without the template.
|
||||
func (k *kubeConfigFactory) ParseConfig(ctx context.Context,
|
||||
template NamespacedName, meta Metadata,
|
||||
) (*Config, error) {
|
||||
var secret v1.Secret
|
||||
|
||||
config := &Config{
|
||||
Metadata: meta,
|
||||
Secret: &secret,
|
||||
}
|
||||
|
||||
if template.Name != "" {
|
||||
template, err := k.LoadTemplate(ctx, template.Name, template.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contextValue := icontext.ConfigRenderContext{
|
||||
Name: meta.Name,
|
||||
Namespace: meta.Namespace,
|
||||
}
|
||||
// Render the output secret
|
||||
output, err := template.Template.RunAndOutput(contextValue, meta.Properties)
|
||||
if err != nil && !cue.IsFieldNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if output != nil {
|
||||
if err := output.UnmarshalTo(&secret); err != nil {
|
||||
return nil, fmt.Errorf("the output format must be secret")
|
||||
}
|
||||
}
|
||||
if secret.Type == "" {
|
||||
secret.Type = v1.SecretType(fmt.Sprintf("%s/%s", "", template.Name))
|
||||
}
|
||||
if secret.Labels == nil {
|
||||
secret.Labels = map[string]string{}
|
||||
}
|
||||
secret.Labels[types.LabelConfigCatalog] = types.VelaCoreConfig
|
||||
secret.Labels[types.LabelConfigType] = template.Name
|
||||
secret.Labels[types.LabelConfigType] = template.Name
|
||||
secret.Labels[types.LabelConfigScope] = template.Scope
|
||||
|
||||
if secret.Annotations == nil {
|
||||
secret.Annotations = map[string]string{}
|
||||
}
|
||||
secret.Annotations[types.AnnotationConfigSensitive] = fmt.Sprintf("%t", template.Sensitive)
|
||||
secret.Annotations[types.AnnotationConfigTemplateNamespace] = template.Namespace
|
||||
config.Template = *template
|
||||
|
||||
// Render the expanded writer configuration
|
||||
data, err := writer.RenderForExpandedWriter(template.ExpandedWriter, config.Template.Template, contextValue, meta.Properties)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to render the content for the expanded writer:%w ", err)
|
||||
}
|
||||
config.ExpandedWriterData = data
|
||||
|
||||
// Render the outputs objects
|
||||
outputs, err := template.Template.RunAndOutput(contextValue, meta.Properties, "template", "outputs")
|
||||
if err != nil && !cue.IsFieldNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if outputs != nil {
|
||||
var objects = map[string]interface{}{}
|
||||
if err := outputs.UnmarshalTo(&objects); err != nil {
|
||||
return nil, fmt.Errorf("the outputs is invalid %w", err)
|
||||
}
|
||||
var objectReferences []v1.ObjectReference
|
||||
config.OutputObjects = make(map[string]*unstructured.Unstructured)
|
||||
for k := range objects {
|
||||
if ob, ok := objects[k].(map[string]interface{}); ok {
|
||||
obj := &unstructured.Unstructured{Object: ob}
|
||||
config.OutputObjects[k] = obj
|
||||
objectReferences = append(objectReferences, v1.ObjectReference{
|
||||
Kind: obj.GetKind(),
|
||||
Namespace: obj.GetNamespace(),
|
||||
Name: obj.GetName(),
|
||||
APIVersion: obj.GetAPIVersion(),
|
||||
})
|
||||
}
|
||||
}
|
||||
objectReferenceJSON, err := json.Marshal(objectReferences)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret.Data == nil {
|
||||
secret.Data = map[string][]byte{}
|
||||
}
|
||||
secret.Data[SaveObjectReferenceKey] = objectReferenceJSON
|
||||
}
|
||||
} else {
|
||||
secret.Labels = map[string]string{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: "",
|
||||
}
|
||||
secret.Annotations = map[string]string{}
|
||||
}
|
||||
secret.Namespace = meta.Namespace
|
||||
if secret.Name == "" {
|
||||
secret.Name = meta.Name
|
||||
}
|
||||
secret.Annotations[types.AnnotationConfigAlias] = meta.Alias
|
||||
secret.Annotations[types.AnnotationConfigDescription] = meta.Description
|
||||
pp, err := json.Marshal(meta.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret.Data == nil {
|
||||
secret.Data = map[string][]byte{}
|
||||
}
|
||||
secret.Data[SaveInputPropertiesKey] = pp
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ReadConfig read the config secret
|
||||
func (k *kubeConfigFactory) ReadConfig(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
|
||||
var secret v1.Secret
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret.Annotations[types.AnnotationConfigSensitive] == "true" {
|
||||
return nil, ErrSensitiveConfig
|
||||
}
|
||||
properties := secret.Data[SaveInputPropertiesKey]
|
||||
var input = map[string]interface{}{}
|
||||
if err := json.Unmarshal(properties, &input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (k *kubeConfigFactory) GetConfig(ctx context.Context, namespace, name string, withStatus bool) (*Config, error) {
|
||||
var secret v1.Secret
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if secret.Annotations[types.AnnotationConfigSensitive] == "true" {
|
||||
return nil, ErrSensitiveConfig
|
||||
}
|
||||
item, err := convertSecret2Config(&secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if withStatus {
|
||||
if err := k.MergeDistributionStatus(ctx, item, item.Namespace); err != nil && !errors.Is(err, ErrNotFoundDistribution) {
|
||||
klog.Warningf("fail to merge the status %s:%s", item.Name, err.Error())
|
||||
}
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateConfig create or update the config.
|
||||
// Write the expand config to the target server.
|
||||
func (k *kubeConfigFactory) CreateOrUpdateConfig(ctx context.Context, i *Config, ns string) error {
|
||||
var secret v1.Secret
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: i.Namespace, Name: i.Name}, &secret); err == nil {
|
||||
if secret.Labels[types.LabelConfigType] != i.Template.Name {
|
||||
return ErrConfigExist
|
||||
}
|
||||
}
|
||||
if err := k.apiApply.Apply(ctx, i.Secret, apply.Quiet()); err != nil {
|
||||
return fmt.Errorf("fail to apply the secret: %w", err)
|
||||
}
|
||||
for key, obj := range i.OutputObjects {
|
||||
obj.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
Name: i.Secret.Name,
|
||||
UID: i.Secret.UID,
|
||||
}})
|
||||
if err := k.apiApply.Apply(ctx, obj, apply.Quiet()); err != nil {
|
||||
return fmt.Errorf("fail to apply the object %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
readConfig := func(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
|
||||
return k.ReadConfig(ctx, namespace, name)
|
||||
}
|
||||
if i.ExpandedWriterData != nil {
|
||||
if errs := writer.Write(ctx, i.ExpandedWriterData, readConfig); len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *kubeConfigFactory) ListConfigs(ctx context.Context, namespace, template, scope string, withStatus bool) ([]*Config, error) {
|
||||
var list = &v1.SecretList{}
|
||||
requirement := fmt.Sprintf("%s=%s", types.LabelConfigCatalog, types.VelaCoreConfig)
|
||||
if template != "" {
|
||||
requirement = fmt.Sprintf("%s,%s=%s", requirement, types.LabelConfigType, template)
|
||||
}
|
||||
if scope != "" {
|
||||
requirement = fmt.Sprintf("%s,%s=%s", requirement, types.LabelConfigScope, scope)
|
||||
}
|
||||
selector, err := labels.Parse(requirement)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := k.cli.List(ctx, list,
|
||||
client.MatchingLabelsSelector{Selector: selector},
|
||||
client.InNamespace(namespace)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var configs []*Config
|
||||
for i := range list.Items {
|
||||
item := list.Items[i]
|
||||
it, err := convertSecret2Config(&item)
|
||||
if err != nil {
|
||||
klog.Warningf("fail to parse the secret %s:%s", item.Name, err.Error())
|
||||
}
|
||||
if it != nil {
|
||||
if withStatus {
|
||||
if err := k.MergeDistributionStatus(ctx, it, it.Namespace); err != nil && !errors.Is(err, ErrNotFoundDistribution) {
|
||||
klog.Warningf("fail to merge the status %s:%s", item.Name, err.Error())
|
||||
}
|
||||
}
|
||||
configs = append(configs, it)
|
||||
}
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func (k *kubeConfigFactory) DeleteConfig(ctx context.Context, namespace, name string) error {
|
||||
var secret v1.Secret
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("the config %s not found", name)
|
||||
}
|
||||
return fmt.Errorf("fail to delete the config %s:%w", name, err)
|
||||
}
|
||||
if secret.Labels[types.LabelConfigCatalog] != types.VelaCoreConfig {
|
||||
return fmt.Errorf("found a secret but is not a config")
|
||||
}
|
||||
|
||||
if objects, exist := secret.Data[SaveObjectReferenceKey]; exist {
|
||||
var objectReferences []v1.ObjectReference
|
||||
if err := json.Unmarshal(objects, &objectReferences); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range objectReferences {
|
||||
if err := k.cli.Delete(ctx, convertObjectReference2Unstructured(obj)); err != nil && !apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("fail to clear the object %s:%w", obj.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return k.cli.Delete(ctx, &secret)
|
||||
}
|
||||
|
||||
func (k *kubeConfigFactory) MergeDistributionStatus(ctx context.Context, config *Config, namespace string) error {
|
||||
app := &v1beta1.Application{}
|
||||
if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: DefaultDistributionName(config.Name)}, app); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return ErrNotFoundDistribution
|
||||
}
|
||||
return err
|
||||
}
|
||||
var targets []*ClusterTargetStatus
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == v1alpha1.TopologyPolicyType {
|
||||
status := workflowv1alpha1.WorkflowStepPhasePending
|
||||
message := ""
|
||||
if app.Status.Workflow != nil {
|
||||
for _, step := range app.Status.Workflow.Steps {
|
||||
if policy.Name == strings.Replace(step.Name, "deploy-", "", 1) {
|
||||
status = step.Phase
|
||||
message = step.Message
|
||||
}
|
||||
}
|
||||
}
|
||||
var spec v1alpha1.TopologyPolicySpec
|
||||
if err := json.Unmarshal(policy.Properties.Raw, &spec); err == nil {
|
||||
for _, clu := range spec.Clusters {
|
||||
targets = append(targets, &ClusterTargetStatus{
|
||||
ClusterTarget: ClusterTarget{
|
||||
Namespace: spec.Namespace,
|
||||
ClusterName: clu,
|
||||
},
|
||||
Application: NamespacedName{Name: app.Name, Namespace: app.Namespace},
|
||||
Status: string(status),
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
config.Targets = append(config.Targets, targets...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *kubeConfigFactory) CreateOrUpdateDistribution(ctx context.Context, ns, name string, ads *CreateDistributionSpec) error {
|
||||
policies := convertTarget2TopologyPolicy(ads.Targets)
|
||||
if len(policies) == 0 {
|
||||
return ErrNoConfigOrTarget
|
||||
}
|
||||
// create the share policy
|
||||
shareSpec := v1alpha1.SharedResourcePolicySpec{
|
||||
Rules: []v1alpha1.SharedResourcePolicyRule{{
|
||||
Selector: v1alpha1.ResourcePolicyRuleSelector{
|
||||
CompNames: []string{name},
|
||||
},
|
||||
}},
|
||||
}
|
||||
properties, err := json.Marshal(shareSpec)
|
||||
if err == nil {
|
||||
policies = append(policies, v1beta1.AppPolicy{
|
||||
Type: v1alpha1.SharedResourcePolicyType,
|
||||
Name: "share-config",
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: properties,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var objects []map[string]string
|
||||
for _, s := range ads.Configs {
|
||||
objects = append(objects, map[string]string{
|
||||
"name": s.Name,
|
||||
"namespace": s.Namespace,
|
||||
"resource": "secret",
|
||||
})
|
||||
}
|
||||
if len(objects) == 0 {
|
||||
return ErrNoConfigOrTarget
|
||||
}
|
||||
|
||||
objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqByte, err := json.Marshal(ads)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distribution := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
// This label will override the secret label, then change the catalog of the distributed secrets.
|
||||
types.LabelConfigCatalog: types.CatalogConfigDistribution,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.AnnotationConfigDistributionSpec: string(reqByte),
|
||||
oam.AnnotationPublishVersion: utils.GenerateVersion("config"),
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: name,
|
||||
Type: v1alpha1.RefObjectsComponentType,
|
||||
Properties: &runtime.RawExtension{Raw: objectsBytes},
|
||||
},
|
||||
},
|
||||
Policies: policies,
|
||||
},
|
||||
}
|
||||
return k.apiApply.Apply(ctx, distribution, apply.Quiet())
|
||||
}
|
||||
|
||||
func (k *kubeConfigFactory) ListDistributions(ctx context.Context, ns string) ([]*Distribution, error) {
|
||||
var apps v1beta1.ApplicationList
|
||||
if err := k.cli.List(ctx, &apps, client.MatchingLabels{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
types.LabelConfigCatalog: types.CatalogConfigDistribution,
|
||||
}, client.InNamespace(ns)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var list []*Distribution
|
||||
for _, app := range apps.Items {
|
||||
dis := &Distribution{
|
||||
Name: app.Name,
|
||||
Namespace: app.Namespace,
|
||||
CreatedTime: app.CreationTimestamp.Time,
|
||||
Application: pkgtypes.NamespacedName{
|
||||
Namespace: app.Namespace,
|
||||
Name: app.Name,
|
||||
},
|
||||
Status: app.Status,
|
||||
}
|
||||
if spec, ok := app.Annotations[types.AnnotationConfigDistributionSpec]; ok {
|
||||
var req CreateDistributionSpec
|
||||
if err := json.Unmarshal([]byte(spec), &req); err == nil {
|
||||
dis.Targets = req.Targets
|
||||
dis.Configs = req.Configs
|
||||
}
|
||||
}
|
||||
list = append(list, dis)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
func (k *kubeConfigFactory) DeleteDistribution(ctx context.Context, ns, name string) error {
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
if err := k.cli.Delete(ctx, app); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return ErrNotFoundDistribution
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertTarget2TopologyPolicy(targets []*ClusterTarget) (policies []v1beta1.AppPolicy) {
|
||||
for _, target := range targets {
|
||||
policySpec := v1alpha1.TopologyPolicySpec{
|
||||
Placement: v1alpha1.Placement{
|
||||
Clusters: []string{target.ClusterName},
|
||||
},
|
||||
Namespace: target.Namespace,
|
||||
}
|
||||
properties, err := json.Marshal(policySpec)
|
||||
if err == nil {
|
||||
policies = append(policies, v1beta1.AppPolicy{
|
||||
Type: v1alpha1.TopologyPolicyType,
|
||||
Name: fmt.Sprintf("%s-%s", target.ClusterName, target.Namespace),
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: properties,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertSecret2Config(se *v1.Secret) (*Config, error) {
|
||||
if se == nil || se.Labels == nil {
|
||||
return nil, fmt.Errorf("this secret is not a valid config secret")
|
||||
}
|
||||
config := &Config{
|
||||
Metadata: Metadata{
|
||||
NamespacedName: NamespacedName{
|
||||
Name: se.Name,
|
||||
Namespace: se.Namespace,
|
||||
},
|
||||
},
|
||||
CreateTime: se.CreationTimestamp.Time,
|
||||
Secret: se,
|
||||
Template: Template{
|
||||
NamespacedName: NamespacedName{
|
||||
Name: se.Labels[types.LabelConfigType],
|
||||
},
|
||||
},
|
||||
}
|
||||
if se.Annotations != nil {
|
||||
config.Alias = se.Annotations[types.AnnotationConfigAlias]
|
||||
config.Description = se.Annotations[types.AnnotationConfigDescription]
|
||||
config.Template.Namespace = se.Annotations[types.AnnotationConfigTemplateNamespace]
|
||||
config.Template.Sensitive = se.Annotations[types.AnnotationConfigSensitive] == "true"
|
||||
}
|
||||
if !config.Template.Sensitive && len(se.Data[SaveInputPropertiesKey]) > 0 {
|
||||
var properties = map[string]interface{}{}
|
||||
if err := yaml.Unmarshal(se.Data[SaveInputPropertiesKey], &properties); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Properties = properties
|
||||
}
|
||||
if !config.Template.Sensitive {
|
||||
config.Secret = se
|
||||
} else {
|
||||
seCope := se.DeepCopy()
|
||||
seCope.Data = nil
|
||||
seCope.StringData = nil
|
||||
config.Secret = seCope
|
||||
}
|
||||
if content, ok := se.Data[SaveObjectReferenceKey]; ok {
|
||||
var objectReferences []v1.ObjectReference
|
||||
if err := json.Unmarshal(content, &objectReferences); err != nil {
|
||||
klog.Warningf("the object references are invalid, config:%s", se.Name)
|
||||
}
|
||||
config.ObjectReferences = objectReferences
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func convertObjectReference2Unstructured(ref v1.ObjectReference) *unstructured.Unstructured {
|
||||
var obj unstructured.Unstructured
|
||||
obj.SetAPIVersion(ref.APIVersion)
|
||||
obj.SetNamespace(ref.Namespace)
|
||||
obj.SetKind(ref.Kind)
|
||||
obj.SetName(ref.Name)
|
||||
return &obj
|
||||
}
|
||||
|
||||
// DefaultDistributionName generate the distribution name by a config name
|
||||
func DefaultDistributionName(configName string) string {
|
||||
return fmt.Sprintf("distribute-%s", configName)
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
nacosmock "github.com/oam-dev/kubevela/test/mock/nacos"
|
||||
)
|
||||
|
||||
func TestParseConfigTemplate(t *testing.T) {
|
||||
r := require.New(t)
|
||||
content, err := ioutil.ReadFile("testdata/helm-repo.cue")
|
||||
r.Equal(err, nil)
|
||||
var inf = &kubeConfigFactory{}
|
||||
template, err := inf.ParseTemplate("default", content)
|
||||
r.Equal(err, nil)
|
||||
r.NotEqual(template, nil)
|
||||
r.Equal(template.Name, "default")
|
||||
r.NotEqual(template.Schema, nil)
|
||||
r.Equal(len(template.Schema.Properties), 4)
|
||||
}
|
||||
|
||||
var _ = Describe("test config factory", func() {
|
||||
|
||||
var fac Factory
|
||||
BeforeEach(func() {
|
||||
fac = NewConfigFactory(k8sClient)
|
||||
})
|
||||
|
||||
It("apply the nacos server template", func() {
|
||||
data, err := os.ReadFile("./testdata/nacos-server.cue")
|
||||
Expect(err).Should(BeNil())
|
||||
t, err := fac.ParseTemplate("", data)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(fac.CreateOrUpdateConfigTemplate(context.TODO(), "default", t)).Should(BeNil())
|
||||
})
|
||||
It("apply a config to the nacos server", func() {
|
||||
|
||||
By("create a nacos server config")
|
||||
nacos, err := fac.ParseConfig(context.TODO(), NamespacedName{Name: "nacos-server", Namespace: "default"}, Metadata{NamespacedName: NamespacedName{Name: "nacos", Namespace: "default"}, Properties: map[string]interface{}{
|
||||
"servers": []map[string]interface{}{{
|
||||
"ipAddr": "127.0.0.1",
|
||||
"port": 8849,
|
||||
}},
|
||||
}})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(nacos.Secret.Data[SaveInputPropertiesKey]) > 0).Should(BeTrue())
|
||||
Expect(fac.CreateOrUpdateConfig(context.Background(), nacos, "default")).Should(BeNil())
|
||||
|
||||
config, err := fac.ReadConfig(context.TODO(), "default", "nacos")
|
||||
Expect(err).Should(BeNil())
|
||||
servers, ok := config["servers"].([]interface{})
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(len(servers)).Should(Equal(1))
|
||||
|
||||
By("apply a template that with the nacos writer")
|
||||
data, err := os.ReadFile("./testdata/mysql-db-nacos.cue")
|
||||
Expect(err).Should(BeNil())
|
||||
t, err := fac.ParseTemplate("", data)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(t.ExpandedWriter.Nacos).ShouldNot(BeNil())
|
||||
Expect(t.ExpandedWriter.Nacos.Endpoint.Name).Should(Equal("nacos"))
|
||||
|
||||
Expect(fac.CreateOrUpdateConfigTemplate(context.TODO(), "default", t)).Should(BeNil())
|
||||
|
||||
db, err := fac.ParseConfig(context.TODO(), NamespacedName{Name: "nacos", Namespace: "default"}, Metadata{NamespacedName: NamespacedName{Name: "db-config", Namespace: "default"}, Properties: map[string]interface{}{
|
||||
"dataId": "dbconfig",
|
||||
"appName": "db",
|
||||
"content": map[string]interface{}{
|
||||
"mysqlHost": "127.0.0.1:3306",
|
||||
"mysqlPort": 3306,
|
||||
"username": "test",
|
||||
"password": "string",
|
||||
},
|
||||
}})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(db.Template.ExpandedWriter).ShouldNot(BeNil())
|
||||
Expect(db.ExpandedWriterData).ShouldNot(BeNil())
|
||||
Expect(len(db.ExpandedWriterData.Nacos.Content) > 0).Should(BeTrue())
|
||||
Expect(db.ExpandedWriterData.Nacos.Metadata.DataID).Should(Equal("dbconfig"))
|
||||
|
||||
Expect(len(db.OutputObjects)).Should(Equal(1))
|
||||
|
||||
nacosClient := nacosmock.NewMockIConfigClient(ctl)
|
||||
db.ExpandedWriterData.Nacos.Client = nacosClient
|
||||
nacosClient.EXPECT().PublishConfig(gomock.Any()).Return(true, nil)
|
||||
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(fac.CreateOrUpdateConfig(context.Background(), db, "default")).Should(BeNil())
|
||||
|
||||
})
|
||||
|
||||
It("list all templates", func() {
|
||||
templates, err := fac.ListTemplates(context.TODO(), "", "")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(templates)).Should(Equal(2))
|
||||
})
|
||||
|
||||
It("list all configs", func() {
|
||||
configs, err := fac.ListConfigs(context.TODO(), "", "", "", true)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(configs)).Should(Equal(2))
|
||||
})
|
||||
|
||||
It("distribute a config", func() {
|
||||
err := fac.CreateOrUpdateDistribution(context.TODO(), "default", "distribute-db-config", &CreateDistributionSpec{
|
||||
Configs: []*NamespacedName{
|
||||
{Name: "db-config", Namespace: "default"},
|
||||
},
|
||||
Targets: []*ClusterTarget{
|
||||
{ClusterName: "local", Namespace: "test"},
|
||||
},
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("get the config", func() {
|
||||
config, err := fac.GetConfig(context.TODO(), "default", "db-config", true)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(config.ObjectReferences)).ShouldNot(BeNil())
|
||||
Expect(config.ObjectReferences[0].Kind).Should(Equal("ConfigMap"))
|
||||
Expect(len(config.Targets)).Should(Equal(1))
|
||||
})
|
||||
|
||||
It("list the distributions", func() {
|
||||
distributions, err := fac.ListDistributions(context.TODO(), "default")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(distributions)).Should(Equal(1))
|
||||
})
|
||||
|
||||
It("delete the distribution", func() {
|
||||
err := fac.DeleteDistribution(context.TODO(), "default", "distribute-db-config")
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("delete the config", func() {
|
||||
err := fac.DeleteConfig(context.TODO(), "default", "db-config")
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
|
||||
It("delete the config template", func() {
|
||||
err := fac.DeleteTemplate(context.TODO(), "default", "nacos")
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
metadata: {
|
||||
name: "helm-repository"
|
||||
// alias: "Helm Repository"
|
||||
scope: "system"
|
||||
sensitive: false
|
||||
}
|
||||
|
||||
template: {
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
labels: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "helm-repository"
|
||||
"config.oam.dev/multi-cluster": "true"
|
||||
"config.oam.dev/sub-type": "helm"
|
||||
}
|
||||
}
|
||||
// If the type is empty, it will assign value using this format.
|
||||
type: "catalog.config.oam.dev/helm-repository"
|
||||
stringData: {
|
||||
url: parameter.url
|
||||
if parameter.username != _|_ {
|
||||
username: parameter.username
|
||||
}
|
||||
if parameter.password != _|_ {
|
||||
password: parameter.password
|
||||
}
|
||||
}
|
||||
data: {
|
||||
if parameter.caFile != _|_ {
|
||||
caFile: parameter.caFile
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=The public url of the helm chart repository.
|
||||
url: string
|
||||
// +usage=The username of basic auth repo.
|
||||
username?: string
|
||||
// +usage=The password of basic auth repo.
|
||||
password?: string
|
||||
// +usage=The ca certificate of helm repository. Please encode this data with base64.
|
||||
caFile?: string
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
metadata: {
|
||||
name: "nacos"
|
||||
alias: "Nacos Config"
|
||||
}
|
||||
|
||||
template: {
|
||||
nacos: {
|
||||
// can not references the parameter
|
||||
endpoint: {
|
||||
name: "nacos"
|
||||
namespace: "default"
|
||||
}
|
||||
format: "properties"
|
||||
|
||||
// could references the parameter
|
||||
metadata: {
|
||||
dataId: parameter.dataId
|
||||
group: parameter.group
|
||||
if parameter.appName != _|_ {
|
||||
appName: parameter.appName
|
||||
}
|
||||
}
|
||||
content: parameter.content
|
||||
}
|
||||
outputs: {
|
||||
"test": {
|
||||
kind: "ConfigMap"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
data: {
|
||||
"string": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
dataId: string
|
||||
group: *"DEFAULT_GROUP" | string
|
||||
appName?: string
|
||||
content: {
|
||||
mysqlHost: string
|
||||
mysqlPort: int
|
||||
username?: string
|
||||
password?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
metadata: {
|
||||
name: "nacos-server"
|
||||
alias: "Nacos Server"
|
||||
}
|
||||
|
||||
template: {
|
||||
parameter: {
|
||||
servers?: [...{
|
||||
ipAddr: string
|
||||
port: int
|
||||
}]
|
||||
client?: {
|
||||
endpoint: string
|
||||
accessKey: string
|
||||
secretKey: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/nacos-group/nacos-sdk-go/v2/clients"
|
||||
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
|
||||
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
|
||||
"github.com/nacos-group/nacos-sdk-go/v2/vo"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
icontext "github.com/oam-dev/kubevela/pkg/config/context"
|
||||
"github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
)
|
||||
|
||||
// NacosConfig defines the nacos output
|
||||
type NacosConfig struct {
|
||||
Endpoint ConfigRef `json:"endpoint"`
|
||||
// Format defines the format in which Data will be output.
|
||||
Format string `json:"format"`
|
||||
Metadata NacosConfigMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
// NacosConfigMetadata the metadata of the nacos config
|
||||
type NacosConfigMetadata struct {
|
||||
DataID string `json:"dataId"`
|
||||
Group string `json:"group"`
|
||||
NamespaceID string `json:"namespaceId"`
|
||||
AppName string `json:"appName,omitempty"`
|
||||
Tenant string `json:"tenant,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// NacosData merge the nacos endpoint config and the rendered data
|
||||
type NacosData struct {
|
||||
NacosConfig
|
||||
Content []byte `json:"-"`
|
||||
Client config_client.IConfigClient `json:"-"`
|
||||
}
|
||||
|
||||
// parseNacosConfig parse the nacos server config
|
||||
func parseNacosConfig(templateField *value.Value, wc *ExpandedWriterConfig) {
|
||||
nacos, _ := templateField.LookupValue("nacos")
|
||||
if nacos != nil {
|
||||
format, err := nacos.GetString("format")
|
||||
if err != nil && !cue.IsFieldNotExist(err) {
|
||||
klog.Warningf("fail to get the format from the nacos config: %s", err.Error())
|
||||
}
|
||||
endpoint, err := nacos.GetString("endpoint", "name")
|
||||
if err != nil && !cue.IsFieldNotExist(err) {
|
||||
klog.Warningf("fail to get the endpoint name from the nacos config: %s", err.Error())
|
||||
}
|
||||
wc.Nacos = &NacosConfig{
|
||||
Format: format,
|
||||
Endpoint: ConfigRef{
|
||||
Name: endpoint,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderNacos(config *NacosConfig, template script.CUE, context icontext.ConfigRenderContext, properties map[string]interface{}) (*NacosData, error) {
|
||||
nacos, err := template.RunAndOutput(context, properties, "template", "nacos")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
format, err := nacos.GetString("format")
|
||||
if err != nil {
|
||||
format = config.Format
|
||||
}
|
||||
var nacosData NacosData
|
||||
if err := nacos.UnmarshalTo(&nacosData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := nacos.LookupValue("content")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := encodingOutput(content, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nacosData.Content = out
|
||||
if nacosData.Endpoint.Namespace == "" {
|
||||
nacosData.Endpoint.Namespace = types.DefaultKubeVelaNS
|
||||
}
|
||||
|
||||
return &nacosData, nil
|
||||
}
|
||||
|
||||
func (n *NacosData) write(ctx context.Context, configReader icontext.ReadConfigProvider) (err error) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
err = fmt.Errorf("panic when writing the data to nacos:%v", rec)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
// the config of the nacos server saving in the default system namespace
|
||||
config, err := configReader(ctx, n.Endpoint.Namespace, n.Endpoint.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to read the config of the nacos server:%w", err)
|
||||
}
|
||||
readString := func(data map[string]interface{}, key string) string {
|
||||
if v, ok := data[key]; ok {
|
||||
str, _ := v.(string)
|
||||
return str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
readUint64 := func(data map[string]interface{}, key string) uint64 {
|
||||
if v, ok := data[key]; ok {
|
||||
vu, _ := v.(float64)
|
||||
return uint64(vu)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
readBool := func(data map[string]interface{}, key string) bool {
|
||||
if v, ok := data[key]; ok {
|
||||
vu, _ := v.(bool)
|
||||
return vu
|
||||
}
|
||||
return false
|
||||
}
|
||||
var nacosParam vo.NacosClientParam
|
||||
if serverConfigs, ok := config["servers"]; ok {
|
||||
var servers []constant.ServerConfig
|
||||
serverEndpoints, _ := serverConfigs.([]interface{})
|
||||
for _, s := range serverEndpoints {
|
||||
sm, ok := s.(map[string]interface{})
|
||||
if ok && sm != nil {
|
||||
servers = append(servers, constant.ServerConfig{
|
||||
IpAddr: readString(sm, "ipAddr"),
|
||||
Port: readUint64(sm, "port"),
|
||||
GrpcPort: readUint64(sm, "grpcPort"),
|
||||
})
|
||||
}
|
||||
}
|
||||
nacosParam.ServerConfigs = servers
|
||||
}
|
||||
// Discover the server endpoint
|
||||
if clientConfigs, ok := config["client"]; ok {
|
||||
client, _ := clientConfigs.(map[string]interface{})
|
||||
if client != nil {
|
||||
nacosParam.ClientConfig = constant.NewClientConfig(
|
||||
constant.WithEndpoint(readString(client, "endpoint")),
|
||||
constant.WithAppName(n.Metadata.AppName),
|
||||
constant.WithNamespaceId(n.Metadata.NamespaceID),
|
||||
constant.WithUsername(readString(client, "username")),
|
||||
constant.WithPassword(readString(client, "password")),
|
||||
constant.WithRegionId(readString(client, "regionId")),
|
||||
constant.WithOpenKMS(readBool(client, "openKMS")),
|
||||
constant.WithAccessKey(readString(client, "accessKey")),
|
||||
constant.WithSecretKey(readString(client, "secretKey")),
|
||||
)
|
||||
}
|
||||
}
|
||||
// The mock client creates on the outer.
|
||||
if n.Client == nil {
|
||||
nacosClient, err := clients.NewConfigClient(nacosParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nacosClient.CloseClient()
|
||||
n.Client = nacosClient
|
||||
}
|
||||
_, err = n.Client.PublishConfig(vo.ConfigParam{
|
||||
DataId: n.Metadata.DataID,
|
||||
Group: n.Metadata.Group,
|
||||
Content: string(n.Content),
|
||||
AppName: n.Metadata.AppName,
|
||||
Tag: n.Metadata.Tag,
|
||||
Type: strings.ToLower(n.Format),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to publish the config to the nacos server:%w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/nacos-group/nacos-sdk-go/v2/vo"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
|
||||
configcontext "github.com/oam-dev/kubevela/pkg/config/context"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
nacosmock "github.com/oam-dev/kubevela/test/mock/nacos"
|
||||
)
|
||||
|
||||
func TestNacosWriter(t *testing.T) {
|
||||
r := require.New(t)
|
||||
v, err := value.NewValue(`
|
||||
nacos: {
|
||||
endpoint: {
|
||||
name: "test-nacos-server"
|
||||
}
|
||||
format: "json"
|
||||
}
|
||||
`, nil, "")
|
||||
r.Equal(err, nil)
|
||||
ewc := &ExpandedWriterConfig{}
|
||||
parseNacosConfig(v, ewc)
|
||||
r.Equal(ewc.Nacos.Endpoint.Name, "test-nacos-server")
|
||||
r.Equal(ewc.Nacos.Format, "json")
|
||||
renderContext := configcontext.ConfigRenderContext{Name: "nacos-config", Namespace: "vela"}
|
||||
data, err := renderNacos(ewc.Nacos, script.CUE(`
|
||||
template: {
|
||||
nacos: {
|
||||
// The endpoint can not references the parameter.
|
||||
endpoint: {
|
||||
// Users must create a config base the nacos-server template firstly.
|
||||
name: "test-nacos-server"
|
||||
}
|
||||
format: parameter.contentType
|
||||
|
||||
// could references the parameter
|
||||
metadata: {
|
||||
dataId: parameter.dataId
|
||||
group: parameter.group
|
||||
if parameter.appName != _|_ {
|
||||
appName: parameter.appName
|
||||
}
|
||||
if parameter.namespaceId != _|_ {
|
||||
namespaceId: parameter.namespaceId
|
||||
}
|
||||
if parameter.tenant != _|_ {
|
||||
tenant: parameter.tenant
|
||||
}
|
||||
if parameter.tag != _|_ {
|
||||
tag: parameter.tag
|
||||
}
|
||||
}
|
||||
content: parameter.content
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Configuration ID
|
||||
dataId: string
|
||||
// +usage=Configuration group
|
||||
group: *"DEFAULT_GROUP" | string
|
||||
// +usage=The configuration content.
|
||||
content: {
|
||||
...
|
||||
}
|
||||
contentType: *"json" | "yaml" | "properties" | "toml"
|
||||
// +usage=The app name of the configuration
|
||||
appName?: string
|
||||
// +usage=The namespaceId of the configuration
|
||||
namespaceId?: string
|
||||
// +usage=The tenant, corresponding to the namespace ID field of Nacos
|
||||
tenant?: string
|
||||
// +usage=The tag of the configuration
|
||||
tag?: string
|
||||
}
|
||||
}
|
||||
`), renderContext, map[string]interface{}{
|
||||
"dataId": "hello",
|
||||
"content": map[string]interface{}{
|
||||
"c1": 1,
|
||||
},
|
||||
"contentType": "properties",
|
||||
"appName": "appName",
|
||||
"namespaceId": "namespaceId",
|
||||
"tenant": "tenant",
|
||||
"tag": "tag",
|
||||
})
|
||||
r.Equal(err, nil)
|
||||
|
||||
ctl := gomock.NewController(t)
|
||||
nacosClient := nacosmock.NewMockIConfigClient(ctl)
|
||||
data.Client = nacosClient
|
||||
nacosClient.EXPECT().PublishConfig(gomock.Eq(vo.ConfigParam{
|
||||
DataId: "hello",
|
||||
Group: "DEFAULT_GROUP",
|
||||
Content: "c1 = 1\n",
|
||||
Tag: "tag",
|
||||
AppName: "appName",
|
||||
Type: "properties",
|
||||
})).Return(true, nil)
|
||||
|
||||
err = data.write(context.TODO(), func(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
|
||||
if name == "test-nacos-server" {
|
||||
return map[string]interface{}{
|
||||
"servers": []interface{}{
|
||||
map[string]interface{}{
|
||||
"ipAddr": "127.0.0.1",
|
||||
"port": 8849,
|
||||
},
|
||||
},
|
||||
"client": map[string]interface{}{
|
||||
"endpoint": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"accessKey": "accessKey",
|
||||
"secretKey": "secretKey",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("config not found")
|
||||
})
|
||||
r.Equal(err, nil)
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/magiconair/properties"
|
||||
"github.com/pelletier/go-toml"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
|
||||
icontext "github.com/oam-dev/kubevela/pkg/config/context"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
)
|
||||
|
||||
// ExpandedWriterConfig define the supported output ways.
|
||||
type ExpandedWriterConfig struct {
|
||||
Nacos *NacosConfig `json:"nacos"`
|
||||
}
|
||||
|
||||
// ExpandedWriterData the data for the expanded writer
|
||||
type ExpandedWriterData struct {
|
||||
Nacos *NacosData `json:"nacos"`
|
||||
}
|
||||
|
||||
// ConfigRef reference a config secret, it must be system scope.
|
||||
type ConfigRef struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// ParseExpandedWriterConfig parse the expanded writer config from the template value
|
||||
func ParseExpandedWriterConfig(template *value.Value) ExpandedWriterConfig {
|
||||
var ewc = ExpandedWriterConfig{}
|
||||
parseNacosConfig(template, &ewc)
|
||||
// parse the other writer configs
|
||||
return ewc
|
||||
}
|
||||
|
||||
// RenderForExpandedWriter render the configuration for all expanded writers
|
||||
func RenderForExpandedWriter(ewc ExpandedWriterConfig, template script.CUE, context icontext.ConfigRenderContext, properties map[string]interface{}) (*ExpandedWriterData, error) {
|
||||
var ewd = ExpandedWriterData{}
|
||||
var err error
|
||||
if ewc.Nacos != nil {
|
||||
ewd.Nacos, err = renderNacos(ewc.Nacos, template, context, properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klog.Info("the config render to nacos context successfully")
|
||||
}
|
||||
return &ewd, nil
|
||||
}
|
||||
|
||||
// Write write the config by the all writers
|
||||
func Write(ctx context.Context, ewd *ExpandedWriterData, ri icontext.ReadConfigProvider) (list []error) {
|
||||
if ewd.Nacos != nil {
|
||||
if err := ewd.Nacos.write(ctx, ri); err != nil {
|
||||
list = append(list, err)
|
||||
} else {
|
||||
klog.Info("the config write to the nacos successfully")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// encodingOutput support the json、toml、xml、properties and yaml formats.
|
||||
func encodingOutput(input *value.Value, format string) ([]byte, error) {
|
||||
var data = make(map[string]interface{})
|
||||
if err := input.UnmarshalTo(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch strings.ToLower(format) {
|
||||
case "json":
|
||||
return json.Marshal(data)
|
||||
case "toml":
|
||||
return toml.Marshal(data)
|
||||
case "properties":
|
||||
var kv = map[string]string{}
|
||||
if err := convertMap2PropertiesKV("", data, kv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(properties.LoadMap(kv).String()), nil
|
||||
default:
|
||||
return yaml.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func convertMap2PropertiesKV(last string, input map[string]interface{}, result map[string]string) error {
|
||||
|
||||
interface2str := func(key string, v interface{}, result map[string]string) (string, error) {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return t, nil
|
||||
case bool:
|
||||
return fmt.Sprintf("%t", t), nil
|
||||
case int64, int, int32:
|
||||
return fmt.Sprintf("%d", t), nil
|
||||
case float64, float32:
|
||||
return fmt.Sprintf("%v", t), nil
|
||||
case map[string]interface{}:
|
||||
if err := convertMap2PropertiesKV(key, t, result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
return fmt.Sprintf("%v", t), nil
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range input {
|
||||
key := k
|
||||
if last != "" {
|
||||
key = fmt.Sprintf("%s.%s", last, k)
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case string, bool, int64, int, int32, float32, float64, map[string]interface{}:
|
||||
v, err := interface2str(key, t, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v != "" {
|
||||
result[key] = v
|
||||
}
|
||||
case []interface{}, []string, []int64, []float64, []map[string]interface{}:
|
||||
var ints []string
|
||||
s := reflect.ValueOf(t)
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
re, err := interface2str(fmt.Sprintf("%s.%d", key, i), s.Index(i).Interface(), result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if re != "" {
|
||||
ints = append(ints, re)
|
||||
}
|
||||
}
|
||||
if len(ints) > 0 {
|
||||
result[key] = strings.Join(ints, ",")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("the value type of %s(%T) can not be supported", key, t)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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 writer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConvertMap2KV(t *testing.T) {
|
||||
r := require.New(t)
|
||||
re := map[string]string{}
|
||||
err := convertMap2PropertiesKV("", map[string]interface{}{
|
||||
"s": "s",
|
||||
"n": 1,
|
||||
"nn": 1.5,
|
||||
"b": true,
|
||||
"m": map[string]interface{}{
|
||||
"s": "s",
|
||||
"b": false,
|
||||
},
|
||||
"aa": []string{"a", "a"},
|
||||
"ai": []int64{1, 2},
|
||||
"ar": []map[string]interface{}{{
|
||||
"s2": "s2",
|
||||
}},
|
||||
}, re)
|
||||
r.Equal(err, nil)
|
||||
r.Equal(re, map[string]string{
|
||||
"s": "s",
|
||||
"n": "1",
|
||||
"nn": "1.5",
|
||||
"b": "true",
|
||||
"m.s": "s",
|
||||
"m.b": "false",
|
||||
"aa": "a,a",
|
||||
"ai": "1,2",
|
||||
"ar.0.s2": "s2",
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncodingOutput(t *testing.T) {
|
||||
r := require.New(t)
|
||||
testValue := `
|
||||
context: {
|
||||
key1: "hello"
|
||||
key2: 2
|
||||
key3: true
|
||||
key4: 4.4
|
||||
key5: ["hello"]
|
||||
key6: [{"hello": 1}]
|
||||
key7: [1, 2]
|
||||
key8: [1.2, 1]
|
||||
key9: {key10: [{"wang": true}]}
|
||||
}
|
||||
`
|
||||
v, err := value.NewValue(testValue, nil, "")
|
||||
r.Equal(err, nil)
|
||||
|
||||
_, err = encodingOutput(v, "yaml")
|
||||
r.Equal(err, nil)
|
||||
|
||||
_, err = encodingOutput(v, "properties")
|
||||
r.Equal(err, nil)
|
||||
|
||||
_, err = encodingOutput(v, "toml")
|
||||
r.Equal(err, nil)
|
||||
|
||||
json, err := encodingOutput(v, "json")
|
||||
r.Equal(err, nil)
|
||||
r.Equal(string(json), `{"context":{"key1":"hello","key2":2,"key3":true,"key4":4.4,"key5":["hello"],"key6":[{"hello":1}],"key7":[1,2],"key8":[1.2,1],"key9":{"key10":[{"wang":true}]}}}`)
|
||||
}
|
||||
|
|
@ -30,8 +30,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
|
|
@ -48,7 +46,6 @@ import (
|
|||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
options
|
||||
|
|
@ -94,7 +91,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
|
||||
def := utils.NewCapabilityComponentDef(&componentDefinition)
|
||||
// Store the parameter of componentDefinition to configMap
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, r.pd, req.Namespace, req.Name, defRev.Name)
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, req.Namespace, req.Name, defRev.Name)
|
||||
if err != nil {
|
||||
klog.InfoS("Could not capability in ConfigMap", "err", err)
|
||||
r.record.Event(&(componentDefinition), event.Warning("Could not store capability in ConfigMap", err))
|
||||
|
|
@ -145,7 +142,6 @@ func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
)
|
||||
|
|
@ -87,14 +85,11 @@ var _ = BeforeSuite(func(done Done) {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = dm.Refresh()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
pd, err := packages.NewPackageDiscover(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r = Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
options: options{
|
||||
defRevLimit: defRevisionLimit,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
|
|
@ -48,7 +46,6 @@ import (
|
|||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
defRevLimit int
|
||||
|
|
@ -97,7 +94,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
def := utils.NewCapabilityPolicyDef(&policyDefinition)
|
||||
def.Name = req.NamespacedName.Name
|
||||
// Store the parameter of policyDefinition to configMap
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, r.pd, req.Namespace, req.Name, defRev.Name)
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, req.Namespace, req.Name, defRev.Name)
|
||||
if err != nil {
|
||||
klog.InfoS("Could not capability in ConfigMap", "err", err)
|
||||
r.record.Event(&(policyDefinition), event.Warning("Could not store capability in ConfigMap", err))
|
||||
|
|
@ -150,7 +147,6 @@ func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
ignoreDefNoCtrlReq: args.IgnoreDefinitionWithoutControllerRequirement,
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
)
|
||||
|
|
@ -84,8 +82,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
pd, err := packages.NewPackageDiscover(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = dm.Refresh()
|
||||
|
|
@ -95,7 +91,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
defRevLimit: defRevisionLimit,
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
|
|
@ -48,7 +46,6 @@ import (
|
|||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
options
|
||||
|
|
@ -101,7 +98,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
def := utils.NewCapabilityTraitDef(&traitDefinition)
|
||||
def.Name = req.NamespacedName.Name
|
||||
// Store the parameter of traitDefinition to configMap
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, r.pd, req.Namespace, req.Name, defRev.Name)
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, req.Namespace, req.Name, defRev.Name)
|
||||
if err != nil {
|
||||
klog.InfoS("Could not store capability in ConfigMap", "err", err)
|
||||
r.record.Event(&(traitDefinition), event.Warning("Could not store capability in ConfigMap", err))
|
||||
|
|
@ -153,7 +150,6 @@ func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
)
|
||||
|
|
@ -83,8 +81,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
pd, err := packages.NewPackageDiscover(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = dm.Refresh()
|
||||
|
|
@ -94,7 +90,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
options: options{
|
||||
defRevLimit: defRevisionLimit,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
)
|
||||
|
|
@ -83,8 +81,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
pd, err := packages.NewPackageDiscover(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
dm, err := discoverymapper.New(mgr.GetConfig())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = dm.Refresh()
|
||||
|
|
@ -94,7 +90,6 @@ var _ = BeforeSuite(func(done Done) {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
options: options{
|
||||
defRevLimit: defRevisionLimit,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
|
|
@ -48,7 +46,6 @@ import (
|
|||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
options
|
||||
|
|
@ -101,7 +98,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
def := utils.NewCapabilityStepDef(&wfStepDefinition)
|
||||
def.Name = req.NamespacedName.Name
|
||||
// Store the parameter of stepDefinition to configMap
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, r.pd, req.Namespace, req.Name, defRev.Name)
|
||||
cmName, err := def.StoreOpenAPISchema(ctx, r.Client, req.Namespace, req.Name, defRev.Name)
|
||||
if err != nil {
|
||||
klog.InfoS("Could not store capability in ConfigMap", "err", err)
|
||||
r.record.Event(&(wfStepDefinition), event.Warning("Could not store capability in ConfigMap", err))
|
||||
|
|
@ -153,7 +150,6 @@ func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
|||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,8 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/src-d/go-git.v4"
|
||||
|
|
@ -36,16 +34,12 @@ import (
|
|||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
commontypes "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/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/helm"
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/script"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/terraform"
|
||||
|
|
@ -131,12 +125,12 @@ func NewCapabilityComponentDef(componentDefinition *v1beta1.ComponentDefinition)
|
|||
}
|
||||
|
||||
// GetOpenAPISchema gets OpenAPI v3 schema by WorkloadDefinition name
|
||||
func (def *CapabilityComponentDefinition) GetOpenAPISchema(pd *packages.PackageDiscover, name string) ([]byte, error) {
|
||||
func (def *CapabilityComponentDefinition) GetOpenAPISchema(name string) ([]byte, error) {
|
||||
capability, err := appfile.ConvertTemplateJSON2Object(name, def.ComponentDefinition.Spec.Extension, def.ComponentDefinition.Spec.Schematic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert ComponentDefinition to Capability Object")
|
||||
}
|
||||
return getOpenAPISchema(capability, pd)
|
||||
return getOpenAPISchema(capability)
|
||||
}
|
||||
|
||||
// GetOpenAPISchemaFromTerraformComponentDefinition gets OpenAPI v3 schema by WorkloadDefinition name
|
||||
|
|
@ -354,8 +348,7 @@ func GetKubeSchematicOpenAPISchema(params []commontypes.KubeParameter) ([]byte,
|
|||
}
|
||||
|
||||
// StoreOpenAPISchema stores OpenAPI v3 schema in ConfigMap from WorkloadDefinition
|
||||
func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client,
|
||||
pd *packages.PackageDiscover, namespace, name, revName string) (string, error) {
|
||||
func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name, revName string) (string, error) {
|
||||
var jsonSchema []byte
|
||||
var err error
|
||||
switch def.WorkloadType {
|
||||
|
|
@ -376,7 +369,7 @@ func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context
|
|||
}
|
||||
jsonSchema, err = GetOpenAPISchemaFromTerraformComponentDefinition(configuration)
|
||||
default:
|
||||
jsonSchema, err = def.GetOpenAPISchema(pd, name)
|
||||
jsonSchema, err = def.GetOpenAPISchema(name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err)
|
||||
|
|
@ -440,23 +433,23 @@ func NewCapabilityTraitDef(traitdefinition *v1beta1.TraitDefinition) CapabilityT
|
|||
}
|
||||
|
||||
// GetOpenAPISchema gets OpenAPI v3 schema by TraitDefinition name
|
||||
func (def *CapabilityTraitDefinition) GetOpenAPISchema(pd *packages.PackageDiscover, name string) ([]byte, error) {
|
||||
func (def *CapabilityTraitDefinition) GetOpenAPISchema(name string) ([]byte, error) {
|
||||
capability, err := appfile.ConvertTemplateJSON2Object(name, def.TraitDefinition.Spec.Extension, def.TraitDefinition.Spec.Schematic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert WorkloadDefinition to Capability Object")
|
||||
}
|
||||
return getOpenAPISchema(capability, pd)
|
||||
return getOpenAPISchema(capability)
|
||||
}
|
||||
|
||||
// StoreOpenAPISchema stores OpenAPI v3 schema from TraitDefinition in ConfigMap
|
||||
func (def *CapabilityTraitDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, pd *packages.PackageDiscover, namespace, name string, revName string) (string, error) {
|
||||
func (def *CapabilityTraitDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name string, revName string) (string, error) {
|
||||
var jsonSchema []byte
|
||||
var err error
|
||||
switch def.DefCategoryType {
|
||||
case util.KubeDef: // Kube template
|
||||
jsonSchema, err = GetKubeSchematicOpenAPISchema(def.Kube.Parameters)
|
||||
default: // CUE template
|
||||
jsonSchema, err = def.GetOpenAPISchema(pd, name)
|
||||
jsonSchema, err = def.GetOpenAPISchema(name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err)
|
||||
|
|
@ -513,20 +506,20 @@ func NewCapabilityStepDef(stepdefinition *v1beta1.WorkflowStepDefinition) Capabi
|
|||
}
|
||||
|
||||
// GetOpenAPISchema gets OpenAPI v3 schema by StepDefinition name
|
||||
func (def *CapabilityStepDefinition) GetOpenAPISchema(pd *packages.PackageDiscover, name string) ([]byte, error) {
|
||||
func (def *CapabilityStepDefinition) GetOpenAPISchema(name string) ([]byte, error) {
|
||||
capability, err := appfile.ConvertTemplateJSON2Object(name, nil, def.StepDefinition.Spec.Schematic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert WorkflowStepDefinition to Capability Object")
|
||||
}
|
||||
return getOpenAPISchema(capability, pd)
|
||||
return getOpenAPISchema(capability)
|
||||
}
|
||||
|
||||
// StoreOpenAPISchema stores OpenAPI v3 schema from StepDefinition in ConfigMap
|
||||
func (def *CapabilityStepDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, pd *packages.PackageDiscover, namespace, name string, revName string) (string, error) {
|
||||
func (def *CapabilityStepDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name string, revName string) (string, error) {
|
||||
var jsonSchema []byte
|
||||
var err error
|
||||
|
||||
jsonSchema, err = def.GetOpenAPISchema(pd, name)
|
||||
jsonSchema, err = def.GetOpenAPISchema(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err)
|
||||
}
|
||||
|
|
@ -582,21 +575,20 @@ func NewCapabilityPolicyDef(policydefinition *v1beta1.PolicyDefinition) Capabili
|
|||
}
|
||||
|
||||
// GetOpenAPISchema gets OpenAPI v3 schema by StepDefinition name
|
||||
func (def *CapabilityPolicyDefinition) GetOpenAPISchema(pd *packages.PackageDiscover, name string) ([]byte, error) {
|
||||
func (def *CapabilityPolicyDefinition) GetOpenAPISchema(name string) ([]byte, error) {
|
||||
capability, err := appfile.ConvertTemplateJSON2Object(name, nil, def.PolicyDefinition.Spec.Schematic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert WorkflowStepDefinition to Capability Object")
|
||||
}
|
||||
return getOpenAPISchema(capability, pd)
|
||||
return getOpenAPISchema(capability)
|
||||
}
|
||||
|
||||
// StoreOpenAPISchema stores OpenAPI v3 schema from StepDefinition in ConfigMap
|
||||
func (def *CapabilityPolicyDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client,
|
||||
pd *packages.PackageDiscover, namespace, name, revName string) (string, error) {
|
||||
func (def *CapabilityPolicyDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name, revName string) (string, error) {
|
||||
var jsonSchema []byte
|
||||
var err error
|
||||
|
||||
jsonSchema, err = def.GetOpenAPISchema(pd, name)
|
||||
jsonSchema, err = def.GetOpenAPISchema(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err)
|
||||
}
|
||||
|
|
@ -694,115 +686,18 @@ func (def *CapabilityBaseDefinition) CreateOrUpdateConfigMap(ctx context.Context
|
|||
}
|
||||
|
||||
// getOpenAPISchema is the main function for GetDefinition API
|
||||
func getOpenAPISchema(capability types.Capability, pd *packages.PackageDiscover) ([]byte, error) {
|
||||
openAPISchema, err := generateOpenAPISchemaFromCapabilityParameter(capability, pd)
|
||||
func getOpenAPISchema(capability types.Capability) ([]byte, error) {
|
||||
cueTemplate, err := script.PrepareTemplateCUEScript([]byte(capability.CueTemplate))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema, err := ConvertOpenAPISchema2SwaggerObject(openAPISchema)
|
||||
schema, err := cueTemplate.ParsePropertiesToSchema()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
FixOpenAPISchema("", schema)
|
||||
|
||||
parameter, err := schema.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parameter, nil
|
||||
}
|
||||
|
||||
// generateOpenAPISchemaFromCapabilityParameter returns the parameter of a definition in cue.Value format
|
||||
func generateOpenAPISchemaFromCapabilityParameter(capability types.Capability, pd *packages.PackageDiscover) ([]byte, error) {
|
||||
ctx := cuecontext.New()
|
||||
template, err := PrepareParameterCue(capability.Name, capability.CueTemplate)
|
||||
if err != nil {
|
||||
if errors.As(err, &ErrNoSectionParameterInCue{}) {
|
||||
// return OpenAPI with empty object parameter, making it possible to generate ConfigMap
|
||||
return common.GenOpenAPI(ctx.CompileString(""))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template += velacue.BaseTemplate
|
||||
val, err := value.NewValue(template, pd, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return common.GenOpenAPI(val.CueValue())
|
||||
}
|
||||
|
||||
// GenerateOpenAPISchemaFromDefinition returns the parameter of a definition
|
||||
func GenerateOpenAPISchemaFromDefinition(definitionName, cueTemplate string) ([]byte, error) {
|
||||
capability := types.Capability{
|
||||
Name: definitionName,
|
||||
CueTemplate: cueTemplate,
|
||||
}
|
||||
return generateOpenAPISchemaFromCapabilityParameter(capability, nil)
|
||||
}
|
||||
|
||||
// PrepareParameterCue cuts `parameter` section form definition .cue file
|
||||
func PrepareParameterCue(capabilityName, capabilityTemplate string) (string, error) {
|
||||
var template string
|
||||
var withParameterFlag bool
|
||||
r := regexp.MustCompile(`[[:space:]]*parameter:[[:space:]]*`)
|
||||
trimRe := regexp.MustCompile(`\s+`)
|
||||
|
||||
for _, text := range strings.Split(capabilityTemplate, "\n") {
|
||||
if r.MatchString(text) {
|
||||
// a variable has to be refined as a definition which starts with "#"
|
||||
// text may be start with space or tab, we should clean up text
|
||||
text = fmt.Sprintf("parameter: #parameter\n#%s", trimRe.ReplaceAllString(text, ""))
|
||||
withParameterFlag = true
|
||||
}
|
||||
template += fmt.Sprintf("%s\n", text)
|
||||
}
|
||||
|
||||
if !withParameterFlag {
|
||||
return "", ErrNoSectionParameterInCue{capName: capabilityName}
|
||||
}
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// FixOpenAPISchema fixes tainted `description` filed, missing of title `field`.
|
||||
func FixOpenAPISchema(name string, schema *openapi3.Schema) {
|
||||
t := schema.Type
|
||||
switch t {
|
||||
case "object":
|
||||
for k, v := range schema.Properties {
|
||||
s := v.Value
|
||||
FixOpenAPISchema(k, s)
|
||||
}
|
||||
case "array":
|
||||
if schema.Items != nil {
|
||||
FixOpenAPISchema("", schema.Items.Value)
|
||||
}
|
||||
}
|
||||
if name != "" {
|
||||
schema.Title = name
|
||||
}
|
||||
|
||||
description := schema.Description
|
||||
if strings.Contains(description, appfile.UsageTag) {
|
||||
description = strings.Split(description, appfile.UsageTag)[1]
|
||||
}
|
||||
if strings.Contains(description, appfile.ShortTag) {
|
||||
description = strings.Split(description, appfile.ShortTag)[0]
|
||||
description = strings.TrimSpace(description)
|
||||
}
|
||||
schema.Description = description
|
||||
}
|
||||
|
||||
// ConvertOpenAPISchema2SwaggerObject converts OpenAPI v2 JSON schema to Swagger Object
|
||||
func ConvertOpenAPISchema2SwaggerObject(data []byte) (*openapi3.Schema, error) {
|
||||
swagger, err := openapi3.NewLoader().LoadFromData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemaRef, ok := swagger.Components.Schemas[process.ParameterFieldName]
|
||||
if !ok {
|
||||
return nil, errors.New(util.ErrGenerateOpenAPIV2JSONSchemaForCapability)
|
||||
}
|
||||
return schemaRef.Value, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ spec:
|
|||
def := &CapabilityComponentDefinition{Name: componentDefinitionName, ComponentDefinition: *componentDefinition.DeepCopy()}
|
||||
|
||||
By("Test GetOpenAPISchema")
|
||||
schema, err := def.GetOpenAPISchema(pd, namespace)
|
||||
schema, err := def.GetOpenAPISchema(namespace)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(schema).Should(Not(BeNil()))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,20 +19,16 @@ package utils
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gotest.tools/assert"
|
||||
|
||||
"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/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
|
|
@ -182,7 +178,7 @@ parameter: [string]: string
|
|||
},
|
||||
}
|
||||
capability, _ := appfile.ConvertTemplateJSON2Object(tc.name, nil, schematic)
|
||||
schema, err := getOpenAPISchema(capability, pd)
|
||||
schema, err := getOpenAPISchema(capability)
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\ngetOpenAPISchema(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
|
|
@ -193,37 +189,6 @@ parameter: [string]: string
|
|||
}
|
||||
}
|
||||
|
||||
func TestFixOpenAPISchema(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
inputFile string
|
||||
fixedFile string
|
||||
}{
|
||||
"StandardWorkload": {
|
||||
inputFile: "webservice.json",
|
||||
fixedFile: "webserviceFixed.json",
|
||||
},
|
||||
"ShortTagJson": {
|
||||
inputFile: "shortTagSchema.json",
|
||||
fixedFile: "shortTagSchemaFixed.json",
|
||||
},
|
||||
"EmptyArrayJson": {
|
||||
inputFile: "arrayWithoutItemsSchema.json",
|
||||
fixedFile: "arrayWithoutItemsSchemaFixed.json",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
swagger, _ := openapi3.NewLoader().LoadFromFile(filepath.Join(TestDir, tc.inputFile))
|
||||
schema := swagger.Components.Schemas[process.ParameterFieldName].Value
|
||||
FixOpenAPISchema("", schema)
|
||||
fixedSchema, _ := schema.MarshalJSON()
|
||||
expectedSchema, _ := os.ReadFile(filepath.Join(TestDir, tc.fixedFile))
|
||||
assert.Equal(t, string(fixedSchema), string(expectedSchema))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCapabilityComponentDef(t *testing.T) {
|
||||
terraform := &common.Terraform{
|
||||
Configuration: "test",
|
||||
|
|
|
|||
|
|
@ -24,5 +24,6 @@ context: {
|
|||
name: string
|
||||
value: string
|
||||
}]
|
||||
...
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -158,3 +158,8 @@ func RetrieveComments(value cue.Value) (string, string, string, bool) {
|
|||
}
|
||||
return short, usage, alias, ignore
|
||||
}
|
||||
|
||||
// IsFieldNotExist check whether the error type is the field not found
|
||||
func IsFieldNotExist(err error) bool {
|
||||
return strings.Contains(err.Error(), "not exist")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
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 script
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
// ParsePropertiesToSchema parse the properties in cue script to the openapi schema
|
||||
// Read the template.parameter field
|
||||
func (c CUE) ParsePropertiesToSchema() (*openapi3.Schema, error) {
|
||||
val, err := c.ParseToValue(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template, err := val.LookupValue("template")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w cue script: %s", err, c)
|
||||
}
|
||||
data, err := common.GenOpenAPI(template.CueValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema, err := ConvertOpenAPISchema2SwaggerObject(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
FixOpenAPISchema("", schema)
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
// FixOpenAPISchema fixes tainted `description` filed, missing of title `field`.
|
||||
func FixOpenAPISchema(name string, schema *openapi3.Schema) {
|
||||
t := schema.Type
|
||||
switch t {
|
||||
case "object":
|
||||
for k, v := range schema.Properties {
|
||||
s := v.Value
|
||||
FixOpenAPISchema(k, s)
|
||||
}
|
||||
case "array":
|
||||
if schema.Items != nil {
|
||||
FixOpenAPISchema("", schema.Items.Value)
|
||||
}
|
||||
}
|
||||
if name != "" {
|
||||
schema.Title = name
|
||||
}
|
||||
|
||||
description := schema.Description
|
||||
if strings.Contains(description, appfile.UsageTag) {
|
||||
description = strings.Split(description, appfile.UsageTag)[1]
|
||||
}
|
||||
if strings.Contains(description, appfile.ShortTag) {
|
||||
description = strings.Split(description, appfile.ShortTag)[0]
|
||||
description = strings.TrimSpace(description)
|
||||
}
|
||||
schema.Description = description
|
||||
}
|
||||
|
||||
// ConvertOpenAPISchema2SwaggerObject converts OpenAPI v2 JSON schema to Swagger Object
|
||||
func ConvertOpenAPISchema2SwaggerObject(data []byte) (*openapi3.Schema, error) {
|
||||
swagger, err := openapi3.NewLoader().LoadFromData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemaRef, ok := swagger.Components.Schemas[process.ParameterFieldName]
|
||||
if !ok {
|
||||
return nil, errors.New(util.ErrGenerateOpenAPIV2JSONSchemaForCapability)
|
||||
}
|
||||
return schemaRef.Value, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
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 script
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
)
|
||||
|
||||
const TestDir = "testdata"
|
||||
|
||||
func TestFixOpenAPISchema(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
inputFile string
|
||||
fixedFile string
|
||||
}{
|
||||
"StandardWorkload": {
|
||||
inputFile: "webservice.json",
|
||||
fixedFile: "webserviceFixed.json",
|
||||
},
|
||||
"ShortTagJson": {
|
||||
inputFile: "shortTagSchema.json",
|
||||
fixedFile: "shortTagSchemaFixed.json",
|
||||
},
|
||||
"EmptyArrayJson": {
|
||||
inputFile: "arrayWithoutItemsSchema.json",
|
||||
fixedFile: "arrayWithoutItemsSchemaFixed.json",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
swagger, _ := openapi3.NewLoader().LoadFromFile(filepath.Join(TestDir, tc.inputFile))
|
||||
schema := swagger.Components.Schemas[process.ParameterFieldName].Value
|
||||
FixOpenAPISchema("", schema)
|
||||
fixedSchema, _ := schema.MarshalJSON()
|
||||
expectedSchema, _ := os.ReadFile(filepath.Join(TestDir, tc.fixedFile))
|
||||
assert.Equal(t, string(fixedSchema), string(expectedSchema))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
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 script
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue/errors"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue"
|
||||
)
|
||||
|
||||
// CUE the cue script with the template format
|
||||
// Like this:
|
||||
// ------------
|
||||
// metadata: {}
|
||||
//
|
||||
// template: {
|
||||
// parameter: {}
|
||||
// output: {}
|
||||
// }
|
||||
//
|
||||
// ------------
|
||||
type CUE string
|
||||
|
||||
// PrepareTemplateCUEScript insert the template path before the parameter field.
|
||||
// The input maybe includes the `package` keywords, so we can not make it as a value directly.
|
||||
// Only used to generate the API schema.
|
||||
func PrepareTemplateCUEScript(content []byte) (*CUE, error) {
|
||||
cueContent := string(content)
|
||||
v, err := value.NewValue(cueContent, nil, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to parse the cue script:%w", err)
|
||||
}
|
||||
_, err = v.LookupValue("template")
|
||||
if err != nil {
|
||||
if cue.IsFieldNotExist(err) {
|
||||
if p, err := v.LookupValue("parameter"); err == nil {
|
||||
ps, err := p.String()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cueContent = fmt.Sprintf("template: {\n parameter: {\n%s\n} \n}", ps)
|
||||
} else if cue.IsFieldNotExist(err) {
|
||||
cueContent += "\ntemplate: {\n parameter: {} \n}"
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("the template cue is invalid")
|
||||
}
|
||||
}
|
||||
cue := CUE(cueContent)
|
||||
return &cue, nil
|
||||
}
|
||||
|
||||
// BuildCUEScriptWithDefaultContext build a cue script instance from a byte array.
|
||||
func BuildCUEScriptWithDefaultContext(defaultContext []byte, content []byte) CUE {
|
||||
return CUE(content) + "\n" + CUE(defaultContext)
|
||||
}
|
||||
|
||||
// ParseToValue parse the cue script to cue.Value
|
||||
// If value.Error() is not nil and the checkAllFields is True, which will return the error.
|
||||
func (c CUE) ParseToValue(checkAllFields bool) (*value.Value, error) {
|
||||
// the cue script must be first, it could include the imports
|
||||
template := string(c) + "\n" + cue.BaseTemplate
|
||||
v, err := value.NewValue(template, nil, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to parse the template:%w", err)
|
||||
}
|
||||
// If the cue script reference the fields in the context, there is a error when we not provide the context struct.
|
||||
// For the ParsePropertiesToSchema and the ValidateProperties function, we do not need check all fields.
|
||||
if checkAllFields && v.Error() != nil {
|
||||
return nil, fmt.Errorf("the template cue is invalid: %w", v.Error())
|
||||
}
|
||||
_, err = v.LookupValue("template")
|
||||
if err != nil {
|
||||
if v.Error() != nil {
|
||||
return nil, fmt.Errorf("the template cue is invalid:%w", v.Error())
|
||||
}
|
||||
return nil, fmt.Errorf("the template cue must include the template field:%w", err)
|
||||
}
|
||||
_, err = v.LookupValue("template", "parameter")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the template cue must include the template.parameter field")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// MergeValues merge the input values to the cue script
|
||||
// The context variables could be referenced in all fields.
|
||||
// The parameter only could be referenced in the template area.
|
||||
func (c CUE) MergeValues(context interface{}, properties map[string]interface{}) (*value.Value, error) {
|
||||
parameterByte, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the parameter is invalid %w", err)
|
||||
}
|
||||
contextByte, err := json.Marshal(context)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the context is invalid %w", err)
|
||||
}
|
||||
var script = strings.Builder{}
|
||||
_, err = script.WriteString(string(c) + "\n")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if properties != nil {
|
||||
_, err = script.WriteString(fmt.Sprintf("template: parameter: %s \n", string(parameterByte)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if context != nil {
|
||||
_, err = script.WriteString(fmt.Sprintf("context: %s \n", string(contextByte)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
mergeValue, err := value.NewValue(script.String(), nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := mergeValue.CueValue().Validate(); err != nil {
|
||||
return nil, fmt.Errorf("fail to validate the merged value %w", err)
|
||||
}
|
||||
return mergeValue, nil
|
||||
}
|
||||
|
||||
// RunAndOutput run the cue script and return the values of the specified field.
|
||||
// The output field must be under the template field.
|
||||
func (c CUE) RunAndOutput(context interface{}, properties map[string]interface{}, outputField ...string) (*value.Value, error) {
|
||||
// Validate the properties
|
||||
if err := c.ValidateProperties(properties); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
render, err := c.MergeValues(context, properties)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to merge the properties to template %w", err)
|
||||
}
|
||||
if render.Error() != nil {
|
||||
return nil, fmt.Errorf("fail to merge the properties to template %w", render.Error())
|
||||
}
|
||||
if len(outputField) == 0 {
|
||||
outputField = []string{"template", "output"}
|
||||
}
|
||||
return render.LookupValue(outputField...)
|
||||
}
|
||||
|
||||
// ValidateProperties validate the input properties by the template
|
||||
func (c CUE) ValidateProperties(properties map[string]interface{}) error {
|
||||
template, err := c.ParseToValue(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parameter, err := template.LookupValue("template", "parameter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parameterStr, err := parameter.String()
|
||||
if err != nil {
|
||||
return fmt.Errorf("the parameter is invalid %w", err)
|
||||
}
|
||||
propertiesByte, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the properties is invalid %w", err)
|
||||
}
|
||||
newCue := strings.Builder{}
|
||||
newCue.WriteString(parameterStr + "\n")
|
||||
newCue.WriteString(string(propertiesByte) + "\n")
|
||||
value, err := value.NewValue(newCue.String(), nil, "")
|
||||
if err != nil {
|
||||
return ConvertFieldError(err)
|
||||
}
|
||||
if err := value.CueValue().Validate(); err != nil {
|
||||
return ConvertFieldError(err)
|
||||
}
|
||||
_, err = value.CueValue().MarshalJSON()
|
||||
if err != nil {
|
||||
return ConvertFieldError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParameterError the error report of the parameter field validation
|
||||
type ParameterError struct {
|
||||
Name string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error return the error message
|
||||
func (e *ParameterError) Error() string {
|
||||
return fmt.Sprintf("Field: %s Message: %s", e.Name, e.Message)
|
||||
}
|
||||
|
||||
// ConvertFieldError convert the cue error to the field error
|
||||
func ConvertFieldError(err error) error {
|
||||
var cueErr errors.Error
|
||||
if errors.As(err, &cueErr) {
|
||||
path := cueErr.Path()
|
||||
fieldName := path[len(path)-1]
|
||||
format, args := cueErr.Msg()
|
||||
message := fmt.Sprintf(format, args...)
|
||||
if strings.Contains(message, "cannot convert incomplete value") {
|
||||
message = "This parameter is required"
|
||||
}
|
||||
return &ParameterError{
|
||||
Name: fieldName,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
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 script
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
var templateScript = `
|
||||
metadata: {
|
||||
name: "helm-repository"
|
||||
alias: "Helm Repository"
|
||||
scope: "system"
|
||||
sensitive: false
|
||||
}
|
||||
template: {
|
||||
output: {
|
||||
url: parameter.url
|
||||
}
|
||||
parameter: {
|
||||
// +usage=The public url of the helm chart repository.
|
||||
url: string
|
||||
// +usage=The username of basic auth repo.
|
||||
username: string
|
||||
// +usage=The password of basic auth repo.
|
||||
password?: string
|
||||
// +usage=The ca certificate of helm repository. Please encode this data with base64.
|
||||
caFile?: string
|
||||
|
||||
options: "o1" | "o2"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var templateWithContextScript = `
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
metadata: {
|
||||
name: "helm-repository"
|
||||
alias: "Helm Repository"
|
||||
scope: "system"
|
||||
sensitive: false
|
||||
}
|
||||
template: {
|
||||
output: {
|
||||
url: parameter.url
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
sensitive: strconv.FormatBool(metadata.sensitive)
|
||||
}
|
||||
parameter: {
|
||||
// +usage=The public url of the helm chart repository.
|
||||
url: string
|
||||
// +usage=The username of basic auth repo.
|
||||
username: string
|
||||
// +usage=The password of basic auth repo.
|
||||
password?: string
|
||||
// +usage=The ca certificate of helm repository. Please encode this data with base64.
|
||||
caFile?: string
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var withPackage = `
|
||||
|
||||
package main
|
||||
|
||||
const: {
|
||||
// +usage=The name of the addon application
|
||||
name: "addon-loki"
|
||||
}
|
||||
|
||||
parameter: {
|
||||
|
||||
// global parameters
|
||||
|
||||
// +usage=The namespace of the loki to be installed
|
||||
namespace: *"o11y-system" | string
|
||||
// +usage=The clusters to install
|
||||
clusters?: [...string]
|
||||
|
||||
// loki parameters
|
||||
|
||||
// +usage=Specify the image of loki
|
||||
image: *"grafana/loki" | string
|
||||
// +usage=Specify the imagePullPolicy of the image
|
||||
imagePullPolicy: *"IfNotPresent" | "Never" | "Always"
|
||||
// +usage=Specify the service type for expose loki. If empty, it will be not exposed.
|
||||
serviceType: *"ClusterIP" | "NodePort" | "LoadBalancer" | ""
|
||||
// +usage=Specify the storage size to use. If empty, emptyDir will be used. Otherwise pvc will be used.
|
||||
storage?: =~"^([1-9][0-9]{0,63})(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)$"
|
||||
// +usage=Specify the storage class to use.
|
||||
storageClassName?: string
|
||||
|
||||
// agent parameters
|
||||
|
||||
// +usage=Specify the type of log agents, if empty, no agent will be installed
|
||||
agent: *"" | "vector" | "promtail"
|
||||
// +usage=Specify the image of promtail
|
||||
promtailImage: *"grafana/promtail" | string
|
||||
// +usage=Specify the image of vector
|
||||
vectorImage: *"timberio/vector:0.24.0-distroless-libc" | string
|
||||
}
|
||||
`
|
||||
|
||||
var withImport = `
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
apply: op.#Apply & {
|
||||
value: parameter.value
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the value of the object
|
||||
value: {...}
|
||||
// +usage=Specify the cluster of the object
|
||||
cluster: *"" | string
|
||||
}`
|
||||
|
||||
func TestMergeValues(t *testing.T) {
|
||||
var cueScript = CUE(templateScript)
|
||||
value, err := cueScript.MergeValues(nil, map[string]interface{}{
|
||||
"url": "hub.docker.com",
|
||||
"username": "name",
|
||||
})
|
||||
assert.Equal(t, err, nil)
|
||||
output, err := value.LookupValue("template", "output")
|
||||
assert.Equal(t, err, nil)
|
||||
var data = map[string]interface{}{}
|
||||
err = output.UnmarshalTo(&data)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, data["url"], "hub.docker.com")
|
||||
}
|
||||
|
||||
func TestRunAndOutput(t *testing.T) {
|
||||
var cueScript = BuildCUEScriptWithDefaultContext([]byte("context:{namespace:string \n name:string}"), []byte(templateWithContextScript))
|
||||
output, err := cueScript.RunAndOutput(map[string]interface{}{
|
||||
"name": "nnn",
|
||||
"namespace": "ns",
|
||||
}, map[string]interface{}{
|
||||
"url": "hub.docker.com",
|
||||
"username": "test",
|
||||
"password": "test",
|
||||
"caFile": "test ca",
|
||||
})
|
||||
assert.Equal(t, err, nil)
|
||||
var data = map[string]interface{}{}
|
||||
err = output.UnmarshalTo(&data)
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, data["name"], "nnn")
|
||||
assert.Equal(t, data["namespace"], "ns")
|
||||
assert.Equal(t, data["url"], "hub.docker.com")
|
||||
}
|
||||
|
||||
func TestValidateProperties(t *testing.T) {
|
||||
var cueScript = CUE(templateScript)
|
||||
// miss the required parameter
|
||||
err := cueScript.ValidateProperties(map[string]interface{}{
|
||||
"url": "hub.docker.com",
|
||||
})
|
||||
assert.Equal(t, err.(*ParameterError).Message, "This parameter is required")
|
||||
|
||||
// wrong the parameter value type
|
||||
err = cueScript.ValidateProperties(map[string]interface{}{
|
||||
"url": 1,
|
||||
"username": "ddd",
|
||||
})
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "conflicting values"), true)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Name, "url"), true)
|
||||
|
||||
// wrong the parameter value
|
||||
err = cueScript.ValidateProperties(map[string]interface{}{
|
||||
"url": "ddd",
|
||||
"username": "ddd",
|
||||
})
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "This parameter is required"), true)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Name, "options"), true)
|
||||
|
||||
// wrong the parameter value and no required value
|
||||
err = cueScript.ValidateProperties(map[string]interface{}{
|
||||
"url": "ddd",
|
||||
"username": "ddd",
|
||||
"options": "o3",
|
||||
})
|
||||
fmt.Println(err.(*ParameterError).Message)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Name, "options"), true)
|
||||
assert.Equal(t, strings.Contains(err.(*ParameterError).Message, "2 errors in empty disjunction"), true)
|
||||
}
|
||||
|
||||
func TestBuildCUEScript(t *testing.T) {
|
||||
cue, err := PrepareTemplateCUEScript([]byte(withPackage))
|
||||
assert.Equal(t, err, nil)
|
||||
schema, err := cue.ParsePropertiesToSchema()
|
||||
assert.Equal(t, err, nil)
|
||||
assert.Equal(t, len(schema.Properties), 10)
|
||||
|
||||
cue, err = PrepareTemplateCUEScript([]byte(withImport))
|
||||
assert.Equal(t, err, nil)
|
||||
_, err = cue.ParsePropertiesToSchema()
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
|
|
@ -177,6 +177,11 @@ import (
|
|||
|
||||
#SendEmail: email.#Send
|
||||
|
||||
#CreateConfig: config.#Create
|
||||
#DeleteConfig: config.#Delete
|
||||
#ReadConfig: config.#Read
|
||||
#ListConfig: config.#List
|
||||
|
||||
#Load: oam.#LoadComponets
|
||||
|
||||
#LoadInOrder: oam.#LoadComponetsInOrder
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
#Create: {
|
||||
#do: "create"
|
||||
#provider: "config"
|
||||
|
||||
name: string
|
||||
namespace: string
|
||||
template?: string
|
||||
config: {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
#Delete: {
|
||||
#do: "delete"
|
||||
#provider: "config"
|
||||
|
||||
name: string
|
||||
namespace: string
|
||||
}
|
||||
|
||||
#Read: {
|
||||
#do: "delete"
|
||||
#provider: "config"
|
||||
|
||||
name: string
|
||||
namespace: string
|
||||
|
||||
config: {...}
|
||||
}
|
||||
|
||||
#List: {
|
||||
#do: "delete"
|
||||
#provider: "config"
|
||||
|
||||
// Must query with the template
|
||||
template: string
|
||||
namespace: string
|
||||
|
||||
configs: [...{...}]
|
||||
}
|
||||
|
|
@ -31,12 +31,14 @@ import (
|
|||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -58,6 +60,7 @@ type applyAction struct {
|
|||
skipUpdate bool
|
||||
updateAnnotation bool
|
||||
dryRun bool
|
||||
quiet bool
|
||||
}
|
||||
|
||||
// ApplyOption is called before applying state to the object.
|
||||
|
|
@ -104,7 +107,10 @@ type APIApplicator struct {
|
|||
}
|
||||
|
||||
// loggingApply will record a log with desired object applied
|
||||
func loggingApply(msg string, desired client.Object) {
|
||||
func loggingApply(msg string, desired client.Object, quiet bool) {
|
||||
if quiet {
|
||||
return
|
||||
}
|
||||
d, ok := desired.(metav1.Object)
|
||||
if !ok {
|
||||
klog.InfoS(msg, "resource", desired.GetObjectKind().GroupVersionKind().String())
|
||||
|
|
@ -166,13 +172,13 @@ func (a *APIApplicator) Apply(ctx context.Context, desired client.Object, ao ...
|
|||
}
|
||||
|
||||
if applyAct.skipUpdate {
|
||||
loggingApply("skip update", desired)
|
||||
loggingApply("skip update", desired, applyAct.quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case utilfeature.DefaultMutableFeatureGate.Enabled(features.ApplyResourceByUpdate) && isUpdatableResource(desired):
|
||||
loggingApply("updating object", desired)
|
||||
loggingApply("updating object", desired, applyAct.quiet)
|
||||
desired.SetResourceVersion(existing.GetResourceVersion())
|
||||
var options []client.UpdateOption
|
||||
if applyAct.dryRun {
|
||||
|
|
@ -180,7 +186,7 @@ func (a *APIApplicator) Apply(ctx context.Context, desired client.Object, ao ...
|
|||
}
|
||||
return errors.Wrapf(a.c.Update(ctx, desired, options...), "cannot update object")
|
||||
default:
|
||||
loggingApply("patching object", desired)
|
||||
loggingApply("patching object", desired, applyAct.quiet)
|
||||
patch, err := a.patcher.patch(existing, desired, applyAct)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot calculate patch by computing a three way diff")
|
||||
|
|
@ -227,13 +233,20 @@ func createOrGetExisting(ctx context.Context, act *applyAction, c client.Client,
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
loggingApply("creating object", desired)
|
||||
loggingApply("creating object", desired, act.quiet)
|
||||
if act.dryRun {
|
||||
return nil, errors.Wrap(c.Create(ctx, desired, client.DryRunAll), "cannot create object")
|
||||
}
|
||||
return nil, errors.Wrap(c.Create(ctx, desired), "cannot create object")
|
||||
}
|
||||
|
||||
if desired.GetObjectKind().GroupVersionKind().Kind == "" {
|
||||
gvk, err := apiutil.GVKForObject(desired, common.Scheme)
|
||||
if err == nil {
|
||||
desired.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
// allow to create object with only generateName
|
||||
if desired.GetName() == "" && desired.GetGenerateName() != "" {
|
||||
return create()
|
||||
|
|
@ -409,6 +422,14 @@ func DryRunAll() ApplyOption {
|
|||
}
|
||||
}
|
||||
|
||||
// Quiet means disable the logger
|
||||
func Quiet() ApplyOption {
|
||||
return func(a *applyAction, existing, _ client.Object) error {
|
||||
a.quiet = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// isUpdatableResource check whether the resource is updatable
|
||||
// Resource like v1.Service cannot unset the spec field (the ip spec is filled by service controller)
|
||||
func isUpdatableResource(desired client.Object) bool {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
|
|
@ -259,6 +260,7 @@ func GenOpenAPI(val cue.Value) (b []byte, err error) {
|
|||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("invalid cue definition to generate open api: %v", r)
|
||||
debug.PrintStack()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
|
@ -293,7 +295,7 @@ func RefineParameterValue(val cue.Value) (cue.Value, error) {
|
|||
case cue.BottomKind:
|
||||
paramOnlyStr = fmt.Sprintf("#%s: {}", process.ParameterFieldName)
|
||||
default:
|
||||
return cue.Value{}, fmt.Errorf("unsupport parameter kind: %s", k.String())
|
||||
return cue.Value{}, fmt.Errorf("unsupported parameter kind: %s", k.String())
|
||||
}
|
||||
paramOnlyVal := cuecontext.New().CompileString(paramOnlyStr)
|
||||
if paramOnlyVal.Err() != nil {
|
||||
|
|
|
|||
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
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/domain/model"
|
||||
)
|
||||
|
||||
const (
|
||||
errProviderExists = "terraform provider %s for %s already exists"
|
||||
errDeleteProvider = "failed to delete Terraform Provider %s err: %w"
|
||||
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, errCheckProviderExistence, name)
|
||||
}
|
||||
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) {
|
||||
return fmt.Errorf(errDeleteProvider, name, 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, err2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
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
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +116,10 @@ func NewCommandWithIOStreams(ioStream util.IOStreams) *cobra.Command {
|
|||
AuthCommandGroup(f, ioStream),
|
||||
KubeCommandGroup(f, ioStream),
|
||||
|
||||
// Config management
|
||||
ConfigCommandGroup(f, ioStream),
|
||||
TemplateCommandGroup(f, ioStream),
|
||||
|
||||
// System
|
||||
NewInstallCommand(commandArgs, "1", ioStream),
|
||||
NewUnInstallCommand(commandArgs, "2", ioStream),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,631 @@
|
|||
/*
|
||||
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 cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"helm.sh/helm/v3/pkg/strvals"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/docgen"
|
||||
)
|
||||
|
||||
// ConfigCommandGroup commands for the config
|
||||
func ConfigCommandGroup(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: i18n.T("Manage the configs."),
|
||||
Long: i18n.T("Manage the configs, such as the terraform provider, image registry, helm repository, etc."),
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(NewListConfigCommand(f, streams))
|
||||
cmd.AddCommand(NewCreateConfigCommand(f, streams))
|
||||
cmd.AddCommand(NewDistributeConfigCommand(f, streams))
|
||||
cmd.AddCommand(NewDeleteConfigCommand(f, streams))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TemplateCommandGroup commands for the template of the config
|
||||
func TemplateCommandGroup(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config-template",
|
||||
Aliases: []string{"ct"},
|
||||
Short: i18n.T("Manage the template of config."),
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeExtension,
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(NewTemplateApplyCommand(f, streams))
|
||||
cmd.AddCommand(NewTemplateListCommand(f, streams))
|
||||
cmd.AddCommand(NewTemplateDeleteCommand(f, streams))
|
||||
cmd.AddCommand(NewTemplateShowCommand(f, streams))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TemplateApplyCommandOptions the options of the command that apply the config template.
|
||||
type TemplateApplyCommandOptions struct {
|
||||
File string
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// TemplateCommandOptions the options of the command that delete or show the config template.
|
||||
type TemplateCommandOptions struct {
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// TemplateListCommandOptions the options of the command that list the config templates.
|
||||
type TemplateListCommandOptions struct {
|
||||
Namespace string
|
||||
AllNamespace bool
|
||||
}
|
||||
|
||||
// NewTemplateApplyCommand command for creating and updating the config template
|
||||
func NewTemplateApplyCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options TemplateApplyCommandOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: i18n.T("Apply a config template."),
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeExtension,
|
||||
},
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
body, err := pkgUtils.ReadRemoteOrLocalPath(options.File, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
template, err := inf.ParseTemplate(options.Name, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := inf.CreateOrUpdateConfigTemplate(context.Background(), options.Namespace, template); err != nil {
|
||||
return err
|
||||
}
|
||||
streams.Infof("the config template %s applied successfully\n", template.Name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.File, "file", "f", "", "specify the template file name")
|
||||
cmd.Flags().StringVarP(&options.Name, "name", "", "", "specify the config template name")
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewTemplateListCommand command for listing the config templates
|
||||
func NewTemplateListCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options TemplateListCommandOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: i18n.T("List the config templates."),
|
||||
Example: "vela config template list [-A]",
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeExtension,
|
||||
},
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
if options.AllNamespace {
|
||||
options.Namespace = ""
|
||||
}
|
||||
templates, err := inf.ListTemplates(context.Background(), options.Namespace, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
table := newUITable()
|
||||
header := []interface{}{"NAME", "ALIAS", "SCOPE", "SENSITIVE", "CREATED-TIME"}
|
||||
if options.AllNamespace {
|
||||
header = append([]interface{}{"NAMESPACE"}, header...)
|
||||
}
|
||||
table.AddRow(header...)
|
||||
for _, t := range templates {
|
||||
row := []interface{}{t.Name, t.Alias, t.Scope, t.Sensitive, t.CreateTime}
|
||||
if options.AllNamespace {
|
||||
row = append([]interface{}{t.Namespace}, row...)
|
||||
}
|
||||
table.AddRow(row...)
|
||||
}
|
||||
if _, err := streams.Out.Write(table.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := streams.Out.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template")
|
||||
cmd.Flags().BoolVarP(&options.AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewTemplateShowCommand command for show the properties document
|
||||
func NewTemplateShowCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options TemplateCommandOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: i18n.T("Show the documents of the template properties"),
|
||||
Example: "vela config-template show <name> [-n]",
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeExtension,
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.Name = args[0]
|
||||
if options.Name == "" {
|
||||
return fmt.Errorf("can not show the properties without the template")
|
||||
}
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
template, err := inf.LoadTemplate(context.Background(), options.Name, options.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
doc, err := docgen.GenerateConsoleDocument(template.Schema.Title, template.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := streams.Out.Write([]byte(doc)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewTemplateDeleteCommand command for deleting the config template
|
||||
func NewTemplateDeleteCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options TemplateCommandOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: i18n.T("Delete a config template."),
|
||||
Example: fmt.Sprintf("vela config template delete <name> [-n %s]", types.DefaultKubeVelaNS),
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("please must provides the template name")
|
||||
}
|
||||
options.Name = args[0]
|
||||
userInput := &UserInput{
|
||||
Writer: streams.Out,
|
||||
Reader: bufio.NewReader(streams.In),
|
||||
}
|
||||
if !assumeYes {
|
||||
userConfirmation := userInput.AskBool("Do you want to delete this template", &UserInputOptions{assumeYes})
|
||||
if !userConfirmation {
|
||||
return fmt.Errorf("stopping deleting")
|
||||
}
|
||||
}
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
if err := inf.DeleteTemplate(context.Background(), options.Namespace, options.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
streams.Infof("the config template %s deleted successfully\n", options.Name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// DistributeConfigCommandOptions the options of the command that distribute the config.
|
||||
type DistributeConfigCommandOptions struct {
|
||||
Targets []string
|
||||
Config string
|
||||
Namespace string
|
||||
Recalled bool
|
||||
}
|
||||
|
||||
// CreateConfigCommandOptions the options of the command that create the config.
|
||||
type CreateConfigCommandOptions struct {
|
||||
Template string
|
||||
Namespace string
|
||||
Name string
|
||||
File string
|
||||
Properties map[string]interface{}
|
||||
DryRun bool
|
||||
Targets []string
|
||||
Description string
|
||||
Alias string
|
||||
}
|
||||
|
||||
// Validate validate the options
|
||||
func (i CreateConfigCommandOptions) Validate() error {
|
||||
if i.Name == "" {
|
||||
return fmt.Errorf("the config name must be specified")
|
||||
}
|
||||
if len(i.Targets) > 0 && i.DryRun {
|
||||
return fmt.Errorf("can not set the distribution in dry-run mode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *CreateConfigCommandOptions) parseProperties(args []string) error {
|
||||
if i.File != "" {
|
||||
body, err := pkgUtils.ReadRemoteOrLocalPath(i.File, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var properties = map[string]interface{}{}
|
||||
if err := yaml.Unmarshal(body, &properties); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Properties = properties
|
||||
return nil
|
||||
}
|
||||
res := map[string]interface{}{}
|
||||
for _, arg := range args {
|
||||
if err := strvals.ParseInto(arg, res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
i.Properties = res
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigListCommandOptions the options of the command that list configs.
|
||||
type ConfigListCommandOptions struct {
|
||||
Namespace string
|
||||
Template string
|
||||
AllNamespace bool
|
||||
}
|
||||
|
||||
// NewListConfigCommand command for listing the config secrets
|
||||
func NewListConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options ConfigListCommandOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: i18n.T("List the configs."),
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := options.Template
|
||||
if strings.Contains(options.Template, "/") {
|
||||
namespacedName := strings.SplitN(options.Template, "/", 2)
|
||||
name = namespacedName[1]
|
||||
}
|
||||
if options.AllNamespace {
|
||||
options.Namespace = ""
|
||||
}
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
configs, err := inf.ListConfigs(context.Background(), options.Namespace, name, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
table := newUITable()
|
||||
header := []interface{}{"NAME", "ALIAS", "DISTRIBUTION", "TEMPLATE", "CREATED-TIME", "DESCRIPTION"}
|
||||
if options.AllNamespace {
|
||||
header = append([]interface{}{"NAMESPACE"}, header...)
|
||||
}
|
||||
table.AddRow(header...)
|
||||
for _, t := range configs {
|
||||
var targetShow = ""
|
||||
for _, target := range t.Targets {
|
||||
if targetShow != "" {
|
||||
targetShow += " "
|
||||
}
|
||||
switch target.Status {
|
||||
case string(workflowv1alpha1.WorkflowStepPhaseSucceeded):
|
||||
targetShow += green.Sprintf("%s/%s", target.ClusterName, target.Namespace)
|
||||
case string(workflowv1alpha1.WorkflowStepPhaseFailed):
|
||||
targetShow += red.Sprintf("%s/%s", target.ClusterName, target.Namespace)
|
||||
default:
|
||||
targetShow += yellow.Sprintf("%s/%s", target.ClusterName, target.Namespace)
|
||||
}
|
||||
}
|
||||
row := []interface{}{t.Name, t.Alias, targetShow, fmt.Sprintf("%s/%s", t.Template.Namespace, t.Template.Name), t.CreateTime, t.Description}
|
||||
if options.AllNamespace {
|
||||
row = append([]interface{}{t.Namespace}, row...)
|
||||
}
|
||||
table.AddRow(row...)
|
||||
}
|
||||
if _, err := streams.Out.Write(table.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := streams.Out.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the config")
|
||||
cmd.Flags().StringVarP(&options.Template, "template", "t", "", "specify the template of the config")
|
||||
cmd.Flags().BoolVarP(&options.AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewCreateConfigCommand command for creating the config
|
||||
func NewCreateConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options CreateConfigCommandOptions
|
||||
createConfigExample := templates.Examples(i18n.T(`
|
||||
# Generate a config with the args
|
||||
vela config create test-registry --template=image-registry registry=index.docker.io auth.username=test auth.password=test
|
||||
|
||||
# Generate a config with the file
|
||||
vela config create test-config --template=image-registry -f config.yaml
|
||||
|
||||
# Generate a config without the template
|
||||
vela config create --name test-vela -f config.yaml
|
||||
`))
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Aliases: []string{"c"},
|
||||
Short: i18n.T("Create a config."),
|
||||
Example: createConfigExample,
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
options.Name = args[0]
|
||||
if err := options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
name := options.Template
|
||||
namespace := types.DefaultKubeVelaNS
|
||||
if strings.Contains(options.Template, "/") {
|
||||
namespacedName := strings.SplitN(options.Template, "/", 2)
|
||||
namespace = namespacedName[0]
|
||||
name = namespacedName[1]
|
||||
}
|
||||
if err := options.parseProperties(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
configItem, err := inf.ParseConfig(context.Background(), config.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}, config.Metadata{
|
||||
NamespacedName: config.NamespacedName{
|
||||
Name: options.Name,
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
Properties: options.Properties,
|
||||
Alias: options.Alias,
|
||||
Description: options.Description,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if options.DryRun {
|
||||
var outBuilder = bytes.NewBuffer(nil)
|
||||
out, err := yaml.Marshal(configItem.Secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = outBuilder.Write(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if configItem.OutputObjects != nil {
|
||||
for k, object := range configItem.OutputObjects {
|
||||
_, err = outBuilder.WriteString("# Object: \n ---" + k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := yaml.Marshal(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := outBuilder.Write(out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = streams.Out.Write(outBuilder.Bytes())
|
||||
return err
|
||||
}
|
||||
if err := inf.CreateOrUpdateConfig(context.Background(), configItem, options.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(options.Targets) > 0 {
|
||||
ads := &config.CreateDistributionSpec{
|
||||
Targets: []*config.ClusterTarget{},
|
||||
Configs: []*config.NamespacedName{
|
||||
&configItem.NamespacedName,
|
||||
},
|
||||
}
|
||||
for _, t := range options.Targets {
|
||||
ti := strings.Split(t, "/")
|
||||
if len(ti) == 2 {
|
||||
ads.Targets = append(ads.Targets, &config.ClusterTarget{
|
||||
ClusterName: ti[0],
|
||||
Namespace: ti[1],
|
||||
})
|
||||
} else {
|
||||
ads.Targets = append(ads.Targets, &config.ClusterTarget{
|
||||
ClusterName: types.ClusterLocalName,
|
||||
Namespace: ti[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
name := config.DefaultDistributionName(options.Name)
|
||||
if err := inf.CreateOrUpdateDistribution(context.Background(), options.Namespace, name, ads); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
streams.Infof("the config %s applied successfully\n", options.Name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.Template, "template", "t", "", "specify the config template name and namespace")
|
||||
cmd.Flags().StringVarP(&options.File, "file", "f", "", "specify the file name of the config properties")
|
||||
cmd.Flags().StringArrayVarP(&options.Targets, "target", "", []string{}, "this config will be distributed if this flag is set")
|
||||
cmd.Flags().BoolVarP(&options.DryRun, "dry-run", "", false, "Dry run to apply the config")
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the config")
|
||||
cmd.Flags().StringVarP(&options.Description, "description", "", "", "specify the description of the config")
|
||||
cmd.Flags().StringVarP(&options.Alias, "alias", "", "", "specify the alias of the config")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewDistributeConfigCommand command for distributing the config
|
||||
func NewDistributeConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options DistributeConfigCommandOptions
|
||||
|
||||
distributionExample := templates.Examples(i18n.T(`
|
||||
# distribute the config(test-registry) from the vela-system namespace to the default namespace in the local cluster.
|
||||
vela config d test-registry -t default
|
||||
|
||||
# distribute the config(test-registry) from the vela-system namespace to the other clusters.
|
||||
vela config d test-registry -t cluster1/default -t cluster2/default
|
||||
|
||||
# recall the config
|
||||
vela config d test-registry --recall
|
||||
`))
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "distribute",
|
||||
Aliases: []string{"d"},
|
||||
Short: i18n.T("Distribute a config"),
|
||||
Example: distributionExample,
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
options.Config = args[0]
|
||||
name := config.DefaultDistributionName(options.Config)
|
||||
if options.Recalled {
|
||||
userInput := &UserInput{
|
||||
Writer: streams.Out,
|
||||
Reader: bufio.NewReader(streams.In),
|
||||
}
|
||||
if !assumeYes {
|
||||
userConfirmation := userInput.AskBool("Do you want to recall this config", &UserInputOptions{assumeYes})
|
||||
if !userConfirmation {
|
||||
return fmt.Errorf("recalling stopped")
|
||||
}
|
||||
}
|
||||
if err := inf.DeleteDistribution(context.Background(), options.Namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
streams.Infof("the distribution %s deleted successfully\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
ads := &config.CreateDistributionSpec{
|
||||
Targets: []*config.ClusterTarget{},
|
||||
Configs: []*config.NamespacedName{
|
||||
{
|
||||
Name: options.Config,
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, t := range options.Targets {
|
||||
ti := strings.Split(t, "/")
|
||||
if len(ti) == 2 {
|
||||
ads.Targets = append(ads.Targets, &config.ClusterTarget{
|
||||
ClusterName: ti[0],
|
||||
Namespace: ti[1],
|
||||
})
|
||||
} else {
|
||||
ads.Targets = append(ads.Targets, &config.ClusterTarget{
|
||||
ClusterName: types.ClusterLocalName,
|
||||
Namespace: ti[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := inf.CreateOrUpdateDistribution(context.Background(), options.Namespace, name, ads); err != nil {
|
||||
return err
|
||||
}
|
||||
streams.Infof("the distribution %s applied successfully\n", name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringArrayVarP(&options.Targets, "target", "t", []string{}, "specify the targets that want to distribute,the format is: <clusterName>/<namespace>")
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the distribution")
|
||||
cmd.Flags().BoolVarP(&options.Recalled, "recall", "r", false, "this field means recalling the configs from all targets.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ConfigDeleteCommandOptions the options of the command that delete the config.
|
||||
type ConfigDeleteCommandOptions struct {
|
||||
Namespace string
|
||||
Name string
|
||||
NotRecall bool
|
||||
}
|
||||
|
||||
// NewDeleteConfigCommand command for deleting the config secret
|
||||
func NewDeleteConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command {
|
||||
var options ConfigDeleteCommandOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: i18n.T("Delete a config."),
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.Name = args[0]
|
||||
inf := config.NewConfigFactory(f.Client())
|
||||
userInput := &UserInput{
|
||||
Writer: streams.Out,
|
||||
Reader: bufio.NewReader(streams.In),
|
||||
}
|
||||
if !assumeYes {
|
||||
userConfirmation := userInput.AskBool("Do you want to delete this config", &UserInputOptions{assumeYes})
|
||||
if !userConfirmation {
|
||||
return fmt.Errorf("deleting stopped")
|
||||
}
|
||||
}
|
||||
|
||||
if !options.NotRecall {
|
||||
if err := inf.DeleteDistribution(context.Background(), options.Namespace, config.DefaultDistributionName(options.Name)); err != nil && !errors.Is(err, config.ErrNotFoundDistribution) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := inf.DeleteConfig(context.Background(), options.Namespace, options.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
streams.Infof("the config %s deleted successfully\n", options.Name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the config")
|
||||
cmd.Flags().BoolVarP(&options.NotRecall, "not-recall", "", false, "means only deleting the config from the local and do not recall from targets.")
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
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 cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cmd"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test the commands of the config", func() {
|
||||
var arg cmd.Factory
|
||||
BeforeEach(func() {
|
||||
arg = cmd.NewTestFactory(cfg, k8sClient)
|
||||
})
|
||||
|
||||
It("Test apply a template", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := TemplateCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"apply", "-f", "./test-data/config-templates/image-registry.cue", "--name", "test"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the config template test applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Test apply a new template", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := TemplateCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"apply", "-f", "./test-data/config-templates/image-registry.cue", "--name", "test2"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the config template test2 applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Test list the templates", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := TemplateCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"list", "-A"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(strings.Contains(buffer.String(), "vela-system")).Should(Equal(true))
|
||||
Expect(strings.Contains(buffer.String(), "test")).Should(Equal(true))
|
||||
Expect(strings.Contains(buffer.String(), "\n")).Should(Equal(true))
|
||||
})
|
||||
|
||||
It("Test show the templates", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := TemplateCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"show", "test2"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(line(buffer.String())).Should(Equal(24))
|
||||
})
|
||||
|
||||
It("Test create the config with the args", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"create", "test", "--template=test", "registry=test.kubevela.net", "auth.username=yueda", "auth.password=yueda123", "useHTTP=true"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the config test applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Test create the config with the file", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"create", "testfile", "--template=test2", "--namespace=default", "-f", "./test-data/config/registry.yaml"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the config testfile applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Test create the config without the template", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"create", "without-template", "--namespace=default", "-f", "./test-data/config/registry.yaml"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the config without-template applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Test creating and distributing the config", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"create", "distribution", "--namespace=default", "-f", "./test-data/config/registry.yaml", "--target", "test"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the config distribution applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Test list the configs", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"list", "-A"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(line(buffer.String())).Should(Equal(5))
|
||||
})
|
||||
|
||||
It("Test list the configs with the namespace filter", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"list", "-n", "default"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(line(buffer.String())).Should(Equal(4))
|
||||
})
|
||||
|
||||
It("Test list the configs with the template filter", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"list", "-A", "-t", "test2"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(line(buffer.String())).Should(Equal(2))
|
||||
})
|
||||
|
||||
It("Test dry run the config", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"create", "testfile", "--template=test", "-f", "./test-data/config/registry.yaml", "--dry-run"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
var secret v1.Secret
|
||||
Expect(yaml.Unmarshal(buffer.Bytes(), &secret)).Should(BeNil())
|
||||
Expect(secret.Name).Should(Equal("testfile"))
|
||||
Expect(secret.Labels["config.oam.dev/type"]).Should(Equal("test"))
|
||||
Expect(secret.Labels["config.oam.dev/catalog"]).Should(Equal("velacore-config"))
|
||||
Expect(string(secret.Type)).Should(Equal("kubernetes.io/dockerconfigjson"))
|
||||
})
|
||||
|
||||
It("Distribute a config", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: os.Stdin, Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"distribute", "testfile", "-t", "test"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("the distribution distribute-testfile applied successfully\n"))
|
||||
})
|
||||
|
||||
It("Recall a config", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: strings.NewReader("y\n"), Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"distribute", "testfile", "--recall"})
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("Do you want to recall this config (y/n)the distribution distribute-testfile deleted successfully\n"))
|
||||
})
|
||||
|
||||
It("Test delete a config", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := ConfigCommandGroup(arg, util.IOStreams{In: strings.NewReader("y\n"), Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"delete", "distribution", "-n", "default"})
|
||||
assumeYes = false
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("Do you want to delete this config (y/n)the config distribution deleted successfully\n"))
|
||||
})
|
||||
|
||||
It("Test delete a template", func() {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
cmd := TemplateCommandGroup(arg, util.IOStreams{In: strings.NewReader("y\n"), Out: buffer, ErrOut: buffer})
|
||||
cmd.SetArgs([]string{"delete", "test"})
|
||||
assumeYes = false
|
||||
err := cmd.Execute()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buffer.String()).Should(Equal("Do you want to delete this template (y/n)the config template test deleted successfully\n"))
|
||||
})
|
||||
})
|
||||
|
||||
func line(data string) int {
|
||||
return len(strings.Split(strings.TrimRight(data, "\n"), "\n"))
|
||||
}
|
||||
|
|
@ -18,36 +18,19 @@ package cli
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
tcv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"github.com/pkg/errors"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"github.com/spf13/cobra"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/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/docgen"
|
||||
)
|
||||
|
||||
const (
|
||||
providerNameParam = "name"
|
||||
errAuthenticateProvider = "failed to authenticate Terraform cloud provider %s err: %w"
|
||||
)
|
||||
|
||||
// NewProviderCommand create `addon` command
|
||||
// +Deprecated
|
||||
func NewProviderCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "provider",
|
||||
|
|
@ -61,8 +44,8 @@ func NewProviderCommand(c common.Args, order string, ioStreams cmdutil.IOStreams
|
|||
cmd.AddCommand(
|
||||
NewProviderListCommand(c, ioStreams),
|
||||
)
|
||||
cmd.AddCommand(prepareProviderAddCommand(c, ioStreams))
|
||||
cmd.AddCommand(prepareProviderDeleteCommand(c, ioStreams))
|
||||
cmd.AddCommand(prepareProviderAddCommand())
|
||||
cmd.AddCommand(prepareProviderDeleteCommand())
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -87,345 +70,35 @@ func NewProviderListCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.C
|
|||
}
|
||||
}
|
||||
|
||||
func prepareProviderAddCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
func prepareProviderAddCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Authenticate Terraform Cloud Provider",
|
||||
Long: "Authenticate Terraform Cloud Provider by creating a credential secret and a Terraform Controller Provider",
|
||||
Example: "vela provider add <provider-type>",
|
||||
Use: "add",
|
||||
Deprecated: "Please use the vela config command: \n vela config create <name> --template <provider-type> [Properties]",
|
||||
}
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1)
|
||||
defer cancel()
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defs, err := getTerraformProviderTypes(ctx, k8sClient)
|
||||
if len(args) < 1 {
|
||||
errMsg := "must specify a Terraform Cloud Provider type"
|
||||
if err == nil {
|
||||
if len(defs) > 0 {
|
||||
providerDefNames := make([]string, len(defs))
|
||||
for i, def := range defs {
|
||||
providerDefNames[i] = def.Name
|
||||
}
|
||||
names := strings.Join(providerDefNames, ", ")
|
||||
errMsg += fmt.Sprintf(": select one from %s", names)
|
||||
} else {
|
||||
errMsg += "\nNo Terraform Cloud Provider types exist. Please run `vela addon enable` first"
|
||||
}
|
||||
}
|
||||
return errors.New(errMsg)
|
||||
} else if err == nil {
|
||||
var found bool
|
||||
for _, def := range defs {
|
||||
if def.Name == args[0] {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("%s is not valid", args[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
addSubCommands, err := prepareProviderAddSubCommand(c, ioStreams)
|
||||
if err != nil {
|
||||
ioStreams.Errorf("Fail to prepare the sub commands for the add command:%s \n", err.Error())
|
||||
}
|
||||
cmd.AddCommand(addSubCommands...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func prepareProviderAddSubCommand(c common.Args, ioStreams cmdutil.IOStreams) ([]*cobra.Command, error) {
|
||||
if len(os.Args) < 2 || os.Args[1] != "provider" {
|
||||
return nil, nil
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*1)
|
||||
defer cancel()
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defs, err := getTerraformProviderTypes(timeoutCtx, k8sClient)
|
||||
if err == nil {
|
||||
cmds := make([]*cobra.Command, len(defs))
|
||||
for i, d := range defs {
|
||||
providerType := d.Name
|
||||
cmd := &cobra.Command{
|
||||
Use: providerType,
|
||||
Short: fmt.Sprintf("Authenticate Terraform Cloud Provider %s", providerType),
|
||||
Long: fmt.Sprintf("Authenticate Terraform Cloud Provider %s by creating a credential secret and a Terraform Controller Provider", providerType),
|
||||
Example: fmt.Sprintf("vela provider add %s", providerType),
|
||||
}
|
||||
parameters, err := getParameters(context.Background(), k8sClient, providerType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range parameters {
|
||||
// TODO(wonderflow): make the provider default name to be unique but keep the compatiblility as some Application didn't specify the name,
|
||||
// now it's “default” for every one, the name will conflict if we have more than one cloud provider.
|
||||
cmd.Flags().String(p.Name, fmt.Sprint(p.Default), p.Usage)
|
||||
}
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
newContext := context.Background()
|
||||
name, err := cmd.Flags().GetString(providerNameParam)
|
||||
if err != nil || name == "" {
|
||||
return fmt.Errorf("must specify a name for the Terraform Cloud Provider %s", providerType)
|
||||
}
|
||||
var properties = make(map[string]string, len(parameters))
|
||||
for _, p := range parameters {
|
||||
value, err := cmd.Flags().GetString(p.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value == "" && p.Required {
|
||||
return fmt.Errorf("must specify a value for %s", p.Name)
|
||||
}
|
||||
properties[p.Name] = value
|
||||
}
|
||||
data, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errAuthenticateProvider, providerType, err)
|
||||
}
|
||||
if err := config.CreateApplication(newContext, k8sClient, name, providerType, string(data), config.UIParam{}); err != nil {
|
||||
return fmt.Errorf(errAuthenticateProvider, providerType, err)
|
||||
}
|
||||
ioStreams.Infof("Successfully authenticate provider %s for %s\n", name, providerType)
|
||||
return nil
|
||||
}
|
||||
cmds[i] = cmd
|
||||
}
|
||||
return cmds, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// getParameters gets parameter from a Terraform Cloud Provider, ie the ComponentDefinition
|
||||
func getParameters(ctx context.Context, k8sClient client.Client, providerType string) ([]types.Parameter, error) {
|
||||
def, err := getTerraformProviderType(ctx, k8sClient, providerType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cap, err := docgen.GetCapabilityByComponentDefinitionObject(*def, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cap.Parameters, nil
|
||||
}
|
||||
|
||||
// ProviderMeta is the metadata for a Terraform Cloud Provider
|
||||
type ProviderMeta struct {
|
||||
Type string
|
||||
Name string
|
||||
Age string
|
||||
}
|
||||
|
||||
func listProviders(ctx context.Context, k8sClient client.Client, ioStreams cmdutil.IOStreams) error {
|
||||
var (
|
||||
providers []ProviderMeta
|
||||
currentProviders []tcv1beta1.Provider
|
||||
legacyProviders []tcv1beta1.Provider
|
||||
)
|
||||
l, err := config.ListTerraformProviders(ctx, k8sClient)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve providers")
|
||||
}
|
||||
|
||||
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`.
|
||||
legacyProviders = append(legacyProviders, p)
|
||||
}
|
||||
}
|
||||
|
||||
defs, err := getTerraformProviderTypes(ctx, k8sClient)
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
ioStreams.Info("no Terraform Cloud Provider found, please run `vela addon enable` first")
|
||||
}
|
||||
return errors.Wrap(err, "failed to retrieve providers")
|
||||
}
|
||||
|
||||
for _, d := range defs {
|
||||
var found bool
|
||||
for _, p := range currentProviders {
|
||||
if p.Labels[oam.WorkloadTypeLabel] == d.Name {
|
||||
found = true
|
||||
providers = append(providers, ProviderMeta{
|
||||
Type: p.Labels[oam.WorkloadTypeLabel],
|
||||
Name: p.Name,
|
||||
Age: p.CreationTimestamp.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
providers = append(providers, ProviderMeta{
|
||||
Type: d.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range legacyProviders {
|
||||
providers = append(providers, ProviderMeta{
|
||||
Type: "-",
|
||||
Name: p.Name + "(legacy)",
|
||||
Age: p.CreationTimestamp.String(),
|
||||
})
|
||||
}
|
||||
|
||||
if len(providers) == 0 {
|
||||
return errors.New("no Terraform Cloud Provider found, please run `vela addon enable` first")
|
||||
l := &terraformapi.ProviderList{}
|
||||
if err := k8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
table := uitable.New()
|
||||
table.AddRow("TYPE", "NAME", "CREATED-TIME")
|
||||
table.AddRow("TYPE", "PROVIDER", "NAME", "REGION", "CREATED-TIME")
|
||||
|
||||
for _, p := range providers {
|
||||
table.AddRow(p.Type, p.Name, p.Age)
|
||||
for _, p := range l.Items {
|
||||
table.AddRow(p.Labels["config.oam.dev/provider"], p.Spec.Provider, p.Name, p.Spec.Region, p.CreationTimestamp)
|
||||
}
|
||||
ioStreams.Info(table.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTerraformProviderTypes retrieves all ComponentDefinition for Terraform Cloud Providers which are delivered by
|
||||
// Terraform Cloud provider addons
|
||||
func getTerraformProviderTypes(ctx context.Context, k8sClient client.Client) ([]v1beta1.ComponentDefinition, error) {
|
||||
// keep compatibility with old version of terraform-xxx provider definition
|
||||
legacyDefs := &v1beta1.ComponentDefinitionList{}
|
||||
if err := k8sClient.List(ctx, legacyDefs, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{definition.UserPrefix + definition.DefinitionType: types.TerraformProvider}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defs := &v1beta1.ComponentDefinitionList{}
|
||||
if err := k8sClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{definition.DefinitionType: types.TerraformProvider}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []v1beta1.ComponentDefinition
|
||||
result = append(result, legacyDefs.Items...)
|
||||
result = append(result, defs.Items...)
|
||||
if len(result) == 0 {
|
||||
return nil, errors.New("no Terraform Cloud Provider ComponentDefinition found")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getTerraformProviderType retrieves the ComponentDefinition for a Terraform Cloud Provider which is delivered by
|
||||
// the Terraform Cloud provider addon
|
||||
func getTerraformProviderType(ctx context.Context, k8sClient client.Client, name string) (*v1beta1.ComponentDefinition, error) {
|
||||
def := &v1beta1.ComponentDefinition{}
|
||||
if err := k8sClient.Get(ctx, k8stypes.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: name}, def); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func prepareProviderDeleteCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
func prepareProviderDeleteCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Aliases: []string{"rm", "del"},
|
||||
Short: "Delete Terraform Cloud Provider",
|
||||
Long: "Delete Terraform Cloud Provider",
|
||||
Example: "vela provider delete <provider-type> --name <provider-name>",
|
||||
}
|
||||
|
||||
deleteSubCommands, err := prepareProviderDeleteSubCommand(c, ioStreams)
|
||||
if err != nil {
|
||||
ioStreams.Errorf("Fail to prepare the sub commands for the delete command:%s \n", err.Error())
|
||||
}
|
||||
cmd.AddCommand(deleteSubCommands...)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*1)
|
||||
defer cancel()
|
||||
defs, err := getTerraformProviderTypes(timeoutCtx, k8sClient)
|
||||
if len(args) < 1 {
|
||||
errMsg := "must specify a Terraform Cloud Provider type"
|
||||
if err == nil {
|
||||
if len(defs) > 0 {
|
||||
providerDefNames := make([]string, len(defs))
|
||||
for i, def := range defs {
|
||||
providerDefNames[i] = def.Name
|
||||
}
|
||||
names := strings.Join(providerDefNames, ", ")
|
||||
errMsg += fmt.Sprintf(": select one from %s", names)
|
||||
} else {
|
||||
errMsg += "\nNo Terraform Cloud Provider types exist. Please run `vela addon enable` first"
|
||||
}
|
||||
}
|
||||
return errors.New(errMsg)
|
||||
} else if err == nil {
|
||||
var found bool
|
||||
for _, def := range defs {
|
||||
if def.Name == args[0] {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("%s is not valid", args[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
Use: "delete",
|
||||
Aliases: []string{"rm", "del"},
|
||||
Deprecated: "Please use the vela config command: \n vela config delete <provider-name>",
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func prepareProviderDeleteSubCommand(c common.Args, ioStreams cmdutil.IOStreams) ([]*cobra.Command, error) {
|
||||
if len(os.Args) < 2 || os.Args[1] != "provider" {
|
||||
return nil, nil
|
||||
}
|
||||
timeoutContext, cancel := context.WithTimeout(context.Background(), time.Minute*1)
|
||||
defer cancel()
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defs, err := getTerraformProviderTypes(timeoutContext, k8sClient)
|
||||
if err == nil {
|
||||
cmds := make([]*cobra.Command, len(defs))
|
||||
for i, d := range defs {
|
||||
providerType := d.Name
|
||||
cmd := &cobra.Command{
|
||||
Use: providerType,
|
||||
Short: fmt.Sprintf("Delete Terraform Cloud Provider %s", providerType),
|
||||
Long: fmt.Sprintf("Delete Terraform Cloud Provider %s", providerType),
|
||||
Example: fmt.Sprintf("vela provider delete %s", providerType),
|
||||
}
|
||||
parameters, err := getParameters(context.Background(), k8sClient, providerType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range parameters {
|
||||
if p.Name == providerNameParam {
|
||||
cmd.Flags().String(p.Name, fmt.Sprint(p.Default), p.Usage)
|
||||
}
|
||||
}
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
name, err := cmd.Flags().GetString(providerNameParam)
|
||||
if err != nil || name == "" {
|
||||
return fmt.Errorf("must specify a name for the Terraform Cloud Provider %s", providerType)
|
||||
}
|
||||
if err := config.DeleteApplication(context.Background(), 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
|
||||
}
|
||||
cmds[i] = cmd
|
||||
}
|
||||
return cmds, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"testing"
|
||||
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -32,7 +31,6 @@ import (
|
|||
|
||||
"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/utils/util"
|
||||
)
|
||||
|
||||
|
|
@ -95,63 +93,3 @@ func TestListProviders(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProviderTypes(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
|
||||
p1 := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p1",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{definition.DefinitionType: types.TerraformProvider},
|
||||
},
|
||||
}
|
||||
p2 := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "p2",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{definition.UserPrefix + definition.DefinitionType: types.TerraformProvider},
|
||||
},
|
||||
}
|
||||
testCases := map[string]struct {
|
||||
objects []client.Object
|
||||
gotTypes map[string]bool
|
||||
wantErr error
|
||||
}{
|
||||
"success": {
|
||||
objects: []client.Object{p1},
|
||||
gotTypes: map[string]bool{
|
||||
"p1": true,
|
||||
},
|
||||
},
|
||||
"success with legacy provider": {
|
||||
objects: []client.Object{p2},
|
||||
gotTypes: map[string]bool{
|
||||
"p2": true,
|
||||
},
|
||||
},
|
||||
"not found": {
|
||||
objects: []client.Object{},
|
||||
wantErr: errors.New("no Terraform Cloud Provider ComponentDefinition found"),
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tc := range testCases {
|
||||
k8sClient = fake.NewClientBuilder().WithScheme(s).WithObjects(tc.objects...).Build()
|
||||
providerTypes, err := getTerraformProviderTypes(ctx, k8sClient)
|
||||
if tc.wantErr != nil {
|
||||
assert.Contains(t, err.Error(), tc.wantErr.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(tc.gotTypes), len(providerTypes))
|
||||
for _, gotType := range providerTypes {
|
||||
_, found := tc.gotTypes[gotType.Name]
|
||||
assert.True(t, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,12 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
"config-image-registry": {
|
||||
type: "component"
|
||||
annotations: {
|
||||
"alias.config.oam.dev": "Image Registry"
|
||||
}
|
||||
labels: {
|
||||
"catalog.config.oam.dev": "velacore-config"
|
||||
"type.config.oam.dev": "image-registry"
|
||||
"multi-cluster.config.oam.dev": "true"
|
||||
"ui-hidden": "true"
|
||||
}
|
||||
metadata: {
|
||||
name: "image-registry"
|
||||
alias: "Image Registry"
|
||||
scope: "project"
|
||||
description: "Config information to authenticate image registry"
|
||||
attributes: workload: type: "autodetects.core.oam.dev"
|
||||
sensitive: false
|
||||
}
|
||||
|
||||
template: {
|
||||
|
|
@ -27,11 +20,8 @@ template: {
|
|||
name: context.name
|
||||
namespace: context.namespace
|
||||
labels: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "image-registry"
|
||||
"config.oam.dev/multi-cluster": "true"
|
||||
"config.oam.dev/identifier": parameter.registry
|
||||
"config.oam.dev/sub-type": "auth"
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "image-registry"
|
||||
}
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
|
|
@ -43,7 +33,7 @@ template: {
|
|||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
"auths": (parameter.registry): {
|
||||
"auths": "\(parameter.registry)": {
|
||||
"username": parameter.auth.username
|
||||
"password": parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
registry: docker.io
|
||||
auth:
|
||||
username: zhangsan
|
||||
password: lisi
|
||||
useHTTP: true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue