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" | 	AnnoIngressControllerHTTPPort = "ingress.controller/http-port" | ||||||
| 	// AnnoIngressControllerHost define ingress controller externally host
 | 	// AnnoIngressControllerHost define ingress controller externally host
 | ||||||
| 	AnnoIngressControllerHost = "ingress.controller/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" | 	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" | 	LabelConfigCatalog = "config.oam.dev/catalog" | ||||||
| 	// LabelConfigSubType is the sub-type for a config type
 | 	// LabelConfigSubType is the sub-type for a config type
 | ||||||
| 	LabelConfigSubType = "config.oam.dev/sub-type" | 	LabelConfigSubType = "config.oam.dev/sub-type" | ||||||
|  | @ -86,10 +86,18 @@ const ( | ||||||
| 	LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster" | 	LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster" | ||||||
| 	// LabelConfigIdentifier is the label for config identifier
 | 	// LabelConfigIdentifier is the label for config identifier
 | ||||||
| 	LabelConfigIdentifier = "config.oam.dev/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 is the annotation for config description
 | ||||||
| 	AnnotationConfigDescription = "config.oam.dev/description" | 	AnnotationConfigDescription = "config.oam.dev/description" | ||||||
| 	// AnnotationConfigAlias is the annotation for config alias
 | 	// AnnotationConfigAlias is the annotation for config alias
 | ||||||
| 	AnnotationConfigAlias = "config.oam.dev/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 ( | const ( | ||||||
|  | @ -156,11 +164,13 @@ const ( | ||||||
| 	// TerraformProvider is the config type for terraform provider
 | 	// TerraformProvider is the config type for terraform provider
 | ||||||
| 	TerraformProvider = "terraform-provider" | 	TerraformProvider = "terraform-provider" | ||||||
| 	// DexConnector is the config type for dex connector
 | 	// DexConnector is the config type for dex connector
 | ||||||
| 	DexConnector = "config-dex-connector" | 	DexConnector = "dex-connector" | ||||||
| 	// ImageRegistry is the config type for image registry
 | 	// 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 is the config type for Helm chart repository
 | ||||||
| 	HelmRepository = "config-helm-repository" | 	HelmRepository = "helm-repository" | ||||||
|  | 	// CatalogConfigDistribution is the catalog type
 | ||||||
|  | 	CatalogConfigDistribution = "config-distribution" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | 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-openapi/spec v0.19.8 | ||||||
| 	github.com/go-playground/validator/v10 v10.9.0 | 	github.com/go-playground/validator/v10 v10.9.0 | ||||||
| 	github.com/go-resty/resty/v2 v2.7.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-cmp v0.5.8 | ||||||
| 	github.com/google/go-containerregistry v0.9.0 | 	github.com/google/go-containerregistry v0.9.0 | ||||||
| 	github.com/google/go-github/v32 v32.1.0 | 	github.com/google/go-github/v32 v32.1.0 | ||||||
|  | @ -68,7 +69,7 @@ require ( | ||||||
| 	github.com/onsi/gomega v1.20.2 | 	github.com/onsi/gomega v1.20.2 | ||||||
| 	github.com/openkruise/kruise-api v1.1.0 | 	github.com/openkruise/kruise-api v1.1.0 | ||||||
| 	github.com/pkg/errors v0.9.1 | 	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/prometheus/client_model v0.2.0 | ||||||
| 	github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a | 	github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a | ||||||
| 	github.com/robfig/cron/v3 v3.0.1 | 	github.com/robfig/cron/v3 v3.0.1 | ||||||
|  | @ -83,7 +84,7 @@ require ( | ||||||
| 	github.com/xanzy/go-gitlab v0.60.0 | 	github.com/xanzy/go-gitlab v0.60.0 | ||||||
| 	github.com/xlab/treeprint v1.1.0 | 	github.com/xlab/treeprint v1.1.0 | ||||||
| 	go.mongodb.org/mongo-driver v1.5.1 | 	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/crypto v0.0.0-20220507011949-2cf3adece122 | ||||||
| 	golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 | 	golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 | ||||||
| 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 | 	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 v1.4.5 // indirect | ||||||
| 	github.com/alibabacloud-go/tea-utils/v2 v2.0.0 // indirect | 	github.com/alibabacloud-go/tea-utils/v2 v2.0.0 // indirect | ||||||
| 	github.com/alibabacloud-go/tea-xml v1.1.2 // 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/aliyun/credentials-go v1.1.2 // indirect | ||||||
| 	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect | 	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect | ||||||
| 	github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect | 	github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect | ||||||
| 	github.com/aws/aws-sdk-go v1.36.30 // indirect | 	github.com/aws/aws-sdk-go v1.36.30 // indirect | ||||||
| 	github.com/beorn7/perks v1.0.1 // indirect | 	github.com/beorn7/perks v1.0.1 // indirect | ||||||
| 	github.com/blang/semver v3.5.1+incompatible // 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/cespare/xxhash/v2 v2.1.2 // indirect | ||||||
| 	github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect | 	github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect | ||||||
| 	github.com/clbanning/mxj/v2 v2.5.5 // 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/go-digest v1.0.0 // indirect | ||||||
| 	github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // 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/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/peterbourgon/diskv v2.0.1+incompatible // indirect | ||||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // 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/net v0.0.0-20220906165146-f3363e06e74c // indirect | ||||||
| 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect | 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect | ||||||
| 	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // 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 | 	golang.org/x/tools v0.1.12 // indirect | ||||||
| 	google.golang.org/appengine v1.6.7 // 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 | 	google.golang.org/protobuf v1.28.0 // indirect | ||||||
| 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | ||||||
| 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect | 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect | ||||||
| 	gopkg.in/gorp.v1 v1.7.2 // indirect | 	gopkg.in/gorp.v1 v1.7.2 // indirect | ||||||
| 	gopkg.in/inf.v0 v0.9.1 // 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/square/go-jose.v2 v2.5.1 // indirect | ||||||
| 	gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect | 	gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect | ||||||
| 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect | 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect | ||||||
| 	gopkg.in/warnings.v0 v0.1.2 // 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/api v0.0.0-20220512212136-561ffec82582 // indirect | ||||||
| 	istio.io/gogo-genproto v0.0.0-20211208193508-5ab4acc9eb1e // indirect | 	istio.io/gogo-genproto v0.0.0-20211208193508-5ab4acc9eb1e // indirect | ||||||
| 	k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // 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-cleanhttp v0.5.2 // indirect | ||||||
| 	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect | 	github.com/hashicorp/go-retryablehttp v0.7.0 // indirect | ||||||
| 	github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // 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/opencontainers/runc v1.1.3 // indirect | ||||||
| 	github.com/openkruise/rollouts v0.1.1-0.20220622054609-149e5a48da5e | 	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 | 	github.com/xanzy/ssh-agent v0.3.0 // indirect | ||||||
| 	google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03 // indirect | 	google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03 // indirect | ||||||
|  | 	gopkg.in/yaml.v2 v2.4.0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| replace ( | 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-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 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M= | ||||||
| github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= | 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/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 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY= | ||||||
| github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= | 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 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= | ||||||
| github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= | 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 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 h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= | ||||||
| github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= | 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= | 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.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||||
| github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | ||||||
| github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | 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-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/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= | 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.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.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= | ||||||
| github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= | 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/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 v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||||
| github.com/golang/protobuf v1.1.0/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/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-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 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.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.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||||
| github.com/json-iterator/go v1.1.8/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/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.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||||
| github.com/magiconair/properties v1.8.1/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/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-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||||||
| github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/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/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 h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= | ||||||
| github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= | 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/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.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= | ||||||
| github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= | 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.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.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.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.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-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-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||||
| github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||||||
| go.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADbpZtnU= | 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.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/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 v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||||
| go.uber.org/multierr v1.1.0/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.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.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.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= | ||||||
| go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= | ||||||
| go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= | 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-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-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-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-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-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-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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/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-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-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-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-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= | ||||||
| golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | 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-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-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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.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.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= | ||||||
| google.golang.org/grpc v1.46.2/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.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/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-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | 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.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||||
| gopkg.in/ini.v1 v1.56.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.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.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 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= | ||||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= | 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= | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | ||||||
|  |  | ||||||
|  | @ -32,7 +32,6 @@ import ( | ||||||
| 	"text/template" | 	"text/template" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"cuelang.org/go/cue/cuecontext" |  | ||||||
| 	"github.com/Masterminds/semver/v3" | 	"github.com/Masterminds/semver/v3" | ||||||
| 	"github.com/google/go-github/v32/github" | 	"github.com/google/go-github/v32/github" | ||||||
| 	"github.com/imdario/mergo" | 	"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/core.oam.dev/v1beta1" | ||||||
| 	"github.com/oam-dev/kubevela/apis/types" | 	"github.com/oam-dev/kubevela/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/log" | 	"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/definition" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/multicluster" | 	"github.com/oam-dev/kubevela/pkg/multicluster" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam" | 	"github.com/oam-dev/kubevela/pkg/oam" | ||||||
|  | @ -100,6 +100,9 @@ const ( | ||||||
| 	// DefinitionsDirName is the addon definitions/ dir name
 | 	// DefinitionsDirName is the addon definitions/ dir name
 | ||||||
| 	DefinitionsDirName string = "definitions" | 	DefinitionsDirName string = "definitions" | ||||||
| 
 | 
 | ||||||
|  | 	// ConfigTemplateDirName is the addon config-templates/ dir name
 | ||||||
|  | 	ConfigTemplateDirName string = "config-templates" | ||||||
|  | 
 | ||||||
| 	// DefSchemaName is the addon definition schemas dir name
 | 	// DefSchemaName is the addon definition schemas dir name
 | ||||||
| 	DefSchemaName string = "schemas" | 	DefSchemaName string = "schemas" | ||||||
| 
 | 
 | ||||||
|  | @ -120,6 +123,7 @@ var ParameterFileName = strings.Join([]string{"resources", "parameter.cue"}, "/" | ||||||
| type ListOptions struct { | type ListOptions struct { | ||||||
| 	GetDetail         bool | 	GetDetail         bool | ||||||
| 	GetDefinition     bool | 	GetDefinition     bool | ||||||
|  | 	GetConfigTemplate bool | ||||||
| 	GetResource       bool | 	GetResource       bool | ||||||
| 	GetParameter      bool | 	GetParameter      bool | ||||||
| 	GetTemplate       bool | 	GetTemplate       bool | ||||||
|  | @ -128,7 +132,7 @@ type ListOptions struct { | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	// UIMetaOptions get Addon metadata for UI display
 | 	// 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 get Addon metadata for CLI display
 | ||||||
| 	CLIMetaOptions = ListOptions{} | 	CLIMetaOptions = ListOptions{} | ||||||
|  | @ -206,6 +210,7 @@ type Pattern struct { | ||||||
| 
 | 
 | ||||||
| // Patterns is the file pattern that the addon should be in
 | // Patterns is the file pattern that the addon should be in
 | ||||||
| var Patterns = []Pattern{ | var Patterns = []Pattern{ | ||||||
|  | 	{IsDir: true, Value: ConfigTemplateDirName}, | ||||||
| 	{Value: ReadmeFileName}, {Value: MetadataFileName}, {Value: TemplateFileName}, | 	{Value: ReadmeFileName}, {Value: MetadataFileName}, {Value: TemplateFileName}, | ||||||
| 	{Value: ParameterFileName}, {IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName}, | 	{Value: ParameterFileName}, {IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName}, | ||||||
| 	{IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName}, {Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}, {Value: LegacyReadmeFileName}} | 	{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}, | 		LegacyReadmeFileName:    {!opt.GetDetail, readReadme}, | ||||||
| 		MetadataFileName:        {false, readMetadata}, | 		MetadataFileName:        {false, readMetadata}, | ||||||
| 		DefinitionsDirName:      {!opt.GetDefinition, readDefFile}, | 		DefinitionsDirName:      {!opt.GetDefinition, readDefFile}, | ||||||
|  | 		ConfigTemplateDirName:   {!opt.GetConfigTemplate, readConfigTemplateFile}, | ||||||
| 		ParameterFileName:       {!opt.GetParameter, readParamFile}, | 		ParameterFileName:       {!opt.GetParameter, readParamFile}, | ||||||
| 		GlobalParameterFileName: {!opt.GetParameter, readGlobalParamFile}, | 		GlobalParameterFileName: {!opt.GetParameter, readGlobalParamFile}, | ||||||
| 	} | 	} | ||||||
|  | @ -318,7 +324,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID | ||||||
| 		} | 		} | ||||||
| 		err := genAddonAPISchema(addon) | 		err := genAddonAPISchema(addon) | ||||||
| 		if err != nil { | 		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} | 	addon.AvailableVersions = []string{addon.Version} | ||||||
|  | @ -342,6 +348,7 @@ func GetInstallPackageFromReader(r AsyncReader, meta *SourceMeta, uiData *UIData | ||||||
| 		Definitions:     uiData.Definitions, | 		Definitions:     uiData.Definitions, | ||||||
| 		CUEDefinitions:  uiData.CUEDefinitions, | 		CUEDefinitions:  uiData.CUEDefinitions, | ||||||
| 		Parameters:      uiData.Parameters, | 		Parameters:      uiData.Parameters, | ||||||
|  | 		ConfigTemplates: uiData.ConfigTemplates, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for contentType, method := range addonContentsReader { | 	for contentType, method := range addonContentsReader { | ||||||
|  | @ -454,6 +461,21 @@ func readDefFile(a *UIData, reader AsyncReader, readPath string) error { | ||||||
| 	return nil | 	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
 | // readViewFile read single view file
 | ||||||
| func readViewFile(a *InstallPackage, reader AsyncReader, readPath string) error { | func readViewFile(a *InstallPackage, reader AsyncReader, readPath string) error { | ||||||
| 	b, err := reader.ReadFile(readPath) | 	b, err := reader.ReadFile(readPath) | ||||||
|  | @ -590,23 +612,14 @@ func unmarshalToContent(content []byte) (fileContent *github.RepositoryContent, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func genAddonAPISchema(addonRes *UIData) error { | func genAddonAPISchema(addonRes *UIData) error { | ||||||
| 	param, err := utils2.PrepareParameterCue(addonRes.Name, addonRes.Parameters) | 	cueScript, err := script.PrepareTemplateCUEScript([]byte(addonRes.Parameters)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	val := cuecontext.New().CompileString(param) | 	schema, err := cueScript.ParsePropertiesToSchema() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		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 | 	addonRes.APISchema = schema | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | @ -688,7 +701,6 @@ func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructu | ||||||
| 		obj.SetNamespace(types.DefaultKubeVelaNS) | 		obj.SetNamespace(types.DefaultKubeVelaNS) | ||||||
| 		defObjs = append(defObjs, obj) | 		defObjs = append(defObjs, obj) | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	for _, cueDef := range addon.CUEDefinitions { | 	for _, cueDef := range addon.CUEDefinitions { | ||||||
| 		def := definition.Definition{Unstructured: unstructured.Unstructured{}} | 		def := definition.Definition{Unstructured: unstructured.Unstructured{}} | ||||||
| 		err := def.FromCUEString(cueDef.Data, config) | 		err := def.FromCUEString(cueDef.Data, config) | ||||||
|  | @ -703,6 +715,29 @@ func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructu | ||||||
| 	return defObjs, nil | 	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.
 | // RenderDefinitionSchema will render definitions' schema in addons.
 | ||||||
| func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured, error) { | func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured, error) { | ||||||
| 	schemaConfigmaps := make([]*unstructured.Unstructured, 0) | 	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})) | 	app.SetLabels(util.MergeMapOverrideWithDst(app.GetLabels(), map[string]string{oam.LabelAddonRegistry: h.r.Name})) | ||||||
| 
 | 
 | ||||||
|  | 	// Step1: Render the definitions
 | ||||||
| 	defs, err := RenderDefinitions(addon, h.config) | 	defs, err := RenderDefinitions(addon, h.config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "render addon definitions fail") | 		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) | 	schemas, err := RenderDefinitionSchema(addon) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "render addon definitions' schema fail") | 		return errors.Wrap(err, "render addon definitions' schema fail") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Step4: Render the velaQL views
 | ||||||
| 	views, err := RenderViews(addon) | 	views, err := RenderViews(addon) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.Wrap(err, "render addon views fail") | 		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 { | 	if err := passDefInAppAnnotation(defs, app); err != nil { | ||||||
| 		return errors.Wrapf(err, "cannot pass definition to addon app's annotation") | 		return errors.Wrapf(err, "cannot pass definition to addon app's annotation") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	if h.dryRun { | 	if h.dryRun { | ||||||
| 		result, err := yaml.Marshal(app) | 		result, err := yaml.Marshal(app) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -1124,6 +1169,7 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auxiliaryOutputs = append(auxiliaryOutputs, defs...) | 	auxiliaryOutputs = append(auxiliaryOutputs, defs...) | ||||||
|  | 	auxiliaryOutputs = append(auxiliaryOutputs, templates...) | ||||||
| 	auxiliaryOutputs = append(auxiliaryOutputs, schemas...) | 	auxiliaryOutputs = append(auxiliaryOutputs, schemas...) | ||||||
| 	auxiliaryOutputs = append(auxiliaryOutputs, views...) | 	auxiliaryOutputs = append(auxiliaryOutputs, views...) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -165,6 +165,8 @@ func testReaderFunc(t *testing.T, reader AsyncReader) { | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, uiData.Name, testAddonName) | 	assert.Equal(t, uiData.Name, testAddonName) | ||||||
| 	assert.True(t, uiData.Parameters != "") | 	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) | 	assert.True(t, len(uiData.Definitions) > 0) | ||||||
| 
 | 
 | ||||||
| 	testAddonName = "example-legacy" | 	testAddonName = "example-legacy" | ||||||
|  | @ -184,6 +186,7 @@ func testReaderFunc(t *testing.T, reader AsyncReader) { | ||||||
| 	// test get ui data
 | 	// test get ui data
 | ||||||
| 	rName := "KubeVela" | 	rName := "KubeVela" | ||||||
| 	uiDataList, err := ListAddonUIDataFromReader(reader, registryMeta, rName, UIMetaOptions) | 	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.True(t, strings.Contains(err.Error(), "preference mark not allowed at this position")) | ||||||
| 	assert.Equal(t, 5, len(uiDataList)) | 	assert.Equal(t, 5, len(uiDataList)) | ||||||
| 	assert.Equal(t, uiDataList[0].RegistryName, rName) | 	assert.Equal(t, uiDataList[0].RegistryName, rName) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
| parameter: { | parameter: { | ||||||
|  | 	//+usage=the example field | ||||||
| 	example: string | 	example: string | ||||||
| } | } | ||||||
|  | @ -35,6 +35,7 @@ type UIData struct { | ||||||
| 
 | 
 | ||||||
| 	Definitions      []ElementFile `json:"definitions"` | 	Definitions      []ElementFile `json:"definitions"` | ||||||
| 	CUEDefinitions   []ElementFile `json:"CUEDefinitions"` | 	CUEDefinitions   []ElementFile `json:"CUEDefinitions"` | ||||||
|  | 	ConfigTemplates  []ElementFile `json:"configTemplates"` | ||||||
| 	Parameters       string        `json:"parameters"` | 	Parameters       string        `json:"parameters"` | ||||||
| 	GlobalParameters string        `json:"globalParameters"` | 	GlobalParameters string        `json:"globalParameters"` | ||||||
| 	RegistryName     string        `json:"registryName"` | 	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 and CUEDefinitions are converted as OAM X-Definitions, they will only in control plane cluster
 | ||||||
| 	Definitions    []ElementFile `json:"definitions"` | 	Definitions    []ElementFile `json:"definitions"` | ||||||
| 	CUEDefinitions []ElementFile `json:"CUEDefinitions"` | 	CUEDefinitions []ElementFile `json:"CUEDefinitions"` | ||||||
|  | 
 | ||||||
|  | 	ConfigTemplates []ElementFile `json:"configTemplates"` | ||||||
|  | 
 | ||||||
| 	// YAMLViews and CUEViews are the instances of velaql, they will only in control plane cluster
 | 	// YAMLViews and CUEViews are the instances of velaql, they will only in control plane cluster
 | ||||||
| 	YAMLViews []ElementFile `json:"YAMLViews"` | 	YAMLViews []ElementFile `json:"YAMLViews"` | ||||||
| 	CUEViews  []ElementFile `json:"CUEViews"` | 	CUEViews  []ElementFile `json:"CUEViews"` | ||||||
|  |  | ||||||
|  | @ -16,6 +16,8 @@ limitations under the License. | ||||||
| 
 | 
 | ||||||
| package model | package model | ||||||
| 
 | 
 | ||||||
|  | import "fmt" | ||||||
|  | 
 | ||||||
| func init() { | func init() { | ||||||
| 	RegisterModel(&Project{}) | 	RegisterModel(&Project{}) | ||||||
| } | } | ||||||
|  | @ -29,6 +31,11 @@ type Project struct { | ||||||
| 	Description string `json:"description,omitempty"` | 	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
 | // TableName return custom table name
 | ||||||
| func (p *Project) TableName() string { | func (p *Project) TableName() string { | ||||||
| 	return tableNamePrefix + "project" | 	return tableNamePrefix + "project" | ||||||
|  |  | ||||||
|  | @ -33,7 +33,6 @@ import ( | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	"k8s.io/client-go/rest" | 	"k8s.io/client-go/rest" | ||||||
| 	"k8s.io/klog/v2" |  | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/yaml" | 	"sigs.k8s.io/yaml" | ||||||
| 
 | 
 | ||||||
|  | @ -669,11 +668,6 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat | ||||||
| 		return nil, err | 		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
 | 	// step2: check and create deploy event
 | ||||||
| 	if !req.Force { | 	if !req.Force { | ||||||
| 		var lastVersion = model.ApplicationRevision{ | 		var lastVersion = model.ApplicationRevision{ | ||||||
|  | @ -771,44 +765,6 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat | ||||||
| 	}, nil | 	}, 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) { | 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 1 uses the requested workflow as release .
 | ||||||
| 	// Priority 2 uses the default 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/common" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | 	"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/domain/model" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" | 	"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" | ||||||
| 	apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" | 	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", | 				Namespace: "vela-system", | ||||||
| 				Labels: map[string]string{ | 				Labels: map[string]string{ | ||||||
| 					"app.oam.dev/source-of-truth": "from-inner-system", | 					"app.oam.dev/source-of-truth": "from-inner-system", | ||||||
| 					"config.oam.dev/catalog":      "velacore-config", | 					"config.oam.dev/catalog":      kubevelatypes.VelaCoreConfig, | ||||||
| 					"config.oam.dev/type":         "config-dex-connector", | 					"config.oam.dev/type":         "dex-connector", | ||||||
| 					"config.oam.dev/sub-type":     "ldap", | 					"config.oam.dev/sub-type":     "ldap", | ||||||
| 					"project":                     "abc", | 					"project":                     "abc", | ||||||
| 				}, | 				}, | ||||||
|  |  | ||||||
|  | @ -19,43 +19,33 @@ package service | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"sort" | ||||||
| 	"strings" |  | ||||||
| 
 | 
 | ||||||
| 	set "github.com/deckarep/golang-set" |  | ||||||
| 	terraformtypes "github.com/oam-dev/terraform-controller/api/types" |  | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	v1 "k8s.io/api/core/v1" | 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||||
| 	kerrors "k8s.io/apimachinery/pkg/api/errors" |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" |  | ||||||
| 	"k8s.io/klog/v2" |  | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"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/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/model" |  | ||||||
| 	apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" | 	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/apiserver/utils/bcode" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/config" | 	"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 ( | // ConfigService handle CRUD of config and template
 | ||||||
| 	configIsReady           = "Ready" |  | ||||||
| 	configIsNotReady        = "Not ready" |  | ||||||
| 	terraformProviderAlias  = "Terraform Cloud Provider" |  | ||||||
| 	configSyncProjectPrefix = "config-sync" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ConfigService handle CRUD of configs
 |  | ||||||
| type ConfigService interface { | type ConfigService interface { | ||||||
| 	ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) | 	ListTemplates(ctx context.Context, project, scope string) ([]*apis.ConfigTemplate, error) | ||||||
| 	GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error) | 	GetTemplate(ctx context.Context, tem config.NamespacedName) (*apis.ConfigTemplateDetail, error) | ||||||
| 	CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error | 	CreateConfig(ctx context.Context, project string, req apis.CreateConfigRequest) (*apis.Config, error) | ||||||
| 	GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error) | 	UpdateConfig(ctx context.Context, project string, name string, req apis.UpdateConfigRequest) (*apis.Config, error) | ||||||
| 	GetConfig(ctx context.Context, configType, name string) (*apis.Config, error) | 	ListConfigs(ctx context.Context, project, template string, withProperties bool) ([]*apis.Config, error) | ||||||
| 	DeleteConfig(ctx context.Context, configType, name string) 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
 | // NewConfigService returns a config use case
 | ||||||
|  | @ -65,424 +55,298 @@ func NewConfigService() ConfigService { | ||||||
| 
 | 
 | ||||||
| type configServiceImpl struct { | type configServiceImpl struct { | ||||||
| 	KubeClient     client.Client    `inject:"kubeClient"` | 	KubeClient     client.Client    `inject:"kubeClient"` | ||||||
|  | 	ProjectService ProjectService   `inject:""` | ||||||
|  | 	Factory        config.Factory   `inject:"configFactory"` | ||||||
|  | 	Apply          apply.Applicator `inject:"apply"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ListConfigTypes returns all config types
 | // ListTemplates list the config templates
 | ||||||
| func (u *configServiceImpl) ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) { | func (u *configServiceImpl) ListTemplates(ctx context.Context, project, scope string) ([]*apis.ConfigTemplate, error) { | ||||||
| 	defs := &v1beta1.ComponentDefinitionList{} | 	queryTemplates, err := u.Factory.ListTemplates(ctx, types.DefaultKubeVelaNS, scope) | ||||||
| 	if err := u.KubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS), | 	if err != nil { | ||||||
| 		client.MatchingLabels{ |  | ||||||
| 			definition.ConfigCatalog: types.VelaCoreConfig, |  | ||||||
| 		}); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 	if scope == "project" && project != "" { | ||||||
| 	var items []v1beta1.ComponentDefinition | 		pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
| 	items = append(items, defs.Items...) | 		if err != nil { | ||||||
| 
 |  | ||||||
| 	// for compatibility of the config catalog key
 |  | ||||||
| 	defsLegacy := &v1beta1.ComponentDefinitionList{} |  | ||||||
| 	if err := u.KubeClient.List(ctx, defsLegacy, client.InNamespace(types.DefaultKubeVelaNS), |  | ||||||
| 		client.MatchingLabels{ |  | ||||||
| 			// leave here as the legacy format to test the compatibility
 |  | ||||||
| 			definition.UserPrefix + definition.ConfigCatalog: types.VelaCoreConfig, |  | ||||||
| 		}); err != nil { |  | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	// filter repeated config,due to new labels that exist at the same time
 | 		templates, err := u.Factory.ListTemplates(ctx, pro.GetNamespace(), scope) | ||||||
| 	for _, legacy := range defsLegacy.Items { | 		if err != nil { | ||||||
| 		if legacy.Labels[definition.ConfigCatalog] == types.VelaCoreConfig { | 			return nil, err | ||||||
| 			continue |  | ||||||
| 		} | 		} | ||||||
| 		items = append(items, legacy) | 		queryTemplates = append(queryTemplates, templates...) | ||||||
| 	} | 	} | ||||||
| 	var tfDefs []v1beta1.ComponentDefinition | 	var templates []*apis.ConfigTemplate | ||||||
| 	var configTypes []*apis.ConfigType | 	for _, t := range queryTemplates { | ||||||
| 
 | 		templates = append(templates, &apis.ConfigTemplate{ | ||||||
| 	for _, d := range items { | 			Alias:       t.Alias, | ||||||
| 		if DefinitionType(d.Labels) == types.TerraformProvider { | 			Name:        t.Name, | ||||||
| 			tfDefs = append(tfDefs, d) | 			Description: t.Description, | ||||||
| 			continue | 			Namespace:   t.Namespace, | ||||||
| 		} | 			Scope:       t.Scope, | ||||||
| 		configTypes = append(configTypes, &apis.ConfigType{ | 			Sensitive:   t.Sensitive, | ||||||
| 			Alias:       DefinitionAlias(d.Annotations), | 			CreateTime:  t.CreateTime, | ||||||
| 			Name:        d.Name, |  | ||||||
| 			Definitions: []string{d.Name}, |  | ||||||
| 			Description: d.Annotations[types.AnnoDefinitionDescription], |  | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 	sort.SliceStable(templates, func(i, j int) bool { | ||||||
| 	if len(tfDefs) > 0 { | 		return templates[i].Alias < templates[j].Alias | ||||||
| 		tfType := &apis.ConfigType{ | 	}) | ||||||
| 			Alias: terraformProviderAlias, | 	return templates, nil | ||||||
| 			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 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetConfigType returns a config type
 | // GetTemplate detail a template
 | ||||||
| func (u *configServiceImpl) GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error) { | func (u *configServiceImpl) GetTemplate(ctx context.Context, tem config.NamespacedName) (*apis.ConfigTemplateDetail, error) { | ||||||
| 	d := &v1beta1.ComponentDefinition{} | 	if tem.Namespace == "" { | ||||||
| 	if err := u.KubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: configType}, d); err != nil { | 		tem.Namespace = types.DefaultKubeVelaNS | ||||||
| 		return nil, errors.Wrap(err, "failed to get config type") |  | ||||||
| 	} | 	} | ||||||
| 
 | 	template, err := u.Factory.LoadTemplate(ctx, tem.Name, tem.Namespace) | ||||||
| 	t := &apis.ConfigType{ | 	if err != nil { | ||||||
| 		Alias:       DefinitionAlias(d.Annotations), | 		if errors.Is(err, config.ErrTemplateNotFound) { | ||||||
| 		Name:        configType, | 			return nil, bcode.ErrTemplateNotFound | ||||||
| 		Description: d.Annotations[types.AnnoDefinitionDescription], | 		} | ||||||
|  | 		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 | 	return t, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (u *configServiceImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error { | func (u *configServiceImpl) CreateConfig(ctx context.Context, project string, req apis.CreateConfigRequest) (*apis.Config, error) { | ||||||
| 	p := req.Properties | 	ns := types.DefaultKubeVelaNS | ||||||
| 	// If the component is Terraform type, set the provider name same as the application name and the component name
 | 	if project != "" { | ||||||
| 	if strings.HasPrefix(req.ComponentType, types.TerraformComponentPrefix) { | 		pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
| 		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) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		configs := make([]*apis.Config, len(providers)) | 		ns = pro.GetNamespace() | ||||||
| 		for i, p := range providers { | 		if err := utils.CreateNamespace(ctx, u.KubeClient, ns); err != nil && !apierrors.IsAlreadyExists(err) { | ||||||
| 			var a v1beta1.Application |  | ||||||
| 			if err := u.KubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: p.Name}, &a); err != nil { |  | ||||||
| 				if kerrors.IsNotFound(err) { |  | ||||||
| 					t := p.CreationTimestamp.Time |  | ||||||
| 					configs[i] = &apis.Config{ |  | ||||||
| 						Name:        p.Name, |  | ||||||
| 						CreatedTime: &t, |  | ||||||
| 					} |  | ||||||
| 					if p.Status.State == terraformtypes.ProviderIsReady { |  | ||||||
| 						configs[i].Status = configIsReady |  | ||||||
| 					} else { |  | ||||||
| 						configs[i].Status = configIsNotReady |  | ||||||
| 					} |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 			// If the application doesn't have any components, skip it as something wrong happened.
 |  | ||||||
| 			if !strings.HasPrefix(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) { |  | ||||||
| 				continue |  | ||||||
| 	} | 	} | ||||||
| 			configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject]) | 	var properties = make(map[string]interface{}) | ||||||
| 		} | 	if err := json.Unmarshal([]byte(req.Properties), &properties); err != nil { | ||||||
| 		return configs, nil |  | ||||||
| 
 |  | ||||||
| 	default: |  | ||||||
| 		return u.getConfigsByConfigType(ctx, configType) |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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 { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 	if req.Template.Namespace == "" { | ||||||
| 	configs := make([]*apis.Config, len(apps.Items)) | 		req.Template.Namespace = types.DefaultKubeVelaNS | ||||||
| 	for i, a := range apps.Items { |  | ||||||
| 		configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject]) |  | ||||||
| 	} | 	} | ||||||
| 	return configs, nil | 	configItem, err := u.Factory.ParseConfig(ctx, config.NamespacedName(req.Template), config.Metadata{ | ||||||
| } | 		NamespacedName: config.NamespacedName{Name: req.Name, Namespace: ns}, | ||||||
| 
 | 		Properties:     properties, | ||||||
| func (u *configServiceImpl) GetConfig(ctx context.Context, configType, name string) (*apis.Config, error) { | 		Alias:          req.Alias, Description: req.Description, | ||||||
| 	var a = &v1beta1.Application{} |  | ||||||
| 	if err := u.KubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); 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, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return config, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (u *configServiceImpl) DeleteConfig(ctx context.Context, configType, name string) error { |  | ||||||
| 	var isTerraformProvider bool |  | ||||||
| 	if strings.HasPrefix(configType, types.TerraformComponentPrefix) { |  | ||||||
| 		isTerraformProvider = true |  | ||||||
| 	} |  | ||||||
| 	return config.DeleteApplication(ctx, u.KubeClient, name, isTerraformProvider) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ApplicationDeployTarget is the struct of application deploy target
 |  | ||||||
| type ApplicationDeployTarget struct { |  | ||||||
| 	Namespace string   `json:"namespace"` |  | ||||||
| 	Clusters  []string `json:"clusters"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SyncConfigs will sync configs to working clusters
 |  | ||||||
| func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, targets []*model.ClusterTarget) error { |  | ||||||
| 	name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project) |  | ||||||
| 	// get all configs which can be synced to working clusters in the project
 |  | ||||||
| 	var secrets v1.SecretList |  | ||||||
| 	if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS), |  | ||||||
| 		client.MatchingLabels{ |  | ||||||
| 			types.LabelConfigCatalog:            types.VelaCoreConfig, |  | ||||||
| 			types.LabelConfigSyncToMultiCluster: "true", |  | ||||||
| 		}); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if len(secrets.Items) == 0 { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	var objects []map[string]string |  | ||||||
| 	for _, s := range secrets.Items { |  | ||||||
| 		if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project { |  | ||||||
| 			objects = append(objects, map[string]string{ |  | ||||||
| 				"name":     s.Name, |  | ||||||
| 				"resource": "secret", |  | ||||||
| 	}) | 	}) | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	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}) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		if errors.Is(err, config.ErrTemplateNotFound) { | ||||||
|  | 			return nil, bcode.ErrTemplateNotFound | ||||||
| 		} | 		} | ||||||
| 
 | 		return nil, err | ||||||
| 	var app = &v1beta1.Application{} |  | ||||||
| 	if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil { |  | ||||||
| 		if !kerrors.IsNotFound(err) { |  | ||||||
| 			return err |  | ||||||
| 	} | 	} | ||||||
| 		// config sync application doesn't exist, create one
 | 	if err := u.Factory.CreateOrUpdateConfig(ctx, configItem, ns); err != nil { | ||||||
| 		clusterTargets := convertClusterTargets(targets) | 		if errors.Is(err, config.ErrConfigExist) { | ||||||
| 		if len(clusterTargets) == 0 { | 			return nil, bcode.ErrConfigExist | ||||||
| 			errMsg := "no policy (no targets found) to sync configs" |  | ||||||
| 			klog.InfoS(errMsg, "project", project) |  | ||||||
| 			return errors.New(errMsg) |  | ||||||
| 		} | 		} | ||||||
| 		policies := make([]v1beta1.AppPolicy, len(clusterTargets)) | 		return nil, err | ||||||
| 		for i, t := range clusterTargets { |  | ||||||
| 			properties, err := json.Marshal(t) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 	} | 	} | ||||||
| 			policies[i] = v1beta1.AppPolicy{ | 	return convertConfig(project, *configItem), nil | ||||||
| 				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 { | func (u *configServiceImpl) UpdateConfig(ctx context.Context, project string, name string, req apis.UpdateConfigRequest) (*apis.Config, error) { | ||||||
| 	var ( | 	ns := types.DefaultKubeVelaNS | ||||||
| 		mergedTargets []ApplicationDeployTarget | 	if project != "" { | ||||||
| 		// make sure the clusters of target with same namespace are merged
 | 		pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
| 		clusterTargets = convertClusterTargets(targets) | 		if err != nil { | ||||||
| 	) | 			return nil, err | ||||||
| 
 |  | ||||||
| 	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) |  | ||||||
| 		} | 		} | ||||||
|  | 		ns = pro.GetNamespace() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, t := range clusterTargets { | 	it, err := u.Factory.GetConfig(ctx, ns, name, false) | ||||||
| 		var hasSameNamespace bool | 	if err != nil { | ||||||
| 		for _, c := range currentTargets { | 		if errors.Is(err, config.ErrSensitiveConfig) { | ||||||
| 			if c.Namespace == t.Namespace { | 			return nil, bcode.ErrSensitiveConfig | ||||||
| 				hasSameNamespace = true |  | ||||||
| 		} | 		} | ||||||
|  | 		if errors.Is(err, config.ErrConfigNotFound) { | ||||||
|  | 			return nil, bcode.ErrConfigNotFound | ||||||
| 		} | 		} | ||||||
| 		if !hasSameNamespace { | 		return nil, err | ||||||
| 			mergedTargets = append(mergedTargets, t) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return mergedTargets | 	var properties = make(map[string]interface{}) | ||||||
|  | 	if err := json.Unmarshal([]byte(req.Properties), &properties); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	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 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func convertClusterTargets(targets []*model.ClusterTarget) []ApplicationDeployTarget { | // ListConfigs query the available configs.
 | ||||||
| 	type Target struct { | // If the project is not empty, it means query all usable configs for this project.
 | ||||||
| 		Namespace string        `json:"namespace"` | func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, template string, withProperties bool) ([]*apis.Config, error) { | ||||||
| 		Clusters  []interface{} `json:"clusters"` | 	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])) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var ( | 	configs, err := u.Factory.ListConfigs(ctx, types.DefaultKubeVelaNS, template, scope, true) | ||||||
| 		clusterTargets []Target | 	if err != nil { | ||||||
| 		namespaceSet   = set.NewSet() | 		return nil, err | ||||||
| 	) | 	} | ||||||
|  | 	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 | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	for i := 0; i < len(targets); i++ { | // CreateConfigDistribution distribute the configs to the target namespaces.
 | ||||||
| 		clusters := set.NewSet(targets[i].ClusterName) | func (u *configServiceImpl) CreateConfigDistribution(ctx context.Context, project string, req apis.CreateConfigDistributionRequest) error { | ||||||
| 		for j := i + 1; j < len(targets); j++ { | 	pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
| 			if targets[i].Namespace == targets[j].Namespace { | 	if err != nil { | ||||||
| 				clusters.Add(targets[j].ClusterName) | 		return err | ||||||
|  | 	} | ||||||
|  | 	if len(req.Configs) == 0 || len(req.Targets) == 0 { | ||||||
|  | 		return bcode.ErrNoConfigOrTarget | ||||||
|  | 	} | ||||||
|  | 	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}) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 		if namespaceSet.Contains(targets[i].Namespace) { | 
 | ||||||
| 			continue | 	var configs []*config.NamespacedName | ||||||
|  | 	for _, t := range req.Configs { | ||||||
|  | 		if t.Name != "" { | ||||||
|  | 			configs = append(configs, &config.NamespacedName{Namespace: t.Namespace, Name: t.Name}) | ||||||
| 		} | 		} | ||||||
| 		clusterTargets = append(clusterTargets, Target{ | 	} | ||||||
| 			Namespace: targets[i].Namespace, | 	return u.Factory.CreateOrUpdateDistribution(ctx, pro.GetNamespace(), req.Name, &config.CreateDistributionSpec{ | ||||||
| 			Clusters:  clusters.ToSlice(), | 		Configs: configs, | ||||||
|  | 		Targets: targets, | ||||||
| 	}) | 	}) | ||||||
| 		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 { | // ListDistributeConfigs list the all distributions
 | ||||||
| 	var s []string | func (u *configServiceImpl) ListConfigDistributions(ctx context.Context, project string) ([]*config.Distribution, error) { | ||||||
| 	for _, v := range i { | 	pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
| 		s = append(s, v.(string)) | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return s | 	return u.Factory.ListDistributions(ctx, pro.GetNamespace()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func stringToInterfaceSlice(i []string) []interface{} { | // DeleteConfigDistribution delete a distribution
 | ||||||
| 	var s []interface{} | func (u *configServiceImpl) DeleteConfigDistribution(ctx context.Context, project, name string) error { | ||||||
| 	for _, v := range i { | 	pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
| 		s = append(s, v) | 	if err != nil { | ||||||
| 	} |  | ||||||
| 	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 | 		return err | ||||||
| 	} | 	} | ||||||
| 		klog.InfoS("config sync application doesn't exist, no need destroy", "application", name) | 	if err := u.Factory.DeleteDistribution(ctx, pro.GetNamespace(), name); err != nil { | ||||||
| 		return nil | 		if errors.Is(err, config.ErrNotFoundDistribution) { | ||||||
|  | 			return bcode.ErrNotFoundDistribution | ||||||
| 		} | 		} | ||||||
| 	return k8sClient.Delete(ctx, app) | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertConfig(project string, config config.Config) *apis.Config { | ||||||
|  | 	return &apis.Config{ | ||||||
|  | 		Template:    config.Template.NamespacedName, | ||||||
|  | 		Sensitive:   config.Template.Sensitive, | ||||||
|  | 		Name:        config.Name, | ||||||
|  | 		Namespace:   config.Namespace, | ||||||
|  | 		Project:     project, | ||||||
|  | 		Alias:       config.Alias, | ||||||
|  | 		Description: config.Description, | ||||||
|  | 		CreatedTime: &config.CreateTime, | ||||||
|  | 		Properties:  config.Properties, | ||||||
|  | 		Secret:      config.Secret, | ||||||
|  | 		Targets:     config.Targets, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (u *configServiceImpl) GetConfig(ctx context.Context, project, name string) (*apis.Config, error) { | ||||||
|  | 	ns := types.DefaultKubeVelaNS | ||||||
|  | 	if project != "" { | ||||||
|  | 		pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		ns = pro.GetNamespace() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	it, err := u.Factory.GetConfig(ctx, ns, name, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, config.ErrSensitiveConfig) { | ||||||
|  | 			return nil, bcode.ErrSensitiveConfig | ||||||
|  | 		} | ||||||
|  | 		if errors.Is(err, config.ErrConfigNotFound) { | ||||||
|  | 			return nil, bcode.ErrConfigNotFound | ||||||
|  | 		} | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return convertConfig(project, *it), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (u *configServiceImpl) DeleteConfig(ctx context.Context, project, name string) error { | ||||||
|  | 	ns := types.DefaultKubeVelaNS | ||||||
|  | 	if project != "" { | ||||||
|  | 		pro, err := u.ProjectService.GetProject(ctx, project) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		ns = pro.GetNamespace() | ||||||
|  | 	} | ||||||
|  | 	return u.Factory.DeleteConfig(ctx, ns, name) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -18,755 +18,183 @@ package service | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"errors" | ||||||
| 	"sort" |  | ||||||
| 	"strings" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	. "github.com/agiledragon/gomonkey/v2" |  | ||||||
| 	terraformtypes "github.com/oam-dev/terraform-controller/api/types" |  | ||||||
| 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | ||||||
| 	"gotest.tools/assert" | 	. "github.com/onsi/ginkgo" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	. "github.com/onsi/gomega" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	apitypes "k8s.io/apimachinery/pkg/types" | ||||||
| 	"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/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/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/model" | 	"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" | ||||||
| 	apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" | 	v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/definition" | 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/multicluster" | 	"github.com/oam-dev/kubevela/pkg/config" | ||||||
|  | 	"github.com/oam-dev/kubevela/pkg/cue/script" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestListConfigTypes(t *testing.T) { | var alibabaTerraformTemplate = ` | ||||||
| 	s := runtime.NewScheme() | import "strings" | ||||||
| 	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() |  | ||||||
| 
 | 
 | ||||||
| 	patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) { | metadata: { | ||||||
| 		return k8sClient, nil, nil | 	name:        "terraform-provider-alibaba" | ||||||
| 	}) | 	alias:       "Alibaba Terraform Provider" | ||||||
| 	defer patches.Reset() | 	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, | 			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) |  | ||||||
| 	}) | 	}) | ||||||
| 	} | 	It("Test apply terraform template", func() { | ||||||
| } | 		tem, err := factory.ParseTemplate("alibaba-provider", []byte(alibabaTerraformTemplate)) | ||||||
| 
 | 		Expect(err).To(BeNil()) | ||||||
| func TestGetConfigType(t *testing.T) { | 		Expect(factory.CreateOrUpdateConfigTemplate(context.Background(), types.DefaultKubeVelaNS, tem)).To(BeNil()) | ||||||
| 	s := runtime.NewScheme() | 	}) | ||||||
| 	v1beta1.AddToScheme(s) | 	It("Test detail the template", func() { | ||||||
| 	corev1.AddToScheme(s) | 		detail, err := configService.GetTemplate(context.TODO(), config.NamespacedName{Name: "alibaba-provider"}) | ||||||
| 	def2 := &v1beta1.ComponentDefinition{ | 		Expect(err).To(BeNil()) | ||||||
| 		TypeMeta: metav1.TypeMeta{ | 		Expect(len(detail.UISchema)).To(Equal(4)) | ||||||
| 			Kind:       "ComponentDefinition", | 	}) | ||||||
| 			APIVersion: "core.oam.dev/v1beta1", | 	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{ | 			Properties: `{}`, | ||||||
| 			Name:      "def2", | 		}) | ||||||
|  | 		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, | 			Namespace: types.DefaultKubeVelaNS, | ||||||
| 			Annotations: map[string]string{ | 			Name:      "test", | ||||||
| 				definition.DefinitionAlias: "Def2", | 		}, &provider)).To(BeNil()) | ||||||
| 			}, |  | ||||||
| 			Labels: map[string]string{ |  | ||||||
| 				definition.UserPrefix + definition.ConfigCatalog: types.VelaCoreConfig, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def2).Build() |  | ||||||
| 
 |  | ||||||
| 	patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) { |  | ||||||
| 		return k8sClient, nil, nil |  | ||||||
| 	}) | 	}) | ||||||
| 	defer patches.Reset() |  | ||||||
| 
 | 
 | ||||||
| 	h := &configServiceImpl{KubeClient: k8sClient} | 	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 { | 		_, err = projectService.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: "mysql-project"}) | ||||||
| 		h    ConfigService | 		Expect(err).To(BeNil()) | ||||||
| 		name string |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	type want struct { | 		// can not share the config that is the system scope
 | ||||||
| 		configType *apis.ConfigType | 		list, err = configService.ListConfigs(context.TODO(), "mysql-project", "alibaba-provider", false) | ||||||
| 		errMsg     string | 		Expect(err).To(BeNil()) | ||||||
| 	} | 		Expect(len(list)).To(Equal(0)) | ||||||
| 
 | 
 | ||||||
| 	ctx := context.Background() | 		list, err = configService.ListConfigs(context.TODO(), "", "not-found", false) | ||||||
| 
 | 		Expect(err).To(BeNil()) | ||||||
| 	testcases := []struct { | 		Expect(len(list)).To(Equal(0)) | ||||||
| 		name string |  | ||||||
| 		args args |  | ||||||
| 		want want |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name: "success", |  | ||||||
| 			args: args{ |  | ||||||
| 				h:    h, |  | ||||||
| 				name: "def2", |  | ||||||
| 			}, |  | ||||||
| 			want: want{ |  | ||||||
| 				configType: &apis.ConfigType{ |  | ||||||
| 					Alias: "Def2", |  | ||||||
| 					Name:  "def2", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "error", |  | ||||||
| 			args: args{ |  | ||||||
| 				h:    h, |  | ||||||
| 				name: "def99", |  | ||||||
| 			}, |  | ||||||
| 			want: want{ |  | ||||||
| 				errMsg: "failed to get config type", |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tc := range testcases { |  | ||||||
| 		t.Run(tc.name, func(t *testing.T) { |  | ||||||
| 			got, err := tc.args.h.GetConfigType(ctx, tc.args.name) |  | ||||||
| 			if tc.want.errMsg != "" || err != nil { |  | ||||||
| 				assert.ErrorContains(t, err, tc.want.errMsg) |  | ||||||
| 			} |  | ||||||
| 			assert.DeepEqual(t, got, tc.want.configType) |  | ||||||
| 	}) | 	}) | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func TestCreateConfig(t *testing.T) { | 	It("Test detail a config", func() { | ||||||
| 	s := runtime.NewScheme() | 		_, err := configService.GetConfig(context.TODO(), "", "alibaba-test") | ||||||
| 	v1beta1.AddToScheme(s) | 		Expect(err).To(Equal(bcode.ErrSensitiveConfig)) | ||||||
| 	corev1.AddToScheme(s) |  | ||||||
| 
 |  | ||||||
| 	k8sClient := fake.NewClientBuilder().WithScheme(s).Build() |  | ||||||
| 
 |  | ||||||
| 	h := &configServiceImpl{KubeClient: k8sClient} |  | ||||||
| 
 |  | ||||||
| 	type args struct { |  | ||||||
| 		h   ConfigService |  | ||||||
| 		req apis.CreateConfigRequest |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	type want struct { |  | ||||||
| 		errMsg string |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx := context.Background() |  | ||||||
| 
 |  | ||||||
| 	properties, err := json.Marshal(map[string]interface{}{ |  | ||||||
| 		"name": "default", |  | ||||||
| 	}) | 	}) | ||||||
| 	assert.NilError(t, err) |  | ||||||
| 
 | 
 | ||||||
| 	testcases := []struct { | 	It("Test delete a config", func() { | ||||||
| 		name string | 		Expect(configService.DeleteConfig(context.TODO(), "", "alibaba-test")).To(BeNil()) | ||||||
| 		args args | 		var list terraformapi.ProviderList | ||||||
| 		want want | 		Expect(k8sClient.List(context.TODO(), &list)).To(BeNil()) | ||||||
| 	}{ | 		Expect(len(list.Items)).To(Equal(0)) | ||||||
| 		{ |  | ||||||
| 			name: "delete config when it's not ready", |  | ||||||
| 			args: args{ |  | ||||||
| 				h: h, |  | ||||||
| 				req: apis.CreateConfigRequest{ |  | ||||||
| 					Name:          "a", |  | ||||||
| 					ComponentType: "b", |  | ||||||
| 					Project:       "c", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "create terraform-alibaba config", |  | ||||||
| 			args: args{ |  | ||||||
| 				h: h, |  | ||||||
| 				req: apis.CreateConfigRequest{ |  | ||||||
| 					Name:          "n1", |  | ||||||
| 					ComponentType: "terraform-alibaba", |  | ||||||
| 					Project:       "p1", |  | ||||||
| 					Properties:    string(properties), |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tc := range testcases { |  | ||||||
| 		t.Run(tc.name, func(t *testing.T) { |  | ||||||
| 			err := tc.args.h.CreateConfig(ctx, tc.args.req) |  | ||||||
| 			if tc.want.errMsg != "" || err != nil { |  | ||||||
| 				assert.ErrorContains(t, err, tc.want.errMsg) |  | ||||||
| 			} |  | ||||||
| 	}) | 	}) | ||||||
| 	} | }) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestGetConfigs(t *testing.T) { |  | ||||||
| 	s := runtime.NewScheme() |  | ||||||
| 	v1beta1.AddToScheme(s) |  | ||||||
| 	corev1.AddToScheme(s) |  | ||||||
| 	terraformapi.AddToScheme(s) |  | ||||||
| 	createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022") |  | ||||||
| 
 |  | ||||||
| 	provider1 := &terraformapi.Provider{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:              "provider1", |  | ||||||
| 			Namespace:         "default", |  | ||||||
| 			CreationTimestamp: metav1.NewTime(createdTime), |  | ||||||
| 		}, |  | ||||||
| 		Status: terraformapi.ProviderStatus{ |  | ||||||
| 			State: terraformtypes.ProviderIsReady, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	provider2 := &terraformapi.Provider{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:              "provider2", |  | ||||||
| 			Namespace:         "default", |  | ||||||
| 			CreationTimestamp: metav1.NewTime(createdTime), |  | ||||||
| 		}, |  | ||||||
| 		Status: terraformapi.ProviderStatus{ |  | ||||||
| 			State: terraformtypes.ProviderIsNotReady, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	provider3 := &terraformapi.Provider{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      "provider3", |  | ||||||
| 			Namespace: "default", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	app1 := &v1beta1.Application{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      "provider3", |  | ||||||
| 			Namespace: types.DefaultKubeVelaNS, |  | ||||||
| 			Labels: map[string]string{ |  | ||||||
| 				types.LabelConfigType: "terraform-alibaba", |  | ||||||
| 			}, |  | ||||||
| 			CreationTimestamp: metav1.NewTime(createdTime), |  | ||||||
| 		}, |  | ||||||
| 		Status: common.AppStatus{ |  | ||||||
| 			Phase: common.ApplicationRendering, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1).Build() |  | ||||||
| 
 |  | ||||||
| 	h := &configServiceImpl{KubeClient: k8sClient} |  | ||||||
| 
 |  | ||||||
| 	type args struct { |  | ||||||
| 		configType string |  | ||||||
| 		h          ConfigService |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	type want struct { |  | ||||||
| 		configs []*apis.Config |  | ||||||
| 		errMsg  string |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx := context.Background() |  | ||||||
| 
 |  | ||||||
| 	testcases := []struct { |  | ||||||
| 		name string |  | ||||||
| 		args args |  | ||||||
| 		want want |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name: "success", |  | ||||||
| 			args: args{ |  | ||||||
| 				configType: types.TerraformProvider, |  | ||||||
| 				h:          h, |  | ||||||
| 			}, |  | ||||||
| 			want: want{ |  | ||||||
| 				configs: []*apis.Config{ |  | ||||||
| 					{ |  | ||||||
| 						Name:        "provider1", |  | ||||||
| 						CreatedTime: &createdTime, |  | ||||||
| 						Status:      "Ready", |  | ||||||
| 					}, |  | ||||||
| 					{ |  | ||||||
| 						Name:        "provider2", |  | ||||||
| 						CreatedTime: &createdTime, |  | ||||||
| 						Status:      "Not ready", |  | ||||||
| 					}, |  | ||||||
| 					{ |  | ||||||
| 						Name:              "provider3", |  | ||||||
| 						CreatedTime:       &createdTime, |  | ||||||
| 						Status:            "Not ready", |  | ||||||
| 						ConfigType:        "terraform-alibaba", |  | ||||||
| 						ApplicationStatus: common.ApplicationRendering, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tc := range testcases { |  | ||||||
| 		t.Run(tc.name, func(t *testing.T) { |  | ||||||
| 			got, err := tc.args.h.GetConfigs(ctx, tc.args.configType) |  | ||||||
| 			if tc.want.errMsg != "" || err != nil { |  | ||||||
| 				assert.ErrorContains(t, err, tc.want.errMsg) |  | ||||||
| 			} |  | ||||||
| 			assert.DeepEqual(t, got, tc.want.configs) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestMergeTargets(t *testing.T) { |  | ||||||
| 	currentTargets := []ApplicationDeployTarget{ |  | ||||||
| 		{ |  | ||||||
| 			Namespace: "n1", |  | ||||||
| 			Clusters:  []string{"c1", "c2"}, |  | ||||||
| 		}, { |  | ||||||
| 			Namespace: "n2", |  | ||||||
| 			Clusters:  []string{"c3"}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	targets := []*model.ClusterTarget{ |  | ||||||
| 		{ |  | ||||||
| 			Namespace:   "n3", |  | ||||||
| 			ClusterName: "c4", |  | ||||||
| 		}, { |  | ||||||
| 			Namespace:   "n1", |  | ||||||
| 			ClusterName: "c5", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Namespace:   "n2", |  | ||||||
| 			ClusterName: "c3", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	expected := []ApplicationDeployTarget{ |  | ||||||
| 		{ |  | ||||||
| 			Namespace: "n1", |  | ||||||
| 			Clusters:  []string{"c1", "c2", "c5"}, |  | ||||||
| 		}, { |  | ||||||
| 			Namespace: "n2", |  | ||||||
| 			Clusters:  []string{"c3"}, |  | ||||||
| 		}, { |  | ||||||
| 			Namespace: "n3", |  | ||||||
| 			Clusters:  []string{"c4"}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	got := mergeTargets(currentTargets, targets) |  | ||||||
| 
 |  | ||||||
| 	for i, g := range got { |  | ||||||
| 		clusters := g.Clusters |  | ||||||
| 		sort.SliceStable(clusters, func(i, j int) bool { |  | ||||||
| 			return clusters[i] < clusters[j] |  | ||||||
| 		}) |  | ||||||
| 		got[i].Clusters = clusters |  | ||||||
| 	} |  | ||||||
| 	assert.DeepEqual(t, expected, got) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestConvert(t *testing.T) { |  | ||||||
| 	targets := []*model.ClusterTarget{ |  | ||||||
| 		{ |  | ||||||
| 			Namespace:   "n3", |  | ||||||
| 			ClusterName: "c4", |  | ||||||
| 		}, { |  | ||||||
| 			Namespace:   "n1", |  | ||||||
| 			ClusterName: "c5", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Namespace:   "n2", |  | ||||||
| 			ClusterName: "c3", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Namespace:   "n3", |  | ||||||
| 			ClusterName: "c5", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	expected := []ApplicationDeployTarget{ |  | ||||||
| 		{ |  | ||||||
| 			Namespace: "n3", |  | ||||||
| 			Clusters:  []string{"c4", "c5"}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Namespace: "n1", |  | ||||||
| 			Clusters:  []string{"c5"}, |  | ||||||
| 		}, { |  | ||||||
| 			Namespace: "n2", |  | ||||||
| 			Clusters:  []string{"c3"}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	got := convertClusterTargets(targets) |  | ||||||
| 
 |  | ||||||
| 	for i, g := range got { |  | ||||||
| 		clusters := g.Clusters |  | ||||||
| 		sort.SliceStable(clusters, func(i, j int) bool { |  | ||||||
| 			return clusters[i] < clusters[j] |  | ||||||
| 		}) |  | ||||||
| 		got[i].Clusters = clusters |  | ||||||
| 	} |  | ||||||
| 	assert.DeepEqual(t, expected, got) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestDestroySyncConfigsApp(t *testing.T) { |  | ||||||
| 	s := runtime.NewScheme() |  | ||||||
| 	v1beta1.AddToScheme(s) |  | ||||||
| 	corev1.AddToScheme(s) |  | ||||||
| 	app1 := &v1beta1.Application{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      "config-sync-p1", |  | ||||||
| 			Namespace: types.DefaultKubeVelaNS, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	k8sClient1 := fake.NewClientBuilder().WithScheme(s).WithObjects(app1).Build() |  | ||||||
| 
 |  | ||||||
| 	k8sClient2 := fake.NewClientBuilder().Build() |  | ||||||
| 
 |  | ||||||
| 	type args struct { |  | ||||||
| 		project   string |  | ||||||
| 		k8sClient client.Client |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	type want struct { |  | ||||||
| 		errMsg string |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx := context.Background() |  | ||||||
| 
 |  | ||||||
| 	testcases := map[string]struct { |  | ||||||
| 		args args |  | ||||||
| 		want want |  | ||||||
| 	}{ |  | ||||||
| 		"found": { |  | ||||||
| 			args: args{ |  | ||||||
| 				project:   "p1", |  | ||||||
| 				k8sClient: k8sClient1, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		"not found": { |  | ||||||
| 			args: args{ |  | ||||||
| 				project:   "p1", |  | ||||||
| 				k8sClient: k8sClient2, |  | ||||||
| 			}, |  | ||||||
| 			want: want{ |  | ||||||
| 				errMsg: "no kind is registered for the type v1beta1.Application", |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	for name, tc := range testcases { |  | ||||||
| 		t.Run(name, func(t *testing.T) { |  | ||||||
| 			err := destroySyncConfigsApp(ctx, tc.args.k8sClient, tc.args.project) |  | ||||||
| 			if err != nil || tc.want.errMsg != "" { |  | ||||||
| 				if !strings.Contains(err.Error(), tc.want.errMsg) { |  | ||||||
| 					assert.ErrorContains(t, err, tc.want.errMsg) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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
 | 		// render default ui schema
 | ||||||
| 		defaultUISchema := renderDefaultUISchema(schema) | 		defaultUISchema := renderDefaultUISchema(schema) | ||||||
| 		// patch from custom ui 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 | 	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 | 	var cm v1.ConfigMap | ||||||
| 	if err := d.KubeClient.Get(ctx, k8stypes.NamespacedName{ | 	if err := cli.Get(ctx, k8stypes.NamespacedName{ | ||||||
| 		Namespace: types.DefaultKubeVelaNS, | 		Namespace: types.DefaultKubeVelaNS, | ||||||
| 		Name:      fmt.Sprintf("%s-uischema-%s", defType, name), | 		Name:      fmt.Sprintf("%s-uischema-%s", defType, name), | ||||||
| 	}, &cm); err != nil { | 	}, &cm); err != nil { | ||||||
|  |  | ||||||
|  | @ -20,18 +20,14 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 
 | 
 | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/config" |  | ||||||
| 
 |  | ||||||
| 	"github.com/oam-dev/kubevela/apis/types" | 	"github.com/oam-dev/kubevela/apis/types" | ||||||
| 	v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" | 	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/bcode" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/log" | 	"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" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/common" | 	"github.com/oam-dev/kubevela/pkg/utils/common" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/helm" | 	"github.com/oam-dev/kubevela/pkg/utils/helm" | ||||||
| 
 | 
 | ||||||
| 	corev1 "k8s.io/api/core/v1" |  | ||||||
| 	types2 "k8s.io/apimachinery/pkg/types" | 	types2 "k8s.io/apimachinery/pkg/types" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 
 | 
 | ||||||
|  | @ -56,6 +52,7 @@ type HelmService interface { | ||||||
| type defaultHelmImpl struct { | type defaultHelmImpl struct { | ||||||
| 	helper        *helm.Helper | 	helper        *helm.Helper | ||||||
| 	K8sClient     client.Client `inject:"kubeClient"` | 	K8sClient     client.Client `inject:"kubeClient"` | ||||||
|  | 	ConfigService ConfigService `inject:""` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d defaultHelmImpl) ListChartNames(ctx context.Context, repoURL string, secretName string, skipCache bool) ([]string, error) { | 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) { | func (d defaultHelmImpl) ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error) { | ||||||
| 	var res []*v1.ChartRepoResponse | 	var res []*v1.ChartRepoResponse | ||||||
| 	var err error | 	configs, err := d.ConfigService.ListConfigs(ctx, projectName, types.HelmRepository, true) | ||||||
| 
 |  | ||||||
| 	projectSecrets := corev1.SecretList{} |  | ||||||
| 	opts := []client.ListOption{ |  | ||||||
| 		client.MatchingLabels{oam.LabelConfigType: "config-helm-repository"}, |  | ||||||
| 		client.InNamespace(types.DefaultKubeVelaNS), |  | ||||||
| 	} |  | ||||||
| 	err = d.K8sClient.List(ctx, &projectSecrets, opts...) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 	for _, item := range configs { | ||||||
| 	for _, item := range projectSecrets.Items { | 		if item.Properties != nil { | ||||||
| 		if config.ProjectMatched(item.DeepCopy(), projectName) { | 			url, ok := item.Properties["url"].(string) | ||||||
| 			res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name}) | 			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/ginkgo" | ||||||
| 	. "github.com/onsi/gomega" | 	. "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/oam/util" | ||||||
|  | 	"github.com/oam-dev/kubevela/pkg/utils/apply" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/helm" | 	"github.com/oam-dev/kubevela/pkg/utils/helm" | ||||||
| 
 | 
 | ||||||
| 	v1 "k8s.io/api/core/v1" | 	v1 "k8s.io/api/core/v1" | ||||||
|  | @ -53,11 +57,22 @@ func TestFlattenKeyFunc(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewTestHelmService new helm service for test
 | // 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{ | 	return &defaultHelmImpl{ | ||||||
| 		helper:    helm.NewHelperWithCache(), | 		helper:    helm.NewHelperWithCache(), | ||||||
| 		K8sClient: k8sClient, | 		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() { | var _ = Describe("Test helm repo list", func() { | ||||||
|  | @ -68,6 +83,7 @@ var _ = Describe("Test helm repo list", func() { | ||||||
| 		pSec = v1.Secret{} | 		pSec = v1.Secret{} | ||||||
| 		gSec = 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: "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(projectSecret), &pSec)).Should(BeNil()) | ||||||
| 		Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil()) | 		Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil()) | ||||||
| 		Expect(k8sClient.Create(ctx, &pSec)).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() { | 	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") | 		list, err := u.ListChartRepo(ctx, "my-project") | ||||||
| 		Expect(err).Should(BeNil()) | 		Expect(err).Should(BeNil()) | ||||||
| 		Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(2)) | 		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() { | 	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") | 		list, err := u.ListChartRepo(ctx, "not-exist-project") | ||||||
| 		Expect(err).Should(BeNil()) | 		Expect(err).Should(BeNil()) | ||||||
| 		Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1)) | 		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() { | 	It("Test list func without project", func() { | ||||||
| 		u := NewTestHelmService() | 		u, _, err := NewTestHelmService() | ||||||
|  | 		Expect(err).Should(BeNil()) | ||||||
| 		list, err := u.ListChartRepo(ctx, "") | 		list, err := u.ListChartRepo(ctx, "") | ||||||
| 		Expect(err).Should(BeNil()) | 		Expect(err).Should(BeNil()) | ||||||
| 		Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1)) | 		Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1)) | ||||||
|  | @ -171,7 +201,8 @@ var _ = Describe("test helm usecasae", func() { | ||||||
| 
 | 
 | ||||||
| 		defer mockServer.Close() | 		defer mockServer.Close() | ||||||
| 
 | 
 | ||||||
| 		u := NewTestHelmService() | 		u, _, err := NewTestHelmService() | ||||||
|  | 		Expect(err).Should(BeNil()) | ||||||
| 		charts, err := u.ListChartNames(ctx, mockServer.URL, "repo-secret", false) | 		charts, err := u.ListChartNames(ctx, mockServer.URL, "repo-secret", false) | ||||||
| 		Expect(err).Should(BeNil()) | 		Expect(err).Should(BeNil()) | ||||||
| 		Expect(len(charts)).Should(BeEquivalentTo(1)) | 		Expect(len(charts)).Should(BeEquivalentTo(1)) | ||||||
|  | @ -189,8 +220,9 @@ var _ = Describe("test helm usecasae", func() { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	It("coverage not secret notExist error", func() { | 	It("coverage not secret notExist error", func() { | ||||||
| 		u := NewTestHelmService() | 		u, _, err := NewTestHelmService() | ||||||
| 		_, err := u.ListChartNames(ctx, "http://127.0.0.1:8080", "repo-secret-notExist", false) | 		Expect(err).Should(BeNil()) | ||||||
|  | 		_, err = u.ListChartNames(ctx, "http://127.0.0.1:8080", "repo-secret-notExist", false) | ||||||
| 		Expect(err).ShouldNot(BeNil()) | 		Expect(err).ShouldNot(BeNil()) | ||||||
| 
 | 
 | ||||||
| 		_, err = u.ListChartVersions(ctx, "http://127.0.0.1:8080", "mysql", "repo-secret-notExist", false) | 		_, 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
 |   url: https://charts.bitnami.com/bitnami
 | ||||||
| kind: Secret | kind: Secret | ||||||
| metadata: | metadata: | ||||||
|   labels: |  | ||||||
|     config.oam.dev/type: config-helm-repository |  | ||||||
|     config.oam.dev/project: "" |  | ||||||
|   name: global-helm-repo |   name: global-helm-repo | ||||||
|   namespace: vela-system |   namespace: vela-system | ||||||
|  |   labels: | ||||||
|  |     config.oam.dev/type: helm-repository | ||||||
|  |     config.oam.dev/scope: project | ||||||
|  |     config.oam.dev/catalog: velacore-config | ||||||
| type: Opaque | type: Opaque | ||||||
| ` | ` | ||||||
| 	projectSecret = ` | 	projectSecret = ` | ||||||
|  | @ -357,10 +390,11 @@ apiVersion: v1 | ||||||
| kind: Secret | kind: Secret | ||||||
| metadata: | metadata: | ||||||
|   name: project-helm-repo |   name: project-helm-repo | ||||||
|   namespace: vela-system |   namespace: project-my-project | ||||||
|   labels: |   labels: | ||||||
|     config.oam.dev/type: config-helm-repository |     config.oam.dev/type: helm-repository | ||||||
|     config.oam.dev/project: my-project |     config.oam.dev/catalog: velacore-config | ||||||
|  |     config.oam.dev/scope: project | ||||||
| stringData: | stringData: | ||||||
|   url: https://kedacore.github.io/charts
 |   url: https://kedacore.github.io/charts
 | ||||||
| type: Opaque | type: Opaque | ||||||
|  | @ -372,8 +406,9 @@ metadata: | ||||||
|   name: repo-secret |   name: repo-secret | ||||||
|   namespace: vela-system |   namespace: vela-system | ||||||
|   labels: |   labels: | ||||||
|     config.oam.dev/type: config-helm-repository |     config.oam.dev/type: helm-repository | ||||||
|     config.oam.dev/project: my-project-2 |     config.oam.dev/project: my-project-2 | ||||||
|  |     config.oam.dev/catalog: velacore-config | ||||||
| stringData: | stringData: | ||||||
|   username: admin |   username: admin | ||||||
|   password: admin |   password: admin | ||||||
|  |  | ||||||
|  | @ -56,28 +56,29 @@ type ImageService interface { | ||||||
| 
 | 
 | ||||||
| type imageImpl struct { | type imageImpl struct { | ||||||
| 	K8sClient     client.Client `inject:"kubeClient"` | 	K8sClient     client.Client `inject:"kubeClient"` | ||||||
|  | 	ConfigService ConfigService `inject:""` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ListImageRepos list the image repositories via user configuration
 | // ListImageRepos list the image repositories via user configuration
 | ||||||
| func (i *imageImpl) ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error) { | func (i *imageImpl) ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error) { | ||||||
| 	var secrets corev1.SecretList | 	configs, err := i.ConfigService.ListConfigs(ctx, project, types.ImageRegistry, true) | ||||||
| 	if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS), | 	if err != nil { | ||||||
| 		client.MatchingLabels{ |  | ||||||
| 			types.LabelConfigCatalog: types.VelaCoreConfig, |  | ||||||
| 			types.LabelConfigType:    types.ImageRegistry, |  | ||||||
| 		}); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	var repos []v1.ImageRegistry | 	var repos []v1.ImageRegistry | ||||||
| 	for _, secret := range secrets.Items { | 	for _, item := range configs { | ||||||
| 		if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project { | 		if item.Properties != nil { | ||||||
|  | 			registry, ok := item.Properties["registry"].(string) | ||||||
|  | 			if ok { | ||||||
| 				repos = append(repos, v1.ImageRegistry{ | 				repos = append(repos, v1.ImageRegistry{ | ||||||
| 				Name:       secret.Name, | 					Name:       item.Name, | ||||||
| 				SecretName: secret.Name, | 					SecretName: item.Name, | ||||||
| 				Domain:     secret.Labels[types.LabelConfigIdentifier], | 					Domain:     registry, | ||||||
|  | 					Secret:     item.Secret, | ||||||
| 				}) | 				}) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	return repos, nil | 	return repos, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -88,56 +89,49 @@ func (i *imageImpl) GetImageInfo(ctx context.Context, project, secretName, image | ||||||
| 	} | 	} | ||||||
| 	ref, err := name.ParseReference(imageName) | 	ref, err := name.ParseReference(imageName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		imageInfo.Message = "The image name is invalid" | 		imageInfo.Message = "The image name is invalid." | ||||||
| 		return imageInfo | 		return imageInfo | ||||||
| 	} | 	} | ||||||
| 	registryDomain := ref.Context().RegistryStr() | 	registryDomain := ref.Context().RegistryStr() | ||||||
| 	imageInfo.Registry = registryDomain | 	imageInfo.Registry = registryDomain | ||||||
| 	var secrets corev1.SecretList | 
 | ||||||
| 	if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS), | 	registries, err := i.ListImageRepos(ctx, project) | ||||||
| 		client.MatchingLabels{ | 	if err != nil { | ||||||
| 			types.LabelConfigCatalog:    types.VelaCoreConfig, | 		log.Logger.Warnf("fail to list the image registries:%s", err.Error()) | ||||||
| 			types.LabelConfigType:       types.ImageRegistry, | 		imageInfo.Message = "There is no registry." | ||||||
| 			types.LabelConfigIdentifier: registryDomain, | 		return imageInfo | ||||||
| 		}); err != nil { |  | ||||||
| 		log.Logger.Warnf("fail to list the docker registries, %s", err.Error()) |  | ||||||
| 	} | 	} | ||||||
| 	var selectSecret []*corev1.Secret | 	var selectRegistry []v1.ImageRegistry | ||||||
| 	var selectSecretNames []string | 	var selectRegistryNames []string | ||||||
| 	// get info with specified secret
 | 	// get info with specified secret
 | ||||||
| 	if secretName != "" { | 	if secretName != "" { | ||||||
| 		for i, secret := range secrets.Items { | 		for i, registry := range registries { | ||||||
| 			if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project { | 			if secretName == registry.SecretName { | ||||||
| 				if secretName == secret.Name { | 				selectRegistry = append(selectRegistry, registries[i]) | ||||||
| 					selectSecret = append(selectSecret, &secrets.Items[i]) | 				selectRegistryNames = append(selectRegistryNames, registry.Name) | ||||||
| 					if secret.Type == corev1.SecretTypeDockerConfigJson { |  | ||||||
| 						selectSecretNames = append(selectSecretNames, secret.Name) |  | ||||||
| 					} |  | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// get info with the secret which match the registry domain
 | 	// get info with the secret which match the registry domain
 | ||||||
| 	if selectSecret == nil { | 	if selectRegistry == nil { | ||||||
| 		for i, secret := range secrets.Items { | 		for i, registry := range registries { | ||||||
| 			if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project { | 			if registry.Domain == registryDomain { | ||||||
| 				if secret.Labels[types.LabelConfigIdentifier] == registryDomain { | 				selectRegistry = append(selectRegistry, registries[i]) | ||||||
| 					selectSecret = append(selectSecret, &secrets.Items[i]) | 				selectRegistryNames = append(selectRegistryNames, registry.Name) | ||||||
| 					if secret.Type == corev1.SecretTypeDockerConfigJson { |  | ||||||
| 						selectSecretNames = append(selectSecretNames, secret.Name) |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	var username, password string | 	var username, password string | ||||||
| 	var insecure = false | 	var insecure = false | ||||||
| 	var useHTTP = false | 	var useHTTP = false | ||||||
| 	imageInfo.SecretNames = selectSecretNames | 	imageInfo.SecretNames = selectRegistryNames | ||||||
| 	if len(selectSecret) > 0 { | 	for _, registry := range selectRegistry { | ||||||
| 		insecure, useHTTP, username, password = getAccountFromSecret(*selectSecret[0], registryDomain) | 		if registry.Secret != nil { | ||||||
|  | 			insecure, useHTTP, username, password = getAccountFromSecret(*registry.Secret, registryDomain) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	err = getImageInfo(imageName, insecure, useHTTP, username, password, &imageInfo) | 	err = getImageInfo(imageName, insecure, useHTTP, username, password, &imageInfo) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -20,16 +20,10 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" |  | ||||||
| 
 | 
 | ||||||
| 	terraformtypes "github.com/oam-dev/terraform-controller/api/types" |  | ||||||
| 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/meta" |  | ||||||
| 	"k8s.io/klog/v2" |  | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"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/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/model" | 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/model" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" | 	"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 | 	DeleteProjectUser(ctx context.Context, projectName string, userName string) error | ||||||
| 	UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error) | 	UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error) | ||||||
| 	Init(ctx context.Context) 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 { | 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 { | 	if err := p.Store.Delete(ctx, &model.Project{Name: name}); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	// delete config-sync application
 | 	return nil | ||||||
| 	return destroySyncConfigsApp(ctx, p.K8sClient, name) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CreateProject create project
 | // CreateProject create project
 | ||||||
|  | @ -507,95 +500,21 @@ func (p *projectServiceImpl) UpdateProjectUser(ctx context.Context, projectName | ||||||
| 	return ConvertProjectUserModel2Base(&projectUser, user), nil | 	return ConvertProjectUserModel2Base(&projectUser, user), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *projectServiceImpl) GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error) { | func (p *projectServiceImpl) ListTerraformProviders(ctx context.Context, projectName string) ([]*apisv1.TerraformProvider, error) { | ||||||
| 	var ( | 	l := &terraformapi.ProviderList{} | ||||||
| 		configs                  []*apisv1.Config | 	if err := p.K8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil { | ||||||
| 		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 { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 	var res []*apisv1.TerraformProvider | ||||||
| 	if configType == types.TerraformProvider || configType == "" { | 	for _, provider := range l.Items { | ||||||
| 		// legacy providers
 | 		res = append(res, &apisv1.TerraformProvider{ | ||||||
| 		var providers = &terraformapi.ProviderList{} | 			Name:       provider.Name, | ||||||
| 		if err := p.K8sClient.List(ctx, providers, client.InNamespace(types.DefaultAppNamespace)); err != nil { | 			Region:     provider.Spec.Region, | ||||||
| 			// this logic depends on the terraform addon, ignore the no matches kind error before the terraform addon is installed.
 | 			Provider:   provider.Spec.Provider, | ||||||
| 			if !meta.IsNoMatchError(err) { | 			CreateTime: provider.CreationTimestamp.Time, | ||||||
| 				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, |  | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	} | 	return res, nil | ||||||
| 
 |  | ||||||
| 	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 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ConvertProjectModel2Base convert project model to base struct
 | // ConvertProjectModel2Base convert project model to base struct
 | ||||||
|  | @ -628,28 +547,6 @@ func ConvertProjectUserModel2Base(user *model.ProjectUser, userModel *model.User | ||||||
| 	return base | 	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
 | // NewTestProjectService create the project service instance for testing
 | ||||||
| func NewTestProjectService(ds datastore.DataStore, c client.Client) ProjectService { | func NewTestProjectService(ds datastore.DataStore, c client.Client) ProjectService { | ||||||
| 	targetImpl := &targetServiceImpl{K8sClient: c, Store: ds} | 	targetImpl := &targetServiceImpl{K8sClient: c, Store: ds} | ||||||
|  |  | ||||||
|  | @ -24,11 +24,8 @@ import ( | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"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" | 	velatypes "github.com/oam-dev/kubevela/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/model" | 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/model" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" | 	"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" | ||||||
|  | @ -158,18 +155,6 @@ var _ = Describe("Test project service functions", func() { | ||||||
| 			Name:        "test-project", | 			Name:        "test-project", | ||||||
| 			Description: "this is a project description", | 			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) | 		_, err := projectService.CreateProject(context.TODO(), req) | ||||||
| 		Expect(err).Should(BeNil()) | 		Expect(err).Should(BeNil()) | ||||||
| 
 | 
 | ||||||
|  | @ -264,19 +249,6 @@ var _ = Describe("Test project service functions", func() { | ||||||
| 			Name:        "test-project", | 			Name:        "test-project", | ||||||
| 			Description: "this is a project description", | 			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) | 		_, err := projectService.CreateProject(context.TODO(), req) | ||||||
| 		Expect(err).Should(BeNil()) | 		Expect(err).Should(BeNil()) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -53,6 +53,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{ | ||||||
| 		Resources: []string{ | 		Resources: []string{ | ||||||
| 			"project:{projectName}", | 			"project:{projectName}", | ||||||
| 			"project:{projectName}/config:*", | 			"project:{projectName}/config:*", | ||||||
|  | 			"project:{projectName}/provider:*", | ||||||
| 			"project:{projectName}/role:*", | 			"project:{projectName}/role:*", | ||||||
| 			"project:{projectName}/projectUser:*", | 			"project:{projectName}/projectUser:*", | ||||||
| 			"project:{projectName}/permission:*", | 			"project:{projectName}/permission:*", | ||||||
|  | @ -90,7 +91,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{ | ||||||
| 	{ | 	{ | ||||||
| 		Name:      "configuration-read", | 		Name:      "configuration-read", | ||||||
| 		Alias:     "Environment Management", | 		Alias:     "Environment Management", | ||||||
| 		Resources: []string{"project:{projectName}/config:*"}, | 		Resources: []string{"project:{projectName}/config:*", "project:{projectName}/provider:*"}, | ||||||
| 		Actions:   []string{"list", "detail"}, | 		Actions:   []string{"list", "detail"}, | ||||||
| 		Effect:    "Allow", | 		Effect:    "Allow", | ||||||
| 		Scope:     "project", | 		Scope:     "project", | ||||||
|  | @ -163,9 +164,9 @@ var defaultPlatformPermission = []*model.PermissionTemplate{ | ||||||
| 		Scope:     "platform", | 		Scope:     "platform", | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name:      "integration-management", | 		Name:      "config-management", | ||||||
| 		Alias:     "Integration Management", | 		Alias:     "Config Management", | ||||||
| 		Resources: []string{"configType:*/*"}, | 		Resources: []string{"config:*/*"}, | ||||||
| 		Actions:   []string{"*"}, | 		Actions:   []string{"*"}, | ||||||
| 		Effect:    "Allow", | 		Effect:    "Allow", | ||||||
| 		Scope:     "platform", | 		Scope:     "platform", | ||||||
|  | @ -229,7 +230,10 @@ var ResourceMaps = map[string]resourceMetadata{ | ||||||
| 				pathName: "userName", | 				pathName: "userName", | ||||||
| 			}, | 			}, | ||||||
| 			"applicationTemplate": {}, | 			"applicationTemplate": {}, | ||||||
| 			"config":              {}, | 			"config": { | ||||||
|  | 				pathName: "configName", | ||||||
|  | 			}, | ||||||
|  | 			"provider": {}, | ||||||
| 		}, | 		}, | ||||||
| 		pathName: "projectName", | 		pathName: "projectName", | ||||||
| 	}, | 	}, | ||||||
|  | @ -268,6 +272,8 @@ var ResourceMaps = map[string]resourceMetadata{ | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	"cloudshell":     {}, | 	"cloudshell":     {}, | ||||||
|  | 	"config":         {}, | ||||||
|  | 	"configTemplate": {}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var existResourcePaths = convert(ResourceMaps) | var existResourcePaths = convert(ResourceMaps) | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 		ControlPlaneStartTimeout: time.Minute * 3, | 		ControlPlaneStartTimeout: time.Minute * 3, | ||||||
| 		ControlPlaneStopTimeout:  time.Minute, | 		ControlPlaneStopTimeout:  time.Minute, | ||||||
| 		UseExistingCluster:       pointer.BoolPtr(false), | 		UseExistingCluster:       pointer.BoolPtr(false), | ||||||
| 		CRDDirectoryPaths:        []string{"../../../../charts/vela-core/crds"}, | 		CRDDirectoryPaths:        []string{"../../../../charts/vela-core/crds", "./testdata/crds"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	By("start kube test env") | 	By("start kube test env") | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ package service | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"k8s.io/apimachinery/pkg/util/rand" | 	"k8s.io/apimachinery/pkg/util/rand" | ||||||
|  | @ -138,7 +139,7 @@ func (u systemInfoServiceImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1. | ||||||
| 			VelaAddress: sysInfo.VelaAddress, | 			VelaAddress: sysInfo.VelaAddress, | ||||||
| 			Connectors:  connectors, | 			Connectors:  connectors, | ||||||
| 		}); err != nil { | 		}); err != nil { | ||||||
| 			return nil, err | 			return nil, fmt.Errorf("fail to generate the dex config: %w", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	err = u.Store.Put(ctx, &modifiedInfo) | 	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" | 	"github.com/oam-dev/kubevela/pkg/apiserver/domain/service" | ||||||
| 	apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" | 	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/apiserver/utils/bcode" | ||||||
|  | 	"github.com/oam-dev/kubevela/pkg/config" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ConfigAPIInterface returns config web service
 | // ConfigAPIInterface returns config web service
 | ||||||
|  | @ -37,67 +38,55 @@ type configAPIInterface struct { | ||||||
| 
 | 
 | ||||||
| func (s *configAPIInterface) GetWebServiceRoute() *restful.WebService { | func (s *configAPIInterface) GetWebServiceRoute() *restful.WebService { | ||||||
| 	ws := new(restful.WebService) | 	ws := new(restful.WebService) | ||||||
| 	ws.Path(versionPrefix+"/config_types"). | 	ws.Path(versionPrefix+"/configs"). | ||||||
| 		Consumes(restful.MIME_XML, restful.MIME_JSON). | 		Consumes(restful.MIME_XML, restful.MIME_JSON). | ||||||
| 		Produces(restful.MIME_JSON, restful.MIME_XML). | 		Produces(restful.MIME_JSON, restful.MIME_XML). | ||||||
| 		Doc("api for configuration management") | 		Doc("api for config management") | ||||||
| 
 | 
 | ||||||
| 	tags := []string{"config"} | 	tags := []string{"config"} | ||||||
| 
 | 
 | ||||||
| 	ws.Route(ws.GET("/").To(s.listConfigTypes). | 	ws.Route(ws.POST("/").To(s.createConfig). | ||||||
| 		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). |  | ||||||
| 		Doc("create or update a config"). | 		Doc("create or update a config"). | ||||||
| 		Metadata(restfulspec.KeyOpenAPITags, tags). | 		Metadata(restfulspec.KeyOpenAPITags, tags). | ||||||
| 		Filter(s.RbacService.CheckPerm("configType/config", "create")). | 		Filter(s.RbacService.CheckPerm("config", "create")). | ||||||
| 		Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")). |  | ||||||
| 		Reads(apis.CreateConfigRequest{}). | 		Reads(apis.CreateConfigRequest{}). | ||||||
| 		Returns(200, "OK", apis.EmptyResponse{}). | 		Returns(200, "OK", apis.Config{}). | ||||||
| 		Returns(400, "Bad Request", bcode.Bcode{}). | 		Returns(400, "Bad Request", bcode.Bcode{}). | ||||||
| 		Returns(404, "Not Found", bcode.Bcode{}). | 		Returns(404, "Not Found", bcode.Bcode{}). | ||||||
| 		Writes(apis.EmptyResponse{})) | 		Writes(apis.Config{})) | ||||||
| 
 | 
 | ||||||
| 	ws.Route(ws.GET("/{configType}/configs").To(s.getConfigs). | 	ws.Route(ws.GET("/").To(s.getConfigs). | ||||||
| 		Doc("get configs from a config type"). | 		Doc("list all configs that belong to the system scope"). | ||||||
| 		Metadata(restfulspec.KeyOpenAPITags, tags). | 		Metadata(restfulspec.KeyOpenAPITags, tags). | ||||||
| 		Filter(s.RbacService.CheckPerm("configType/config", "list")). | 		Filter(s.RbacService.CheckPerm("config", "list")). | ||||||
| 		Param(ws.PathParameter("configType", "identifier of the config").DataType("string")). | 		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(200, "OK", []*apis.Config{}). | ||||||
| 		Returns(400, "Bad Request", bcode.Bcode{}). | 		Returns(400, "Bad Request", bcode.Bcode{}). | ||||||
| 		Writes(apis.ConfigType{})) | 		Writes(apis.Config{})) | ||||||
| 
 | 
 | ||||||
| 	ws.Route(ws.GET("/{configType}/configs/{name}").To(s.getConfig). | 	ws.Route(ws.PUT("/{configName}").To(s.updateConfig). | ||||||
| 		Doc("get a config from a config type"). | 		Doc("update a config"). | ||||||
| 		Metadata(restfulspec.KeyOpenAPITags, tags). | 		Metadata(restfulspec.KeyOpenAPITags, tags). | ||||||
| 		Filter(s.RbacService.CheckPerm("configType/config", "get")). | 		Filter(s.RbacService.CheckPerm("config", "update")). | ||||||
| 		Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")). | 		Param(ws.PathParameter("configName", "identifier of the config").DataType("string")). | ||||||
| 		Param(ws.PathParameter("name", "identifier of the config").DataType("string")). | 		Returns(200, "OK", []*apis.UpdateConfigRequest{}). | ||||||
| 		Returns(200, "OK", []*apis.Config{}). |  | ||||||
| 		Returns(400, "Bad Request", bcode.Bcode{}). | 		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"). | 		Doc("delete a config"). | ||||||
| 		Metadata(restfulspec.KeyOpenAPITags, tags). | 		Metadata(restfulspec.KeyOpenAPITags, tags). | ||||||
| 		Filter(s.RbacService.CheckPerm("configType/config", "delete")). | 		Filter(s.RbacService.CheckPerm("config", "delete")). | ||||||
| 		Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")). | 		Param(ws.PathParameter("configName", "identifier of the config").DataType("string")). | ||||||
| 		Param(ws.PathParameter("name", "identifier of the config").DataType("string")). |  | ||||||
| 		Returns(200, "OK", apis.EmptyResponse{}). | 		Returns(200, "OK", apis.EmptyResponse{}). | ||||||
| 		Returns(400, "Bad Request", bcode.Bcode{}). | 		Returns(400, "Bad Request", bcode.Bcode{}). | ||||||
| 		Returns(404, "Not Found", bcode.Bcode{}). | 		Returns(404, "Not Found", bcode.Bcode{}). | ||||||
|  | @ -107,21 +96,65 @@ func (s *configAPIInterface) GetWebServiceRoute() *restful.WebService { | ||||||
| 	return ws | 	return ws | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *configAPIInterface) listConfigTypes(req *restful.Request, res *restful.Response) { | // ConfigTemplateAPIInterface returns config web service
 | ||||||
| 	types, err := s.ConfigService.ListConfigTypes(req.Request.Context(), req.QueryParameter("query")) | func ConfigTemplateAPIInterface() Interface { | ||||||
| 	if len(types) == 0 && err != nil { | 	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) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	err = res.WriteEntity(types) | 	err = res.WriteEntity(apis.ListConfigTemplateResponse{Templates: templates}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *configAPIInterface) getConfigType(req *restful.Request, res *restful.Response) { | func (s *configTemplateAPIInterface) getConfigTemplate(req *restful.Request, res *restful.Response) { | ||||||
| 	t, err := s.ConfigService.GetConfigType(req.Request.Context(), req.PathParameter("configType")) | 	t, err := s.ConfigService.GetTemplate(req.Request.Context(), config.NamespacedName{ | ||||||
|  | 		Name:      req.PathParameter("templateName"), | ||||||
|  | 		Namespace: req.QueryParameter("namespace"), | ||||||
|  | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
|  | @ -145,24 +178,47 @@ func (s *configAPIInterface) createConfig(req *restful.Request, res *restful.Res | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err := s.ConfigService.CreateConfig(req.Request.Context(), createReq) | 	config, err := s.ConfigService.CreateConfig(req.Request.Context(), "", createReq) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		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) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *configAPIInterface) getConfigs(req *restful.Request, res *restful.Response) { | 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 { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	err = res.WriteEntity(configs) | 	err = res.WriteEntity(apis.ListConfigResponse{Configs: configs}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		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) { | 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 { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		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) { | 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 { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ import ( | ||||||
| 	registryv1 "github.com/google/go-containerregistry/pkg/v1" | 	registryv1 "github.com/google/go-containerregistry/pkg/v1" | ||||||
| 	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" | 	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" | ||||||
| 	"helm.sh/helm/v3/pkg/repo" | 	"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/common" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | 	"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/domain/model" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils" | 	"github.com/oam-dev/kubevela/pkg/apiserver/utils" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/cloudprovider" | 	"github.com/oam-dev/kubevela/pkg/cloudprovider" | ||||||
|  | 	"github.com/oam-dev/kubevela/pkg/config" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -190,27 +192,64 @@ type AddonArgsResponse struct { | ||||||
| 	Args map[string]string `json:"args"` | 	Args map[string]string `json:"args"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ConfigType define the format for listing configuration types
 | // CreateConfigRequest is the request body to creates a config
 | ||||||
| type ConfigType struct { | type CreateConfigRequest struct { | ||||||
| 	Definitions []string `json:"definitions"` | 	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"` | 	Alias       string    `json:"alias"` | ||||||
| 	Name        string    `json:"name"` | 	Name        string    `json:"name"` | ||||||
|  | 	Namespace   string    `json:"namespace"` | ||||||
| 	Description string    `json:"description"` | 	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
 | // Config define the metadata of a config
 | ||||||
| type Config struct { | type Config struct { | ||||||
| 	ConfigType        string                  `json:"configType"` | 	Template    config.NamespacedName         `json:"template"` | ||||||
| 	ConfigTypeAlias   string                  `json:"configTypeAlias"` |  | ||||||
| 	Name        string                        `json:"name"` | 	Name        string                        `json:"name"` | ||||||
|  | 	Namespace   string                        `json:"namespace"` | ||||||
|  | 	Sensitive   bool                          `json:"sensitive"` | ||||||
| 	Project     string                        `json:"project"` | 	Project     string                        `json:"project"` | ||||||
| 	Identifier        string                  `json:"identifier"` |  | ||||||
| 	Alias       string                        `json:"alias"` | 	Alias       string                        `json:"alias"` | ||||||
| 	Description string                        `json:"description"` | 	Description string                        `json:"description"` | ||||||
| 	CreatedTime *time.Time                    `json:"createdTime"` | 	CreatedTime *time.Time                    `json:"createdTime"` | ||||||
| 	UpdatedTime       *time.Time              `json:"updatedTime"` | 	Properties  map[string]interface{}        `json:"properties,omitempty"` | ||||||
| 	ApplicationStatus common.ApplicationPhase `json:"applicationStatus"` | 	Shared      bool                          `json:"shared"` | ||||||
| 	Status            string                  `json:"status"` | 	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
 | // ImageResponse is the response for checking image
 | ||||||
|  | @ -438,16 +477,6 @@ type CreateApplicationRequest struct { | ||||||
| 	Component   *CreateComponentRequest `json:"component"` | 	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
 | // UpdateApplicationRequest update application base config
 | ||||||
| type UpdateApplicationRequest struct { | type UpdateApplicationRequest struct { | ||||||
| 	Alias       string            `json:"alias" validate:"checkalias" optional:"true"` | 	Alias       string            `json:"alias" validate:"checkalias" optional:"true"` | ||||||
|  | @ -1446,6 +1475,7 @@ type ImageRegistry struct { | ||||||
| 	Name       string         `json:"name"` | 	Name       string         `json:"name"` | ||||||
| 	SecretName string         `json:"secretName"` | 	SecretName string         `json:"secretName"` | ||||||
| 	Domain     string         `json:"domain"` | 	Domain     string         `json:"domain"` | ||||||
|  | 	Secret     *corev1.Secret `json:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ListImageRegistryResponse the response struct of listing the image registries
 | // ListImageRegistryResponse the response struct of listing the image registries
 | ||||||
|  | @ -1458,3 +1488,42 @@ type CloudShellPrepareResponse struct { | ||||||
| 	Status  string `json:"status"` | 	Status  string `json:"status"` | ||||||
| 	Message string `json:"message"` | 	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
 | 	// Config management
 | ||||||
| 	RegisterAPIInterface(ConfigAPIInterface()) | 	RegisterAPIInterface(ConfigAPIInterface()) | ||||||
|  | 	RegisterAPIInterface(ConfigTemplateAPIInterface()) | ||||||
| 
 | 
 | ||||||
| 	// Resources
 | 	// Resources
 | ||||||
| 	RegisterAPIInterface(NewClusterAPIInterface()) | 	RegisterAPIInterface(NewClusterAPIInterface()) | ||||||
|  |  | ||||||
|  | @ -23,5 +23,5 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestInitAPIBean(t *testing.T) { | 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" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode" | 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/log" | 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/log" | ||||||
|  | 	"github.com/oam-dev/kubevela/pkg/config" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type projectAPIInterface struct { | type projectAPIInterface struct { | ||||||
| 	RbacService    service.RBACService    `inject:""` | 	RbacService    service.RBACService    `inject:""` | ||||||
| 	ProjectService service.ProjectService `inject:""` | 	ProjectService service.ProjectService `inject:""` | ||||||
| 	TargetService  service.TargetService  `inject:""` | 	TargetService  service.TargetService  `inject:""` | ||||||
|  | 	ConfigService  service.ConfigService  `inject:""` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewProjectAPIInterface new project APIInterface
 | // NewProjectAPIInterface new project APIInterface
 | ||||||
|  | @ -193,15 +195,115 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService { | ||||||
| 		Returns(200, "OK", []apis.PermissionBase{}). | 		Returns(200, "OK", []apis.PermissionBase{}). | ||||||
| 		Writes([]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). | 	ws.Route(ws.GET("/{projectName}/configs").To(n.getConfigs). | ||||||
| 		Doc("get configs which are in a project"). | 		Doc("get configs which are in a project"). | ||||||
| 		Metadata(restfulspec.KeyOpenAPITags, tags). | 		Metadata(restfulspec.KeyOpenAPITags, tags). | ||||||
| 		Filter(n.RbacService.CheckPerm("project/config", "list")). | 		Filter(n.RbacService.CheckPerm("project/config", "list")). | ||||||
| 		Param(ws.QueryParameter("configType", "config type").DataType("string")). | 		Param(ws.QueryParameter("template", "the template name").DataType("string")). | ||||||
| 		Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")). | 		Param(ws.PathParameter("projectName", "identifier of the project").DataType("string").Required(true)). | ||||||
| 		Returns(200, "OK", []*apis.Config{}). | 		Returns(200, "OK", apis.ListConfigResponse{}). | ||||||
| 		Returns(400, "Bad Request", bcode.Bcode{}). | 		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) | 	ws.Filter(authCheckFilter) | ||||||
| 	return ws | 	return ws | ||||||
|  | @ -570,20 +672,179 @@ func (n *projectAPIInterface) deleteProjectPermission(req *restful.Request, res | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Response) { | func (n *projectAPIInterface) getConfigTemplates(req *restful.Request, res *restful.Response) { | ||||||
| 	configs, err := n.ProjectService.GetConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("configType")) | 	templates, err := n.ConfigService.ListTemplates(req.Request.Context(), req.PathParameter("projectName"), "project") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if configs == nil { | 	err = res.WriteEntity(apis.ListConfigTemplateResponse{Templates: templates}) | ||||||
| 		if err := res.WriteEntity(apis.EmptyResponse{}); err != nil { | 	if err != nil { | ||||||
| 			bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 			return | 		return | ||||||
| 		} | 	} | ||||||
| 		return | } | ||||||
| 	} | 
 | ||||||
| 	err = res.WriteEntity(configs) | func (n *projectAPIInterface) getConfigTemplate(req *restful.Request, res *restful.Response) { | ||||||
|  | 	t, err := n.ConfigService.GetTemplate(req.Request.Context(), config.NamespacedName{ | ||||||
|  | 		Name:      req.PathParameter("templateName"), | ||||||
|  | 		Namespace: req.QueryParameter("namespace"), | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(t) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Response) { | ||||||
|  | 	configs, err := n.ConfigService.ListConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("template"), false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(apis.ListConfigResponse{Configs: configs}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) createConfig(req *restful.Request, res *restful.Response) { | ||||||
|  | 	// Verify the validity of parameters
 | ||||||
|  | 	var createReq apis.CreateConfigRequest | ||||||
|  | 	if err := req.ReadEntity(&createReq); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := validate.Struct(&createReq); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	config, err := n.ConfigService.CreateConfig(req.Request.Context(), req.PathParameter("projectName"), createReq) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) updateConfig(req *restful.Request, res *restful.Response) { | ||||||
|  | 	// Verify the validity of parameters
 | ||||||
|  | 	var updateReq apis.UpdateConfigRequest | ||||||
|  | 	if err := req.ReadEntity(&updateReq); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := validate.Struct(&updateReq); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	config, err := n.ConfigService.UpdateConfig(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("configName"), updateReq) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) detailConfig(req *restful.Request, res *restful.Response) { | ||||||
|  | 	config, err := n.ConfigService.GetConfig(req.Request.Context(), | ||||||
|  | 		req.PathParameter("projectName"), req.PathParameter("configName")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) deleteConfig(req *restful.Request, res *restful.Response) { | ||||||
|  | 	err := n.ConfigService.DeleteConfig(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("configName")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(apis.EmptyResponse{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) getProviders(req *restful.Request, res *restful.Response) { | ||||||
|  | 	providers, err := n.ProjectService.ListTerraformProviders(req.Request.Context(), req.PathParameter("projectName")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(apis.ListTerraformProviderResponse{Providers: providers}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) applyDistribution(req *restful.Request, res *restful.Response) { | ||||||
|  | 	// Verify the validity of parameters
 | ||||||
|  | 	var createReq apis.CreateConfigDistributionRequest | ||||||
|  | 	if err := req.ReadEntity(&createReq); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := validate.Struct(&createReq); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// Call the domain layer code
 | ||||||
|  | 	err := n.ConfigService.CreateConfigDistribution(req.Request.Context(), req.PathParameter("projectName"), createReq) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Write back response data
 | ||||||
|  | 	if err := res.WriteEntity(apis.EmptyResponse{}); err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) listDistributions(req *restful.Request, res *restful.Response) { | ||||||
|  | 	distributions, err := n.ConfigService.ListConfigDistributions(req.Request.Context(), req.PathParameter("projectName")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(apis.ListConfigDistributionResponse{Distributions: distributions}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *projectAPIInterface) deleteDistribution(req *restful.Request, res *restful.Response) { | ||||||
|  | 	err := n.ConfigService.DeleteConfigDistribution(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("distributionName")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		bcode.ReturnError(req, res, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err = res.WriteEntity(apis.EmptyResponse{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		bcode.ReturnError(req, res, err) | 		bcode.ReturnError(req, res, err) | ||||||
| 		return | 		return | ||||||
|  |  | ||||||
|  | @ -42,6 +42,7 @@ import ( | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils" | 	"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/container" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/apiserver/utils/log" | 	"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" | 	pkgUtils "github.com/oam-dev/kubevela/pkg/utils" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/apply" | 	"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) | 		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
 | 	// domain
 | ||||||
| 	if err := s.beanContainer.Provides(service.InitServiceBean(s.cfg)...); err != nil { | 	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) | 		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" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 
 | 
 | ||||||
| 	"github.com/oam-dev/kubevela/apis/types" | 	"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
 | // 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 { | 	for i, s := range secrets.Items { | ||||||
| 		var data map[string]interface{} | 		var data map[string]interface{} | ||||||
| 		key := s.Labels[types.LabelConfigSubType] | 		key := s.Labels[types.LabelConfigSubType] | ||||||
|  | 		if _, ok := s.Data[key]; ok { | ||||||
| 			err := json.Unmarshal(s.Data[key], &data) | 			err := json.Unmarshal(s.Data[key], &data) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			return nil, err | 				log.Logger.Warnf("the dex connector %s is invalid", s.Name) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
| 			connectors[i] = map[string]interface{}{ | 			connectors[i] = map[string]interface{}{ | ||||||
| 				"type":   s.Labels[types.LabelConfigSubType], | 				"type":   s.Labels[types.LabelConfigSubType], | ||||||
|  | @ -47,6 +51,7 @@ func GetDexConnectors(ctx context.Context, k8sClient client.Client) ([]map[strin | ||||||
| 				"config": data, | 				"config": data, | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return connectors, nil | 	return connectors, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ func TestGetDexConnectors(t *testing.T) { | ||||||
| 			Labels: map[string]string{ | 			Labels: map[string]string{ | ||||||
| 				"app.oam.dev/source-of-truth": "from-inner-system", | 				"app.oam.dev/source-of-truth": "from-inner-system", | ||||||
| 				"config.oam.dev/catalog":      "velacore-config", | 				"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", | 				"config.oam.dev/sub-type":     "ldap", | ||||||
| 				"project":                     "abc", | 				"project":                     "abc", | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
|  | @ -137,3 +137,21 @@ func (f *deferredFactory) Client() client.Client { | ||||||
| 	} | 	} | ||||||
| 	return f.Factory.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/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"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/common" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | ||||||
|  | @ -48,7 +46,6 @@ import ( | ||||||
| type Reconciler struct { | type Reconciler struct { | ||||||
| 	client.Client | 	client.Client | ||||||
| 	dm     discoverymapper.DiscoveryMapper | 	dm     discoverymapper.DiscoveryMapper | ||||||
| 	pd     *packages.PackageDiscover |  | ||||||
| 	Scheme *runtime.Scheme | 	Scheme *runtime.Scheme | ||||||
| 	record event.Recorder | 	record event.Recorder | ||||||
| 	options | 	options | ||||||
|  | @ -94,7 +91,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu | ||||||
| 
 | 
 | ||||||
| 	def := utils.NewCapabilityComponentDef(&componentDefinition) | 	def := utils.NewCapabilityComponentDef(&componentDefinition) | ||||||
| 	// Store the parameter of componentDefinition to configMap
 | 	// 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 { | 	if err != nil { | ||||||
| 		klog.InfoS("Could not capability in ConfigMap", "err", err) | 		klog.InfoS("Could not capability in ConfigMap", "err", err) | ||||||
| 		r.record.Event(&(componentDefinition), event.Warning("Could not store capability in ConfigMap", 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(), | 		Client:  mgr.GetClient(), | ||||||
| 		Scheme:  mgr.GetScheme(), | 		Scheme:  mgr.GetScheme(), | ||||||
| 		dm:      args.DiscoveryMapper, | 		dm:      args.DiscoveryMapper, | ||||||
| 		pd:      args.PackageDiscover, |  | ||||||
| 		options: parseOptions(args), | 		options: parseOptions(args), | ||||||
| 	} | 	} | ||||||
| 	return r.SetupWithManager(mgr) | 	return r.SetupWithManager(mgr) | ||||||
|  |  | ||||||
|  | @ -33,8 +33,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/envtest" | 	"sigs.k8s.io/controller-runtime/pkg/envtest" | ||||||
| 
 | 
 | ||||||
| 	"github.com/kubevela/workflow/pkg/cue/packages" |  | ||||||
| 
 |  | ||||||
| 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | ||||||
| ) | ) | ||||||
|  | @ -87,14 +85,11 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 	_, err = dm.Refresh() | 	_, err = dm.Refresh() | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 	pd, err := packages.NewPackageDiscover(cfg) |  | ||||||
| 	Expect(err).ToNot(HaveOccurred()) |  | ||||||
| 
 | 
 | ||||||
| 	r = Reconciler{ | 	r = Reconciler{ | ||||||
| 		Client: mgr.GetClient(), | 		Client: mgr.GetClient(), | ||||||
| 		Scheme: mgr.GetScheme(), | 		Scheme: mgr.GetScheme(), | ||||||
| 		dm:     dm, | 		dm:     dm, | ||||||
| 		pd:     pd, |  | ||||||
| 		options: options{ | 		options: options{ | ||||||
| 			defRevLimit: defRevisionLimit, | 			defRevLimit: defRevisionLimit, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -30,8 +30,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"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/common" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | ||||||
|  | @ -48,7 +46,6 @@ import ( | ||||||
| type Reconciler struct { | type Reconciler struct { | ||||||
| 	client.Client | 	client.Client | ||||||
| 	dm                   discoverymapper.DiscoveryMapper | 	dm                   discoverymapper.DiscoveryMapper | ||||||
| 	pd                   *packages.PackageDiscover |  | ||||||
| 	Scheme               *runtime.Scheme | 	Scheme               *runtime.Scheme | ||||||
| 	record               event.Recorder | 	record               event.Recorder | ||||||
| 	defRevLimit          int | 	defRevLimit          int | ||||||
|  | @ -97,7 +94,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu | ||||||
| 	def := utils.NewCapabilityPolicyDef(&policyDefinition) | 	def := utils.NewCapabilityPolicyDef(&policyDefinition) | ||||||
| 	def.Name = req.NamespacedName.Name | 	def.Name = req.NamespacedName.Name | ||||||
| 	// Store the parameter of policyDefinition to configMap
 | 	// 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 { | 	if err != nil { | ||||||
| 		klog.InfoS("Could not capability in ConfigMap", "err", err) | 		klog.InfoS("Could not capability in ConfigMap", "err", err) | ||||||
| 		r.record.Event(&(policyDefinition), event.Warning("Could not store capability in ConfigMap", 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(), | 		Client:               mgr.GetClient(), | ||||||
| 		Scheme:               mgr.GetScheme(), | 		Scheme:               mgr.GetScheme(), | ||||||
| 		dm:                   args.DiscoveryMapper, | 		dm:                   args.DiscoveryMapper, | ||||||
| 		pd:                   args.PackageDiscover, |  | ||||||
| 		defRevLimit:          args.DefRevisionLimit, | 		defRevLimit:          args.DefRevisionLimit, | ||||||
| 		concurrentReconciles: args.ConcurrentReconciles, | 		concurrentReconciles: args.ConcurrentReconciles, | ||||||
| 		ignoreDefNoCtrlReq:   args.IgnoreDefinitionWithoutControllerRequirement, | 		ignoreDefNoCtrlReq:   args.IgnoreDefinitionWithoutControllerRequirement, | ||||||
|  |  | ||||||
|  | @ -34,8 +34,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/envtest" | 	"sigs.k8s.io/controller-runtime/pkg/envtest" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||||||
| 
 | 
 | ||||||
| 	"github.com/kubevela/workflow/pkg/cue/packages" |  | ||||||
| 
 |  | ||||||
| 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | ||||||
| ) | ) | ||||||
|  | @ -84,8 +82,6 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 	}) | 	}) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 
 | 
 | ||||||
| 	pd, err := packages.NewPackageDiscover(cfg) |  | ||||||
| 	Expect(err).ToNot(HaveOccurred()) |  | ||||||
| 	dm, err := discoverymapper.New(mgr.GetConfig()) | 	dm, err := discoverymapper.New(mgr.GetConfig()) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 	_, err = dm.Refresh() | 	_, err = dm.Refresh() | ||||||
|  | @ -95,7 +91,6 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 		Client:      mgr.GetClient(), | 		Client:      mgr.GetClient(), | ||||||
| 		Scheme:      mgr.GetScheme(), | 		Scheme:      mgr.GetScheme(), | ||||||
| 		dm:          dm, | 		dm:          dm, | ||||||
| 		pd:          pd, |  | ||||||
| 		defRevLimit: defRevisionLimit, | 		defRevLimit: defRevisionLimit, | ||||||
| 	} | 	} | ||||||
| 	Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred()) | 	Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred()) | ||||||
|  |  | ||||||
|  | @ -30,8 +30,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"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/common" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | ||||||
|  | @ -48,7 +46,6 @@ import ( | ||||||
| type Reconciler struct { | type Reconciler struct { | ||||||
| 	client.Client | 	client.Client | ||||||
| 	dm     discoverymapper.DiscoveryMapper | 	dm     discoverymapper.DiscoveryMapper | ||||||
| 	pd     *packages.PackageDiscover |  | ||||||
| 	Scheme *runtime.Scheme | 	Scheme *runtime.Scheme | ||||||
| 	record event.Recorder | 	record event.Recorder | ||||||
| 	options | 	options | ||||||
|  | @ -101,7 +98,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu | ||||||
| 	def := utils.NewCapabilityTraitDef(&traitDefinition) | 	def := utils.NewCapabilityTraitDef(&traitDefinition) | ||||||
| 	def.Name = req.NamespacedName.Name | 	def.Name = req.NamespacedName.Name | ||||||
| 	// Store the parameter of traitDefinition to configMap
 | 	// 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 { | 	if err != nil { | ||||||
| 		klog.InfoS("Could not store capability in ConfigMap", "err", err) | 		klog.InfoS("Could not store capability in ConfigMap", "err", err) | ||||||
| 		r.record.Event(&(traitDefinition), event.Warning("Could not store capability in ConfigMap", 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(), | 		Client:  mgr.GetClient(), | ||||||
| 		Scheme:  mgr.GetScheme(), | 		Scheme:  mgr.GetScheme(), | ||||||
| 		dm:      args.DiscoveryMapper, | 		dm:      args.DiscoveryMapper, | ||||||
| 		pd:      args.PackageDiscover, |  | ||||||
| 		options: parseOptions(args), | 		options: parseOptions(args), | ||||||
| 	} | 	} | ||||||
| 	return r.SetupWithManager(mgr) | 	return r.SetupWithManager(mgr) | ||||||
|  |  | ||||||
|  | @ -33,8 +33,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/envtest" | 	"sigs.k8s.io/controller-runtime/pkg/envtest" | ||||||
| 
 | 
 | ||||||
| 	"github.com/kubevela/workflow/pkg/cue/packages" |  | ||||||
| 
 |  | ||||||
| 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | ||||||
| ) | ) | ||||||
|  | @ -83,8 +81,6 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 	}) | 	}) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 
 | 
 | ||||||
| 	pd, err := packages.NewPackageDiscover(cfg) |  | ||||||
| 	Expect(err).ToNot(HaveOccurred()) |  | ||||||
| 	dm, err := discoverymapper.New(mgr.GetConfig()) | 	dm, err := discoverymapper.New(mgr.GetConfig()) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 	_, err = dm.Refresh() | 	_, err = dm.Refresh() | ||||||
|  | @ -94,7 +90,6 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 		Client: mgr.GetClient(), | 		Client: mgr.GetClient(), | ||||||
| 		Scheme: mgr.GetScheme(), | 		Scheme: mgr.GetScheme(), | ||||||
| 		dm:     dm, | 		dm:     dm, | ||||||
| 		pd:     pd, |  | ||||||
| 		options: options{ | 		options: options{ | ||||||
| 			defRevLimit: defRevisionLimit, | 			defRevLimit: defRevisionLimit, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -33,8 +33,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/envtest" | 	"sigs.k8s.io/controller-runtime/pkg/envtest" | ||||||
| 
 | 
 | ||||||
| 	"github.com/kubevela/workflow/pkg/cue/packages" |  | ||||||
| 
 |  | ||||||
| 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | 	oamCore "github.com/oam-dev/kubevela/apis/core.oam.dev" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | 	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper" | ||||||
| ) | ) | ||||||
|  | @ -83,8 +81,6 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 	}) | 	}) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 
 | 
 | ||||||
| 	pd, err := packages.NewPackageDiscover(cfg) |  | ||||||
| 	Expect(err).ToNot(HaveOccurred()) |  | ||||||
| 	dm, err := discoverymapper.New(mgr.GetConfig()) | 	dm, err := discoverymapper.New(mgr.GetConfig()) | ||||||
| 	Expect(err).ToNot(HaveOccurred()) | 	Expect(err).ToNot(HaveOccurred()) | ||||||
| 	_, err = dm.Refresh() | 	_, err = dm.Refresh() | ||||||
|  | @ -94,7 +90,6 @@ var _ = BeforeSuite(func(done Done) { | ||||||
| 		Client: mgr.GetClient(), | 		Client: mgr.GetClient(), | ||||||
| 		Scheme: mgr.GetScheme(), | 		Scheme: mgr.GetScheme(), | ||||||
| 		dm:     dm, | 		dm:     dm, | ||||||
| 		pd:     pd, |  | ||||||
| 		options: options{ | 		options: options{ | ||||||
| 			defRevLimit: defRevisionLimit, | 			defRevLimit: defRevisionLimit, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -30,8 +30,6 @@ import ( | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"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/common" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition" | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | 	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" | ||||||
|  | @ -48,7 +46,6 @@ import ( | ||||||
| type Reconciler struct { | type Reconciler struct { | ||||||
| 	client.Client | 	client.Client | ||||||
| 	dm     discoverymapper.DiscoveryMapper | 	dm     discoverymapper.DiscoveryMapper | ||||||
| 	pd     *packages.PackageDiscover |  | ||||||
| 	Scheme *runtime.Scheme | 	Scheme *runtime.Scheme | ||||||
| 	record event.Recorder | 	record event.Recorder | ||||||
| 	options | 	options | ||||||
|  | @ -101,7 +98,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu | ||||||
| 	def := utils.NewCapabilityStepDef(&wfStepDefinition) | 	def := utils.NewCapabilityStepDef(&wfStepDefinition) | ||||||
| 	def.Name = req.NamespacedName.Name | 	def.Name = req.NamespacedName.Name | ||||||
| 	// Store the parameter of stepDefinition to configMap
 | 	// 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 { | 	if err != nil { | ||||||
| 		klog.InfoS("Could not store capability in ConfigMap", "err", err) | 		klog.InfoS("Could not store capability in ConfigMap", "err", err) | ||||||
| 		r.record.Event(&(wfStepDefinition), event.Warning("Could not store capability in ConfigMap", 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(), | 		Client:  mgr.GetClient(), | ||||||
| 		Scheme:  mgr.GetScheme(), | 		Scheme:  mgr.GetScheme(), | ||||||
| 		dm:      args.DiscoveryMapper, | 		dm:      args.DiscoveryMapper, | ||||||
| 		pd:      args.PackageDiscover, |  | ||||||
| 		options: parseOptions(args), | 		options: parseOptions(args), | ||||||
| 	} | 	} | ||||||
| 	return r.SetupWithManager(mgr) | 	return r.SetupWithManager(mgr) | ||||||
|  |  | ||||||
|  | @ -22,10 +22,8 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"regexp" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"cuelang.org/go/cue/cuecontext" |  | ||||||
| 	"github.com/getkin/kin-openapi/openapi3" | 	"github.com/getkin/kin-openapi/openapi3" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	"gopkg.in/src-d/go-git.v4" | 	"gopkg.in/src-d/go-git.v4" | ||||||
|  | @ -36,16 +34,12 @@ import ( | ||||||
| 	"k8s.io/utils/pointer" | 	"k8s.io/utils/pointer" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"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" | 	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/core.oam.dev/v1beta1" | ||||||
| 	"github.com/oam-dev/kubevela/apis/types" | 	"github.com/oam-dev/kubevela/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/appfile" | 	"github.com/oam-dev/kubevela/pkg/appfile" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/appfile/helm" | 	"github.com/oam-dev/kubevela/pkg/appfile/helm" | ||||||
| 	velacue "github.com/oam-dev/kubevela/pkg/cue" | 	"github.com/oam-dev/kubevela/pkg/cue/script" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/cue/process" |  | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam/util" | 	"github.com/oam-dev/kubevela/pkg/oam/util" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/common" | 	"github.com/oam-dev/kubevela/pkg/utils/common" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/terraform" | 	"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
 | // 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) | 	capability, err := appfile.ConvertTemplateJSON2Object(name, def.ComponentDefinition.Spec.Extension, def.ComponentDefinition.Spec.Schematic) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to convert ComponentDefinition to Capability Object") | 		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
 | // 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
 | // StoreOpenAPISchema stores OpenAPI v3 schema in ConfigMap from WorkloadDefinition
 | ||||||
| func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, | func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name, revName string) (string, error) { | ||||||
| 	pd *packages.PackageDiscover, namespace, name, revName string) (string, error) { |  | ||||||
| 	var jsonSchema []byte | 	var jsonSchema []byte | ||||||
| 	var err error | 	var err error | ||||||
| 	switch def.WorkloadType { | 	switch def.WorkloadType { | ||||||
|  | @ -376,7 +369,7 @@ func (def *CapabilityComponentDefinition) StoreOpenAPISchema(ctx context.Context | ||||||
| 		} | 		} | ||||||
| 		jsonSchema, err = GetOpenAPISchemaFromTerraformComponentDefinition(configuration) | 		jsonSchema, err = GetOpenAPISchemaFromTerraformComponentDefinition(configuration) | ||||||
| 	default: | 	default: | ||||||
| 		jsonSchema, err = def.GetOpenAPISchema(pd, name) | 		jsonSchema, err = def.GetOpenAPISchema(name) | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err) | 		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
 | // 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) | 	capability, err := appfile.ConvertTemplateJSON2Object(name, def.TraitDefinition.Spec.Extension, def.TraitDefinition.Spec.Schematic) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to convert WorkloadDefinition to Capability Object") | 		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
 | // 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 jsonSchema []byte | ||||||
| 	var err error | 	var err error | ||||||
| 	switch def.DefCategoryType { | 	switch def.DefCategoryType { | ||||||
| 	case util.KubeDef: // Kube template
 | 	case util.KubeDef: // Kube template
 | ||||||
| 		jsonSchema, err = GetKubeSchematicOpenAPISchema(def.Kube.Parameters) | 		jsonSchema, err = GetKubeSchematicOpenAPISchema(def.Kube.Parameters) | ||||||
| 	default: // CUE  template
 | 	default: // CUE  template
 | ||||||
| 		jsonSchema, err = def.GetOpenAPISchema(pd, name) | 		jsonSchema, err = def.GetOpenAPISchema(name) | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err) | 		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
 | // 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) | 	capability, err := appfile.ConvertTemplateJSON2Object(name, nil, def.StepDefinition.Spec.Schematic) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to convert WorkflowStepDefinition to Capability Object") | 		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
 | // 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 jsonSchema []byte | ||||||
| 	var err error | 	var err error | ||||||
| 
 | 
 | ||||||
| 	jsonSchema, err = def.GetOpenAPISchema(pd, name) | 	jsonSchema, err = def.GetOpenAPISchema(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err) | 		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
 | // 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) | 	capability, err := appfile.ConvertTemplateJSON2Object(name, nil, def.PolicyDefinition.Spec.Schematic) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to convert WorkflowStepDefinition to Capability Object") | 		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
 | // StoreOpenAPISchema stores OpenAPI v3 schema from StepDefinition in ConfigMap
 | ||||||
| func (def *CapabilityPolicyDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, | func (def *CapabilityPolicyDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, namespace, name, revName string) (string, error) { | ||||||
| 	pd *packages.PackageDiscover, namespace, name, revName string) (string, error) { |  | ||||||
| 	var jsonSchema []byte | 	var jsonSchema []byte | ||||||
| 	var err error | 	var err error | ||||||
| 
 | 
 | ||||||
| 	jsonSchema, err = def.GetOpenAPISchema(pd, name) | 	jsonSchema, err = def.GetOpenAPISchema(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err) | 		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
 | // getOpenAPISchema is the main function for GetDefinition API
 | ||||||
| func getOpenAPISchema(capability types.Capability, pd *packages.PackageDiscover) ([]byte, error) { | func getOpenAPISchema(capability types.Capability) ([]byte, error) { | ||||||
| 	openAPISchema, err := generateOpenAPISchemaFromCapabilityParameter(capability, pd) | 	cueTemplate, err := script.PrepareTemplateCUEScript([]byte(capability.CueTemplate)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	schema, err := ConvertOpenAPISchema2SwaggerObject(openAPISchema) | 	schema, err := cueTemplate.ParsePropertiesToSchema() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	FixOpenAPISchema("", schema) |  | ||||||
| 
 |  | ||||||
| 	parameter, err := schema.MarshalJSON() | 	parameter, err := schema.MarshalJSON() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return parameter, nil | 	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()} | 			def := &CapabilityComponentDefinition{Name: componentDefinitionName, ComponentDefinition: *componentDefinition.DeepCopy()} | ||||||
| 
 | 
 | ||||||
| 			By("Test GetOpenAPISchema") | 			By("Test GetOpenAPISchema") | ||||||
| 			schema, err := def.GetOpenAPISchema(pd, namespace) | 			schema, err := def.GetOpenAPISchema(namespace) | ||||||
| 			Expect(err).Should(BeNil()) | 			Expect(err).Should(BeNil()) | ||||||
| 			Expect(schema).Should(Not(BeNil())) | 			Expect(schema).Should(Not(BeNil())) | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -19,20 +19,16 @@ package utils | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/crossplane/crossplane-runtime/pkg/test" | 	"github.com/crossplane/crossplane-runtime/pkg/test" | ||||||
| 	"github.com/getkin/kin-openapi/openapi3" |  | ||||||
| 	"github.com/google/go-cmp/cmp" | 	"github.com/google/go-cmp/cmp" | ||||||
| 	"gotest.tools/assert" | 	"gotest.tools/assert" | ||||||
| 
 | 
 | ||||||
| 	"github.com/oam-dev/kubevela/apis/core.oam.dev/common" | 	"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/core.oam.dev/v1beta1" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/appfile" | 	"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/oam/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -182,7 +178,7 @@ parameter: [string]: string | ||||||
| 				}, | 				}, | ||||||
| 			} | 			} | ||||||
| 			capability, _ := appfile.ConvertTemplateJSON2Object(tc.name, nil, schematic) | 			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 != "" { | 			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) | 				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) { | func TestNewCapabilityComponentDef(t *testing.T) { | ||||||
| 	terraform := &common.Terraform{ | 	terraform := &common.Terraform{ | ||||||
| 		Configuration: "test", | 		Configuration: "test", | ||||||
|  |  | ||||||
|  | @ -24,5 +24,6 @@ context: { | ||||||
|     name: string |     name: string | ||||||
|     value: string |     value: string | ||||||
|   }] |   }] | ||||||
|  |   ... | ||||||
| } | } | ||||||
| ` | ` | ||||||
|  |  | ||||||
|  | @ -158,3 +158,8 @@ func RetrieveComments(value cue.Value) (string, string, string, bool) { | ||||||
| 	} | 	} | ||||||
| 	return short, usage, alias, ignore | 	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 | #SendEmail: email.#Send | ||||||
| 
 | 
 | ||||||
|  | #CreateConfig: config.#Create | ||||||
|  | #DeleteConfig: config.#Delete | ||||||
|  | #ReadConfig:   config.#Read | ||||||
|  | #ListConfig:   config.#List | ||||||
|  | 
 | ||||||
| #Load: oam.#LoadComponets | #Load: oam.#LoadComponets | ||||||
| 
 | 
 | ||||||
| #LoadInOrder: oam.#LoadComponetsInOrder | #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" | 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||||
| 	"k8s.io/klog/v2" | 	"k8s.io/klog/v2" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"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/apis/core.oam.dev/v1beta1" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/controller/utils" | 	"github.com/oam-dev/kubevela/pkg/controller/utils" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/features" | 	"github.com/oam-dev/kubevela/pkg/features" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam" | 	"github.com/oam-dev/kubevela/pkg/oam" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/oam/util" | 	"github.com/oam-dev/kubevela/pkg/oam/util" | ||||||
|  | 	"github.com/oam-dev/kubevela/pkg/utils/common" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -58,6 +60,7 @@ type applyAction struct { | ||||||
| 	skipUpdate       bool | 	skipUpdate       bool | ||||||
| 	updateAnnotation bool | 	updateAnnotation bool | ||||||
| 	dryRun           bool | 	dryRun           bool | ||||||
|  | 	quiet            bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ApplyOption is called before applying state to the object.
 | // 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
 | // 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) | 	d, ok := desired.(metav1.Object) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		klog.InfoS(msg, "resource", desired.GetObjectKind().GroupVersionKind().String()) | 		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 { | 	if applyAct.skipUpdate { | ||||||
| 		loggingApply("skip update", desired) | 		loggingApply("skip update", desired, applyAct.quiet) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch { | 	switch { | ||||||
| 	case utilfeature.DefaultMutableFeatureGate.Enabled(features.ApplyResourceByUpdate) && isUpdatableResource(desired): | 	case utilfeature.DefaultMutableFeatureGate.Enabled(features.ApplyResourceByUpdate) && isUpdatableResource(desired): | ||||||
| 		loggingApply("updating object", desired) | 		loggingApply("updating object", desired, applyAct.quiet) | ||||||
| 		desired.SetResourceVersion(existing.GetResourceVersion()) | 		desired.SetResourceVersion(existing.GetResourceVersion()) | ||||||
| 		var options []client.UpdateOption | 		var options []client.UpdateOption | ||||||
| 		if applyAct.dryRun { | 		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") | 		return errors.Wrapf(a.c.Update(ctx, desired, options...), "cannot update object") | ||||||
| 	default: | 	default: | ||||||
| 		loggingApply("patching object", desired) | 		loggingApply("patching object", desired, applyAct.quiet) | ||||||
| 		patch, err := a.patcher.patch(existing, desired, applyAct) | 		patch, err := a.patcher.patch(existing, desired, applyAct) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return errors.Wrap(err, "cannot calculate patch by computing a three way diff") | 			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 | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		loggingApply("creating object", desired) | 		loggingApply("creating object", desired, act.quiet) | ||||||
| 		if act.dryRun { | 		if act.dryRun { | ||||||
| 			return nil, errors.Wrap(c.Create(ctx, desired, client.DryRunAll), "cannot create object") | 			return nil, errors.Wrap(c.Create(ctx, desired, client.DryRunAll), "cannot create object") | ||||||
| 		} | 		} | ||||||
| 		return nil, errors.Wrap(c.Create(ctx, desired), "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
 | 	// allow to create object with only generateName
 | ||||||
| 	if desired.GetName() == "" && desired.GetGenerateName() != "" { | 	if desired.GetName() == "" && desired.GetGenerateName() != "" { | ||||||
| 		return create() | 		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
 | // isUpdatableResource check whether the resource is updatable
 | ||||||
| // Resource like v1.Service cannot unset the spec field (the ip spec is filled by service controller)
 | // Resource like v1.Service cannot unset the spec field (the ip spec is filled by service controller)
 | ||||||
| func isUpdatableResource(desired client.Object) bool { | func isUpdatableResource(desired client.Object) bool { | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"runtime/debug" | ||||||
| 
 | 
 | ||||||
| 	"cuelang.org/go/cue" | 	"cuelang.org/go/cue" | ||||||
| 	"cuelang.org/go/cue/cuecontext" | 	"cuelang.org/go/cue/cuecontext" | ||||||
|  | @ -259,6 +260,7 @@ func GenOpenAPI(val cue.Value) (b []byte, err error) { | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		if r := recover(); r != nil { | 		if r := recover(); r != nil { | ||||||
| 			err = fmt.Errorf("invalid cue definition to generate open api: %v", r) | 			err = fmt.Errorf("invalid cue definition to generate open api: %v", r) | ||||||
|  | 			debug.PrintStack() | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  | @ -293,7 +295,7 @@ func RefineParameterValue(val cue.Value) (cue.Value, error) { | ||||||
| 	case cue.BottomKind: | 	case cue.BottomKind: | ||||||
| 		paramOnlyStr = fmt.Sprintf("#%s: {}", process.ParameterFieldName) | 		paramOnlyStr = fmt.Sprintf("#%s: {}", process.ParameterFieldName) | ||||||
| 	default: | 	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) | 	paramOnlyVal := cuecontext.New().CompileString(paramOnlyStr) | ||||||
| 	if paramOnlyVal.Err() != nil { | 	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), | 		AuthCommandGroup(f, ioStream), | ||||||
| 		KubeCommandGroup(f, ioStream), | 		KubeCommandGroup(f, ioStream), | ||||||
| 
 | 
 | ||||||
|  | 		// Config management
 | ||||||
|  | 		ConfigCommandGroup(f, ioStream), | ||||||
|  | 		TemplateCommandGroup(f, ioStream), | ||||||
|  | 
 | ||||||
| 		// System
 | 		// System
 | ||||||
| 		NewInstallCommand(commandArgs, "1", ioStream), | 		NewInstallCommand(commandArgs, "1", ioStream), | ||||||
| 		NewUnInstallCommand(commandArgs, "2", 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 ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/gosuri/uitable" | 	"github.com/gosuri/uitable" | ||||||
| 	tcv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1" | 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | ||||||
| 	"github.com/pkg/errors" |  | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 	kerrors "k8s.io/apimachinery/pkg/api/errors" |  | ||||||
| 	k8stypes "k8s.io/apimachinery/pkg/types" |  | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"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/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/common" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/config" |  | ||||||
| 	cmdutil "github.com/oam-dev/kubevela/pkg/utils/util" | 	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
 | // NewProviderCommand create `addon` command
 | ||||||
|  | // +Deprecated
 | ||||||
| func NewProviderCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command { | func NewProviderCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command { | ||||||
| 	cmd := &cobra.Command{ | 	cmd := &cobra.Command{ | ||||||
| 		Use:   "provider", | 		Use:   "provider", | ||||||
|  | @ -61,8 +44,8 @@ func NewProviderCommand(c common.Args, order string, ioStreams cmdutil.IOStreams | ||||||
| 	cmd.AddCommand( | 	cmd.AddCommand( | ||||||
| 		NewProviderListCommand(c, ioStreams), | 		NewProviderListCommand(c, ioStreams), | ||||||
| 	) | 	) | ||||||
| 	cmd.AddCommand(prepareProviderAddCommand(c, ioStreams)) | 	cmd.AddCommand(prepareProviderAddCommand()) | ||||||
| 	cmd.AddCommand(prepareProviderDeleteCommand(c, ioStreams)) | 	cmd.AddCommand(prepareProviderDeleteCommand()) | ||||||
| 	return cmd | 	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{ | 	cmd := &cobra.Command{ | ||||||
| 		Use:        "add", | 		Use:        "add", | ||||||
| 		Short:   "Authenticate Terraform Cloud Provider", | 		Deprecated: "Please use the vela config command: \n vela config create <name> --template <provider-type> [Properties]", | ||||||
| 		Long:    "Authenticate Terraform Cloud Provider by creating a credential secret and a Terraform Controller Provider", |  | ||||||
| 		Example: "vela provider add <provider-type>", |  | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	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 | 	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 { | func listProviders(ctx context.Context, k8sClient client.Client, ioStreams cmdutil.IOStreams) error { | ||||||
| 	var ( | 	l := &terraformapi.ProviderList{} | ||||||
| 		providers        []ProviderMeta | 	if err := k8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil { | ||||||
| 		currentProviders []tcv1beta1.Provider | 		return err | ||||||
| 		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") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	table := uitable.New() | 	table := uitable.New() | ||||||
| 	table.AddRow("TYPE", "NAME", "CREATED-TIME") | 	table.AddRow("TYPE", "PROVIDER", "NAME", "REGION", "CREATED-TIME") | ||||||
| 
 | 
 | ||||||
| 	for _, p := range providers { | 	for _, p := range l.Items { | ||||||
| 		table.AddRow(p.Type, p.Name, p.Age) | 		table.AddRow(p.Labels["config.oam.dev/provider"], p.Spec.Provider, p.Name, p.Spec.Region, p.CreationTimestamp) | ||||||
| 	} | 	} | ||||||
| 	ioStreams.Info(table.String()) | 	ioStreams.Info(table.String()) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // getTerraformProviderTypes retrieves all ComponentDefinition for Terraform Cloud Providers which are delivered by
 | func prepareProviderDeleteCommand() *cobra.Command { | ||||||
| // 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 { |  | ||||||
| 	cmd := &cobra.Command{ | 	cmd := &cobra.Command{ | ||||||
| 		Use:        "delete", | 		Use:        "delete", | ||||||
| 		Aliases:    []string{"rm", "del"}, | 		Aliases:    []string{"rm", "del"}, | ||||||
| 		Short:   "Delete Terraform Cloud Provider", | 		Deprecated: "Please use the vela config command: \n  vela config delete <provider-name>", | ||||||
| 		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 |  | ||||||
| 	} | 	} | ||||||
| 	return cmd | 	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" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | 	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" | ||||||
| 	"github.com/pkg/errors" |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/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/core.oam.dev/v1beta1" | ||||||
| 	"github.com/oam-dev/kubevela/apis/types" | 	"github.com/oam-dev/kubevela/apis/types" | ||||||
| 	"github.com/oam-dev/kubevela/pkg/definition" |  | ||||||
| 	"github.com/oam-dev/kubevela/pkg/utils/util" | 	"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" | 	"strconv" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| "config-image-registry": { | metadata: { | ||||||
| 	type: "component" | 	name:        "image-registry" | ||||||
| 	annotations: { | 	alias:       "Image Registry" | ||||||
| 		"alias.config.oam.dev": "Image Registry" | 	scope:       "project" | ||||||
| 	} |  | ||||||
| 	labels: { |  | ||||||
| 		"catalog.config.oam.dev":       "velacore-config" |  | ||||||
| 		"type.config.oam.dev":          "image-registry" |  | ||||||
| 		"multi-cluster.config.oam.dev": "true" |  | ||||||
| 		"ui-hidden":                    "true" |  | ||||||
| 	} |  | ||||||
| 	description: "Config information to authenticate image registry" | 	description: "Config information to authenticate image registry" | ||||||
| 	attributes: workload: type: "autodetects.core.oam.dev" | 	sensitive:   false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template: { | template: { | ||||||
|  | @ -29,9 +22,6 @@ template: { | ||||||
| 			labels: { | 			labels: { | ||||||
| 				"config.oam.dev/catalog": "velacore-config" | 				"config.oam.dev/catalog": "velacore-config" | ||||||
| 				"config.oam.dev/type":    "image-registry" | 				"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 != _|_ { | 		if parameter.auth != _|_ { | ||||||
|  | @ -43,7 +33,7 @@ template: { | ||||||
| 		stringData: { | 		stringData: { | ||||||
| 			if parameter.auth != _|_ && parameter.auth.username != _|_ { | 			if parameter.auth != _|_ && parameter.auth.username != _|_ { | ||||||
| 				".dockerconfigjson": json.Marshal({ | 				".dockerconfigjson": json.Marshal({ | ||||||
| 					"auths": (parameter.registry): { | 					"auths": "\(parameter.registry)": { | ||||||
| 						"username": parameter.auth.username | 						"username": parameter.auth.username | ||||||
| 						"password": parameter.auth.password | 						"password": parameter.auth.password | ||||||
| 						if parameter.auth.email != _|_ { | 						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