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" | ||||
| 
 | ||||
|  | @ -120,6 +123,7 @@ var ParameterFileName = strings.Join([]string{"resources", "parameter.cue"}, "/" | |||
| type ListOptions struct { | ||||
| 	GetDetail         bool | ||||
| 	GetDefinition     bool | ||||
| 	GetConfigTemplate bool | ||||
| 	GetResource       bool | ||||
| 	GetParameter      bool | ||||
| 	GetTemplate       bool | ||||
|  | @ -128,7 +132,7 @@ type ListOptions struct { | |||
| 
 | ||||
| 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} | ||||
|  | @ -342,6 +348,7 @@ func GetInstallPackageFromReader(r AsyncReader, meta *SourceMeta, uiData *UIData | |||
| 		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: { | ||||
| 	//+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
 | ||||
|  | @ -65,424 +55,298 @@ func NewConfigService() ConfigService { | |||
| 
 | ||||
| type configServiceImpl struct { | ||||
| 	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 { | ||||
| 	if scope == "project" && project != "" { | ||||
| 		pro, err := u.ProjectService.GetProject(ctx, project) | ||||
| 		if 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 | ||||
| 		templates, err := u.Factory.ListTemplates(ctx, pro.GetNamespace(), scope) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		items = append(items, legacy) | ||||
| 		queryTemplates = append(queryTemplates, templates...) | ||||
| 	} | ||||
| 	var tfDefs []v1beta1.ComponentDefinition | ||||
| 	var configTypes []*apis.ConfigType | ||||
| 
 | ||||
| 	for _, d := range items { | ||||
| 		if DefinitionType(d.Labels) == types.TerraformProvider { | ||||
| 			tfDefs = append(tfDefs, d) | ||||
| 			continue | ||||
| 		} | ||||
| 		configTypes = append(configTypes, &apis.ConfigType{ | ||||
| 			Alias:       DefinitionAlias(d.Annotations), | ||||
| 			Name:        d.Name, | ||||
| 			Definitions: []string{d.Name}, | ||||
| 			Description: d.Annotations[types.AnnoDefinitionDescription], | ||||
| 	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, | ||||
| 		ns = pro.GetNamespace() | ||||
| 		if err := utils.CreateNamespace(ctx, u.KubeClient, ns); err != nil && !apierrors.IsAlreadyExists(err) { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 					if p.Status.State == terraformtypes.ProviderIsReady { | ||||
| 						configs[i].Status = configIsReady | ||||
| 					} else { | ||||
| 						configs[i].Status = configIsNotReady | ||||
| 	} | ||||
| 					continue | ||||
| 	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 the application doesn't have any components, skip it as something wrong happened.
 | ||||
| 			if !strings.HasPrefix(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) { | ||||
| 				continue | ||||
| 	if err := u.Factory.CreateOrUpdateConfig(ctx, configItem, ns); err != nil { | ||||
| 		if errors.Is(err, config.ErrConfigExist) { | ||||
| 			return nil, bcode.ErrConfigExist | ||||
| 		} | ||||
| 			configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject]) | ||||
| 		} | ||||
| 		return configs, nil | ||||
| 
 | ||||
| 	default: | ||||
| 		return u.getConfigsByConfigType(ctx, configType) | ||||
| 
 | ||||
| 		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 | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| 	return config, nil | ||||
| // 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])) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| func (u *configServiceImpl) DeleteConfig(ctx context.Context, configType, name string) error { | ||||
| 	var isTerraformProvider bool | ||||
| 	if strings.HasPrefix(configType, types.TerraformComponentPrefix) { | ||||
| 		isTerraformProvider = true | ||||
| 	configs, err := u.Factory.ListConfigs(ctx, types.DefaultKubeVelaNS, template, scope, true) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return config.DeleteApplication(ctx, u.KubeClient, name, isTerraformProvider) | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| // 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 { | ||||
| // 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 | ||||
| 	} | ||||
| 	if len(secrets.Items) == 0 { | ||||
| 		return nil | ||||
| 	if len(req.Configs) == 0 || len(req.Targets) == 0 { | ||||
| 		return bcode.ErrNoConfigOrTarget | ||||
| 	} | ||||
| 	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", | ||||
| 	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}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var configs []*config.NamespacedName | ||||
| 	for _, t := range req.Configs { | ||||
| 		if t.Name != "" { | ||||
| 			configs = append(configs, &config.NamespacedName{Namespace: t.Namespace, Name: t.Name}) | ||||
| 		} | ||||
| 	} | ||||
| 	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 | ||||
| 	} | ||||
| 	if len(objects) == 0 { | ||||
| 		klog.InfoS("no configs need to sync to working clusters", "project", project) | ||||
| 	return nil | ||||
| } | ||||
| 	objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects}) | ||||
| 
 | ||||
| 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 | ||||
| 		} | ||||
| 
 | ||||
| 	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 | ||||
| 		ns = pro.GetNamespace() | ||||
| 	} | ||||
| 		// 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) | ||||
| 	} | ||||
| 	// 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 | ||||
| 		} | ||||
| 		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) | ||||
| 	} | ||||
| 	mergedPolicies := make([]v1beta1.AppPolicy, len(mergedTarget)) | ||||
| 	for i, t := range mergedTarget { | ||||
| 		properties, err := json.Marshal(t) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		mergedPolicies[i] = v1beta1.AppPolicy{ | ||||
| 			Type: "topology", | ||||
| 			Name: t.Namespace, | ||||
| 			Properties: &runtime.RawExtension{ | ||||
| 				Raw: properties, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| 	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{ | ||||
| template: { | ||||
| 	outputs: { | ||||
| 		"provider": { | ||||
| 			apiVersion: "terraform.core.oam.dev/v1beta1" | ||||
| 			kind:       "Provider" | ||||
| 			metadata: { | ||||
| 				name:      parameter.name | ||||
| 				namespace: context.namespace | ||||
| 				labels:    l | ||||
| 			} | ||||
| 			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 | ||||
| 	} | ||||
| } | ||||
| ` | ||||
| 
 | ||||
| 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, | ||||
| 		} | ||||
| 
 | ||||
| 	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) | ||||
| 			} | ||||
| 			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", | ||||
| 	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", | ||||
| 			}, | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "def2", | ||||
| 			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")) | ||||
| 
 | ||||
| 		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", | ||||
| 			}, | ||||
| 			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, | ||||
| 			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 | ||||
| 			Name:      "test", | ||||
| 		}, &provider)).To(BeNil()) | ||||
| 	}) | ||||
| 	defer patches.Reset() | ||||
| 
 | ||||
| 	h := &configServiceImpl{KubeClient: k8sClient} | ||||
| 	It("Test list configs", func() { | ||||
| 		list, err := configService.ListConfigs(context.TODO(), "", "alibaba-provider", false) | ||||
| 		Expect(err).To(BeNil()) | ||||
| 		Expect(len(list)).To(Equal(1)) | ||||
| 
 | ||||
| 	type args struct { | ||||
| 		h    ConfigService | ||||
| 		name string | ||||
| 	} | ||||
| 		_, err = projectService.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: "mysql-project"}) | ||||
| 		Expect(err).To(BeNil()) | ||||
| 
 | ||||
| 	type want struct { | ||||
| 		configType *apis.ConfigType | ||||
| 		errMsg     string | ||||
| 	} | ||||
| 		// 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)) | ||||
| 
 | ||||
| 	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) | ||||
| 		list, err = configService.ListConfigs(context.TODO(), "", "not-found", false) | ||||
| 		Expect(err).To(BeNil()) | ||||
| 		Expect(len(list)).To(Equal(0)) | ||||
| 	}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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", | ||||
| 	It("Test detail a config", func() { | ||||
| 		_, err := configService.GetConfig(context.TODO(), "", "alibaba-test") | ||||
| 		Expect(err).To(Equal(bcode.ErrSensitiveConfig)) | ||||
| 	}) | ||||
| 	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) | ||||
| 			} | ||||
| 	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)) | ||||
| 	}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestDeleteConfig(t *testing.T) { | ||||
| 	s := runtime.NewScheme() | ||||
| 	v1beta1.AddToScheme(s) | ||||
| 	corev1.AddToScheme(s) | ||||
| 	terraformapi.AddToScheme(s) | ||||
| 	provider1 := &terraformapi.Provider{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "p1", | ||||
| 			Namespace: "default", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	provider2 := &terraformapi.Provider{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "p2", | ||||
| 			Namespace: "default", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	provider3 := &terraformapi.Provider{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "p3", | ||||
| 			Namespace: "default", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	app1 := &v1beta1.Application{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "config-terraform-provider-p1", | ||||
| 			Namespace: types.DefaultKubeVelaNS, | ||||
| 			Labels: map[string]string{ | ||||
| 				types.LabelConfigType: "terraform-alibaba", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	app2 := &v1beta1.Application{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "p2", | ||||
| 			Namespace: types.DefaultKubeVelaNS, | ||||
| 			Labels: map[string]string{ | ||||
| 				types.LabelConfigType: "terraform-alibaba", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	normalApp := &v1beta1.Application{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "a9", | ||||
| 			Namespace: types.DefaultKubeVelaNS, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1, app2, normalApp).Build() | ||||
| 
 | ||||
| 	h := &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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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" | ||||
| 
 | ||||
|  | @ -56,6 +52,7 @@ type HelmService interface { | |||
| type defaultHelmImpl struct { | ||||
| 	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 | ||||
|  |  | |||
|  | @ -56,28 +56,29 @@ type ImageService interface { | |||
| 
 | ||||
| type imageImpl struct { | ||||
| 	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 { | ||||
| 	for _, item := range configs { | ||||
| 		if item.Properties != nil { | ||||
| 			registry, ok := item.Properties["registry"].(string) | ||||
| 			if ok { | ||||
| 				repos = append(repos, v1.ImageRegistry{ | ||||
| 				Name:       secret.Name, | ||||
| 				SecretName: secret.Name, | ||||
| 				Domain:     secret.Labels[types.LabelConfigIdentifier], | ||||
| 					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) | ||||
| 					} | ||||
| 		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", | ||||
| 	}, | ||||
|  | @ -268,6 +272,8 @@ var ResourceMaps = map[string]resourceMetadata{ | |||
| 		}, | ||||
| 	}, | ||||
| 	"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"` | ||||
| // 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"` | ||||
| 	Template    config.NamespacedName         `json:"template"` | ||||
| 	Name        string                        `json:"name"` | ||||
| 	Namespace   string                        `json:"namespace"` | ||||
| 	Sensitive   bool                          `json:"sensitive"` | ||||
| 	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"` | ||||
| 	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"` | ||||
|  | @ -1446,6 +1475,7 @@ type ImageRegistry struct { | |||
| 	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 { | ||||
| 	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(configs) | ||||
| 	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,9 +38,11 @@ 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] | ||||
| 		if _, ok := s.Data[key]; ok { | ||||
| 			err := json.Unmarshal(s.Data[key], &data) | ||||
| 			if err != nil { | ||||
| 			return nil, err | ||||
| 				log.Logger.Warnf("the dex connector %s is invalid", s.Name) | ||||
| 				continue | ||||
| 			} | ||||
| 			connectors[i] = map[string]interface{}{ | ||||
| 				"type":   s.Labels[types.LabelConfigSubType], | ||||
|  | @ -47,6 +51,7 @@ func GetDexConnectors(ctx context.Context, k8sClient client.Client) ([]map[strin | |||
| 				"config": data, | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return connectors, nil | ||||
| } | ||||
|  |  | |||
|  | @ -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>", | ||||
| 		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 | ||||
| 		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: { | ||||
|  | @ -29,9 +22,6 @@ template: { | |||
| 			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 != _|_ { | ||||
|  | @ -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