github.com/oam-dev/kubevela@v1.9.11/pkg/config/factory.go (about)

     1  /*
     2  Copyright 2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	"cuelang.org/go/cue"
    28  	"github.com/getkin/kin-openapi/openapi3"
    29  	v1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/labels"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	pkgtypes "k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/klog/v2"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  	"sigs.k8s.io/yaml"
    39  
    40  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    41  
    42  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    43  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    44  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    45  	"github.com/oam-dev/kubevela/apis/types"
    46  	icontext "github.com/oam-dev/kubevela/pkg/config/context"
    47  	"github.com/oam-dev/kubevela/pkg/config/writer"
    48  	velacue "github.com/oam-dev/kubevela/pkg/cue"
    49  	"github.com/oam-dev/kubevela/pkg/cue/script"
    50  	"github.com/oam-dev/kubevela/pkg/oam"
    51  	"github.com/oam-dev/kubevela/pkg/oam/util"
    52  	"github.com/oam-dev/kubevela/pkg/utils/apply"
    53  )
    54  
    55  // SaveInputPropertiesKey define the key name for saving the input properties in the secret.
    56  const SaveInputPropertiesKey = "input-properties"
    57  
    58  // SaveObjectReferenceKey define the key name for saving the outputs objects reference metadata in the secret.
    59  const SaveObjectReferenceKey = "objects-reference"
    60  
    61  // SaveExpandedWriterKey define the key name for saving the expanded writer config
    62  const SaveExpandedWriterKey = "expanded-writer"
    63  
    64  // SaveSchemaKey define the key name for saving the API schema
    65  const SaveSchemaKey = "schema"
    66  
    67  // SaveTemplateKey define the key name for saving the config-template
    68  const SaveTemplateKey = "template"
    69  
    70  // TemplateConfigMapNamePrefix the prefix of the configmap name.
    71  const TemplateConfigMapNamePrefix = "config-template-"
    72  
    73  // TemplateValidationReturns define the key name for the config-template validation returns
    74  const TemplateValidationReturns = SaveTemplateKey + ".validation.$returns"
    75  
    76  // TemplateOutput define the key name for the config-template output
    77  const TemplateOutput = SaveTemplateKey + ".output"
    78  
    79  // TemplateOutputs define the key name for the config-template outputs
    80  const TemplateOutputs = SaveTemplateKey + ".outputs"
    81  
    82  // ErrSensitiveConfig means this config can not be read directly.
    83  var ErrSensitiveConfig = errors.New("the config is sensitive")
    84  
    85  // ErrNoConfigOrTarget means the config or the target is empty.
    86  var ErrNoConfigOrTarget = errors.New("you must specify the config name and destination to distribute")
    87  
    88  // ErrNotFoundDistribution means the app of the distribution does not exist.
    89  var ErrNotFoundDistribution = errors.New("the distribution does not found")
    90  
    91  // ErrConfigExist means the config does exist.
    92  var ErrConfigExist = errors.New("the config does exist")
    93  
    94  // ErrConfigNotFound means the config does not exist
    95  var ErrConfigNotFound = errors.New("the config does not exist")
    96  
    97  // ErrTemplateNotFound means the template does not exist
    98  var ErrTemplateNotFound = errors.New("the template does not exist")
    99  
   100  // ErrChangeTemplate means the template of the config can not be changed
   101  var ErrChangeTemplate = errors.New("the template of the config can not be changed")
   102  
   103  // ErrChangeSecretType means the secret type of the config can not be changed
   104  var ErrChangeSecretType = errors.New("the secret type of the config can not be changed")
   105  
   106  // NamespacedName the namespace and name model
   107  type NamespacedName struct {
   108  	Name      string `json:"name"`
   109  	Namespace string `json:"namespace"`
   110  }
   111  
   112  // Template This is the spec of the config template, parse from the cue script.
   113  type Template struct {
   114  	NamespacedName
   115  	Alias       string `json:"alias,omitempty"`
   116  	Description string `json:"description,omitempty"`
   117  	// Scope defines the usage scope of the configuration template. Provides two options: System or Namespace
   118  	// System: The system users could use this template, and the config secret will save in the vela-system namespace.
   119  	// Namespace: The config secret will save in the target namespace, such as this namespace belonging to one project.
   120  	Scope string `json:"scope"`
   121  	// Sensitive means this config can not be read from the API or the workflow step, only support the safe way, such as Secret.
   122  	Sensitive bool `json:"sensitive"`
   123  
   124  	CreateTime time.Time `json:"createTime"`
   125  
   126  	Template script.CUE `json:"template"`
   127  
   128  	ExpandedWriter writer.ExpandedWriterConfig `json:"expandedWriter"`
   129  
   130  	Schema *openapi3.Schema `json:"schema"`
   131  
   132  	ConfigMap *v1.ConfigMap `json:"-"`
   133  }
   134  
   135  // Metadata users should provide this model.
   136  type Metadata struct {
   137  	NamespacedName
   138  	Alias       string                 `json:"alias,omitempty"`
   139  	Description string                 `json:"description,omitempty"`
   140  	Properties  map[string]interface{} `json:"properties"`
   141  }
   142  
   143  // TemplateMetadata This is the metadata of the config template
   144  type TemplateMetadata struct {
   145  	Name        string `json:"name"`
   146  	Alias       string `json:"alias,omitempty"`
   147  	Description string `json:"description,omitempty"`
   148  	Sensitive   bool   `json:"sensitive,omitempty"`
   149  	Scope       string `json:"scope,omitempty"`
   150  }
   151  
   152  // Config this is the config model, generated from the template and properties.
   153  type Config struct {
   154  	Metadata
   155  	CreateTime time.Time
   156  	Template   Template `json:"template"`
   157  	// Secret this is default output way.
   158  	Secret *v1.Secret `json:"secret"`
   159  
   160  	// ExpandedWriterData
   161  	ExpandedWriterData *writer.ExpandedWriterData `json:"expandedWriterData"`
   162  
   163  	// OutputObjects this means users could define other objects.
   164  	// This field assign value only on config render stage.
   165  	OutputObjects map[string]*unstructured.Unstructured
   166  
   167  	// ObjectReferences correspond OutputObjects
   168  	ObjectReferences []v1.ObjectReference
   169  
   170  	Targets []*ClusterTargetStatus
   171  }
   172  
   173  // ClusterTargetStatus merge the status of the distribution
   174  type ClusterTargetStatus struct {
   175  	ClusterTarget
   176  	Status      string         `json:"status"`
   177  	Application NamespacedName `json:"application"`
   178  	Message     string         `json:"message"`
   179  }
   180  
   181  // ClusterTarget kubernetes delivery target
   182  type ClusterTarget struct {
   183  	ClusterName string `json:"clusterName"`
   184  	Namespace   string `json:"namespace"`
   185  }
   186  
   187  // Distribution the config distribution model
   188  type Distribution struct {
   189  	Name        string                  `json:"name"`
   190  	Namespace   string                  `json:"namespace"`
   191  	CreatedTime time.Time               `json:"createdTime"`
   192  	Configs     []*NamespacedName       `json:"configs"`
   193  	Targets     []*ClusterTarget        `json:"targets"`
   194  	Application pkgtypes.NamespacedName `json:"application"`
   195  	Status      common.AppStatus        `json:"status"`
   196  }
   197  
   198  // CreateDistributionSpec the spec of the distribution
   199  type CreateDistributionSpec struct {
   200  	Configs []*NamespacedName `json:"configs"`
   201  	Targets []*ClusterTarget  `json:"targets"`
   202  }
   203  
   204  // Validation the response of the validation
   205  type Validation struct {
   206  	Result  bool   `json:"result"`
   207  	Message string `json:"message"`
   208  }
   209  
   210  // Error return the error message
   211  func (e *Validation) Error() string {
   212  	return fmt.Sprintf("failed to validate config: %s", e.Message)
   213  }
   214  
   215  // Factory handle the config
   216  type Factory interface {
   217  	ParseTemplate(defaultName string, content []byte) (*Template, error)
   218  	ParseConfig(ctx context.Context, template NamespacedName, meta Metadata) (*Config, error)
   219  
   220  	LoadTemplate(ctx context.Context, name, ns string) (*Template, error)
   221  	CreateOrUpdateConfigTemplate(ctx context.Context, ns string, it *Template) error
   222  	DeleteTemplate(ctx context.Context, ns, name string) error
   223  	ListTemplates(ctx context.Context, ns, scope string) ([]*Template, error)
   224  
   225  	ReadConfig(ctx context.Context, namespace, name string) (map[string]interface{}, error)
   226  	GetConfig(ctx context.Context, namespace, name string, withStatus bool) (*Config, error)
   227  	ListConfigs(ctx context.Context, namespace, template, scope string, withStatus bool) ([]*Config, error)
   228  	DeleteConfig(ctx context.Context, namespace, name string) error
   229  	CreateOrUpdateConfig(ctx context.Context, i *Config, ns string) error
   230  	IsExist(ctx context.Context, namespace, name string) (bool, error)
   231  
   232  	CreateOrUpdateDistribution(ctx context.Context, ns, name string, ads *CreateDistributionSpec) error
   233  	ListDistributions(ctx context.Context, ns string) ([]*Distribution, error)
   234  	DeleteDistribution(ctx context.Context, ns, name string) error
   235  	MergeDistributionStatus(ctx context.Context, config *Config, namespace string) error
   236  }
   237  
   238  // Dispatcher is a client for apply resources.
   239  type Dispatcher func(context.Context, []*unstructured.Unstructured, []apply.ApplyOption) error
   240  
   241  // NewConfigFactory create a config factory instance
   242  func NewConfigFactory(cli client.Client) Factory {
   243  	return &kubeConfigFactory{cli: cli, apiApply: defaultDispatcher(cli)}
   244  }
   245  
   246  // NewConfigFactoryWithDispatcher create a config factory instance with a specified dispatcher
   247  func NewConfigFactoryWithDispatcher(cli client.Client, ds Dispatcher) Factory {
   248  	if ds == nil {
   249  		ds = defaultDispatcher(cli)
   250  	}
   251  	return &kubeConfigFactory{cli: cli, apiApply: ds}
   252  }
   253  
   254  func defaultDispatcher(cli client.Client) Dispatcher {
   255  	api := apply.NewAPIApplicator(cli)
   256  	return func(ctx context.Context, manifests []*unstructured.Unstructured, ao []apply.ApplyOption) error {
   257  		for _, m := range manifests {
   258  			if err := api.Apply(ctx, m, ao...); err != nil {
   259  				return err
   260  			}
   261  		}
   262  		return nil
   263  	}
   264  }
   265  
   266  type kubeConfigFactory struct {
   267  	cli      client.Client
   268  	apiApply Dispatcher
   269  }
   270  
   271  // ParseTemplate parse a config template instance form the cue script
   272  func (k *kubeConfigFactory) ParseTemplate(defaultName string, content []byte) (*Template, error) {
   273  	cueScript := script.BuildCUEScriptWithDefaultContext(icontext.DefaultContext, content)
   274  	value, err := cueScript.ParseToTemplateValueWithCueX()
   275  	if err != nil {
   276  		return nil, fmt.Errorf("the cue script is invalid:%w", err)
   277  	}
   278  	// Render the metadata
   279  	tm := TemplateMetadata{}
   280  	metadata := value.LookupPath(cue.ParsePath("metadata"))
   281  	if !metadata.Exists() && defaultName == "" {
   282  		return nil, fmt.Errorf("failed to lookup value: var(path=metadata) not exist")
   283  	}
   284  	if err := metadata.Decode(&tm); err != nil {
   285  		return nil, fmt.Errorf("failed to decode the template metadata: %w", err)
   286  	}
   287  	if defaultName != "" {
   288  		tm.Name = defaultName
   289  	}
   290  
   291  	templateValue := value.LookupPath(cue.ParsePath("template"))
   292  	if !templateValue.Exists() {
   293  		return nil, fmt.Errorf("failed to lookup value: var(path=template) not exist")
   294  	}
   295  	schema, err := cueScript.ParsePropertiesToSchemaWithCueX("template")
   296  	if err != nil {
   297  		return nil, fmt.Errorf("the properties of the cue script is invalid:%w", err)
   298  	}
   299  	template := &Template{
   300  		NamespacedName: NamespacedName{
   301  			Name: tm.Name,
   302  		},
   303  		Alias:          tm.Alias,
   304  		Scope:          tm.Scope,
   305  		Sensitive:      tm.Sensitive,
   306  		Template:       cueScript,
   307  		Schema:         schema,
   308  		ExpandedWriter: writer.ParseExpandedWriterConfig(templateValue),
   309  	}
   310  
   311  	var configmap v1.ConfigMap
   312  	configmap.Name = TemplateConfigMapNamePrefix + template.Name
   313  
   314  	configmap.Data = map[string]string{
   315  		SaveTemplateKey: string(template.Template),
   316  	}
   317  	if template.Schema != nil {
   318  		data, err := yaml.Marshal(template.Schema)
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  		configmap.Data[SaveSchemaKey] = string(data)
   323  	}
   324  	data, err := yaml.Marshal(template.ExpandedWriter)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	configmap.Data[SaveExpandedWriterKey] = string(data)
   329  	configmap.Labels = map[string]string{
   330  		types.LabelConfigCatalog: types.VelaCoreConfig,
   331  		types.LabelConfigScope:   template.Scope,
   332  	}
   333  	configmap.Annotations = map[string]string{
   334  		types.AnnotationConfigDescription: template.Description,
   335  		types.AnnotationConfigAlias:       template.Alias,
   336  		types.AnnotationConfigSensitive:   fmt.Sprintf("%t", template.Sensitive),
   337  	}
   338  	template.ConfigMap = &configmap
   339  
   340  	return template, nil
   341  }
   342  
   343  // IsFieldNotExist check whether the error type is the field not found
   344  func IsFieldNotExist(err error) bool {
   345  	return strings.Contains(err.Error(), "not exist")
   346  }
   347  
   348  // CreateOrUpdateConfigTemplate parse and update the config template
   349  func (k *kubeConfigFactory) CreateOrUpdateConfigTemplate(ctx context.Context, ns string, it *Template) error {
   350  	if ns != "" {
   351  		it.ConfigMap.Namespace = ns
   352  	}
   353  	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(it.ConfigMap)
   354  	if err != nil {
   355  		return fmt.Errorf("fail to convert configmap to unstructured: %w", err)
   356  	}
   357  	us := &unstructured.Unstructured{Object: obj}
   358  	us.SetAPIVersion("v1")
   359  	us.SetKind("ConfigMap")
   360  	return k.apiApply(ctx, []*unstructured.Unstructured{us}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()})
   361  }
   362  
   363  func convertConfigMap2Template(cm v1.ConfigMap) (*Template, error) {
   364  	if cm.Labels == nil || cm.Annotations == nil {
   365  		return nil, fmt.Errorf("this configmap is not a valid config-template")
   366  	}
   367  	it := &Template{
   368  		NamespacedName: NamespacedName{
   369  			Name:      strings.Replace(cm.Name, TemplateConfigMapNamePrefix, "", 1),
   370  			Namespace: cm.Namespace,
   371  		},
   372  		Alias:       cm.Annotations[types.AnnotationConfigAlias],
   373  		Description: cm.Annotations[types.AnnotationConfigDescription],
   374  		Sensitive:   cm.Annotations[types.AnnotationConfigSensitive] == "true",
   375  		Scope:       cm.Labels[types.LabelConfigScope],
   376  		CreateTime:  cm.CreationTimestamp.Time,
   377  		Template:    script.CUE(cm.Data[SaveTemplateKey]),
   378  	}
   379  	if cm.Data[SaveSchemaKey] != "" {
   380  		var schema openapi3.Schema
   381  		err := yaml.Unmarshal([]byte(cm.Data[SaveSchemaKey]), &schema)
   382  		if err != nil {
   383  			return nil, fmt.Errorf("fail to parse the schema: %w", err)
   384  		}
   385  		it.Schema = &schema
   386  	}
   387  	if cm.Data[SaveExpandedWriterKey] != "" {
   388  		var config writer.ExpandedWriterConfig
   389  		err := yaml.Unmarshal([]byte(cm.Data[SaveExpandedWriterKey]), &config)
   390  		if err != nil {
   391  			return nil, fmt.Errorf("fail to parse the schema: %w", err)
   392  		}
   393  		it.ExpandedWriter = config
   394  	}
   395  	return it, nil
   396  }
   397  
   398  // DeleteTemplate delete the config template
   399  func (k *kubeConfigFactory) DeleteTemplate(ctx context.Context, ns, name string) error {
   400  	var configmap v1.ConfigMap
   401  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: ns, Name: TemplateConfigMapNamePrefix + name}, &configmap); err != nil {
   402  		if apierrors.IsNotFound(err) {
   403  			return fmt.Errorf("the config template %s not found", name)
   404  		}
   405  		return fmt.Errorf("fail to delete the config template %s:%w", name, err)
   406  	}
   407  	return k.cli.Delete(ctx, &configmap)
   408  }
   409  
   410  // ListTemplates list the config templates
   411  func (k *kubeConfigFactory) ListTemplates(ctx context.Context, ns, scope string) ([]*Template, error) {
   412  	var list = &v1.ConfigMapList{}
   413  	selector, err := labels.Parse(fmt.Sprintf("%s=%s", types.LabelConfigCatalog, types.VelaCoreConfig))
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	if err := k.cli.List(ctx, list,
   418  		client.MatchingLabelsSelector{Selector: selector},
   419  		client.InNamespace(ns)); err != nil {
   420  		return nil, err
   421  	}
   422  	var templates []*Template
   423  	for _, item := range list.Items {
   424  		it, err := convertConfigMap2Template(item)
   425  		if err != nil {
   426  			klog.Warningf("fail to parse the configmap %s:%s", item.Name, err.Error())
   427  		}
   428  		if it != nil {
   429  			if scope == "" || it.Scope == scope {
   430  				templates = append(templates, it)
   431  			}
   432  		}
   433  	}
   434  	return templates, nil
   435  }
   436  
   437  // LoadTemplate load the template
   438  func (k *kubeConfigFactory) LoadTemplate(ctx context.Context, name, ns string) (*Template, error) {
   439  	var cm v1.ConfigMap
   440  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: ns, Name: TemplateConfigMapNamePrefix + name}, &cm); err != nil {
   441  		if apierrors.IsNotFound(err) {
   442  			return nil, ErrTemplateNotFound
   443  		}
   444  		return nil, err
   445  	}
   446  	return convertConfigMap2Template(cm)
   447  }
   448  
   449  // ParseConfig merge the properties to template and build a config instance
   450  // If the templateName is empty, means creating a secret without the template.
   451  func (k *kubeConfigFactory) ParseConfig(ctx context.Context,
   452  	template NamespacedName, meta Metadata,
   453  ) (*Config, error) {
   454  	var secret v1.Secret
   455  
   456  	config := &Config{
   457  		Metadata: meta,
   458  		Secret:   &secret,
   459  	}
   460  
   461  	if template.Name != "" {
   462  		template, err := k.LoadTemplate(ctx, template.Name, template.Namespace)
   463  		if err != nil {
   464  			return nil, err
   465  		}
   466  		contextValue := icontext.ConfigRenderContext{
   467  			Name:      meta.Name,
   468  			Namespace: meta.Namespace,
   469  		}
   470  		// Compile the config template
   471  		val, err := template.Template.RunAndOutputWithCueX(ctx, contextValue, meta.Properties)
   472  		if err != nil && !velacue.IsFieldNotExist(err) {
   473  			return nil, err
   474  		}
   475  		// Render the validation response and check validation result
   476  		valid := val.LookupPath(cue.ParsePath(TemplateValidationReturns))
   477  		validation := Validation{}
   478  		if valid.Exists() {
   479  			if err := valid.Decode(&validation); err != nil {
   480  				return nil, fmt.Errorf("the validation.$returns format must be validation")
   481  			}
   482  		}
   483  		if len(validation.Message) > 0 {
   484  			return nil, &validation
   485  		}
   486  		// Render the output secret
   487  		output := val.LookupPath(cue.ParsePath(TemplateOutput))
   488  		if output.Exists() {
   489  			if err := output.Decode(&secret); err != nil {
   490  				return nil, fmt.Errorf("the output format must be secret")
   491  			}
   492  		}
   493  		if secret.Type == "" {
   494  			secret.Type = v1.SecretType(fmt.Sprintf("%s/%s", "", template.Name))
   495  		}
   496  		if secret.Labels == nil {
   497  			secret.Labels = map[string]string{}
   498  		}
   499  		secret.Labels[types.LabelConfigCatalog] = types.VelaCoreConfig
   500  		secret.Labels[types.LabelConfigType] = template.Name
   501  		secret.Labels[types.LabelConfigType] = template.Name
   502  		secret.Labels[types.LabelConfigScope] = template.Scope
   503  
   504  		if secret.Annotations == nil {
   505  			secret.Annotations = map[string]string{}
   506  		}
   507  		secret.Annotations[types.AnnotationConfigSensitive] = fmt.Sprintf("%t", template.Sensitive)
   508  		secret.Annotations[types.AnnotationConfigTemplateNamespace] = template.Namespace
   509  		config.Template = *template
   510  
   511  		// Render the expanded writer configuration
   512  		data, err := writer.RenderForExpandedWriter(template.ExpandedWriter, config.Template.Template, contextValue, meta.Properties)
   513  		if err != nil {
   514  			return nil, fmt.Errorf("fail to render the content for the expanded writer:%w ", err)
   515  		}
   516  		config.ExpandedWriterData = data
   517  
   518  		// Render the outputs objects
   519  		outputs := val.LookupPath(cue.ParsePath(TemplateOutputs))
   520  		if outputs.Exists() {
   521  			var objects = map[string]interface{}{}
   522  			if err := outputs.Decode(&objects); err != nil {
   523  				return nil, fmt.Errorf("the outputs is invalid %w", err)
   524  			}
   525  			var objectReferences []v1.ObjectReference
   526  			config.OutputObjects = make(map[string]*unstructured.Unstructured)
   527  			for k := range objects {
   528  				if ob, ok := objects[k].(map[string]interface{}); ok {
   529  					obj := &unstructured.Unstructured{Object: ob}
   530  					config.OutputObjects[k] = obj
   531  					objectReferences = append(objectReferences, v1.ObjectReference{
   532  						Kind:       obj.GetKind(),
   533  						Namespace:  obj.GetNamespace(),
   534  						Name:       obj.GetName(),
   535  						APIVersion: obj.GetAPIVersion(),
   536  					})
   537  				}
   538  			}
   539  			objectReferenceJSON, err := json.Marshal(objectReferences)
   540  			if err != nil {
   541  				return nil, err
   542  			}
   543  			if secret.Data == nil {
   544  				secret.Data = map[string][]byte{}
   545  			}
   546  			secret.Data[SaveObjectReferenceKey] = objectReferenceJSON
   547  		}
   548  	} else {
   549  		secret.Labels = map[string]string{
   550  			types.LabelConfigCatalog: types.VelaCoreConfig,
   551  			types.LabelConfigType:    "",
   552  		}
   553  		secret.Annotations = map[string]string{}
   554  	}
   555  	secret.Namespace = meta.Namespace
   556  	if secret.Name == "" {
   557  		secret.Name = meta.Name
   558  	}
   559  	secret.Annotations[types.AnnotationConfigAlias] = meta.Alias
   560  	secret.Annotations[types.AnnotationConfigDescription] = meta.Description
   561  	pp, err := json.Marshal(meta.Properties)
   562  	if err != nil {
   563  		return nil, err
   564  	}
   565  	if secret.Data == nil {
   566  		secret.Data = map[string][]byte{}
   567  	}
   568  	secret.Data[SaveInputPropertiesKey] = pp
   569  
   570  	return config, nil
   571  }
   572  
   573  // ReadConfig read the config secret
   574  func (k *kubeConfigFactory) ReadConfig(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
   575  	var secret v1.Secret
   576  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
   577  		return nil, err
   578  	}
   579  	if secret.Annotations[types.AnnotationConfigSensitive] == "true" {
   580  		return nil, ErrSensitiveConfig
   581  	}
   582  	properties := secret.Data[SaveInputPropertiesKey]
   583  	var input = map[string]interface{}{}
   584  	if err := json.Unmarshal(properties, &input); err != nil {
   585  		return nil, err
   586  	}
   587  	return input, nil
   588  }
   589  
   590  func (k *kubeConfigFactory) GetConfig(ctx context.Context, namespace, name string, withStatus bool) (*Config, error) {
   591  	var secret v1.Secret
   592  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
   593  		if apierrors.IsNotFound(err) {
   594  			return nil, ErrConfigNotFound
   595  		}
   596  		return nil, err
   597  	}
   598  	if secret.Annotations[types.AnnotationConfigSensitive] == "true" {
   599  		return nil, ErrSensitiveConfig
   600  	}
   601  	item, err := convertSecret2Config(&secret)
   602  	if err != nil {
   603  		return nil, err
   604  	}
   605  	if withStatus {
   606  		if err := k.MergeDistributionStatus(ctx, item, item.Namespace); err != nil && !errors.Is(err, ErrNotFoundDistribution) {
   607  			klog.Warningf("fail to merge the status %s:%s", item.Name, err.Error())
   608  		}
   609  	}
   610  	return item, nil
   611  }
   612  
   613  // CreateOrUpdateConfig create or update the config.
   614  // Write the expand config to the target server.
   615  func (k *kubeConfigFactory) CreateOrUpdateConfig(ctx context.Context, i *Config, _ string) error {
   616  	var secret v1.Secret
   617  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: i.Namespace, Name: i.Name}, &secret); err == nil {
   618  		if secret.Labels[types.LabelConfigType] != i.Template.Name {
   619  			return ErrChangeTemplate
   620  		}
   621  		if i.Secret.Type != "" && secret.Type != i.Secret.Type {
   622  			return ErrChangeSecretType
   623  		}
   624  	}
   625  
   626  	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(i.Secret)
   627  	if err != nil {
   628  		return fmt.Errorf("fail to convert secret to unstructured: %w", err)
   629  	}
   630  	us := &unstructured.Unstructured{Object: obj}
   631  	us.SetAPIVersion("v1")
   632  	us.SetKind("Secret")
   633  
   634  	if err := k.apiApply(ctx, []*unstructured.Unstructured{us}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()}); err != nil {
   635  		return fmt.Errorf("fail to apply the secret: %w", err)
   636  	}
   637  	for key, obj := range i.OutputObjects {
   638  		if err := k.apiApply(ctx, []*unstructured.Unstructured{obj}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()}); err != nil {
   639  			return fmt.Errorf("fail to apply the object %s: %w", key, err)
   640  		}
   641  	}
   642  	readConfig := func(ctx context.Context, namespace, name string) (map[string]interface{}, error) {
   643  		return k.ReadConfig(ctx, namespace, name)
   644  	}
   645  	if i.ExpandedWriterData != nil {
   646  		if errs := writer.Write(ctx, i.ExpandedWriterData, readConfig); len(errs) > 0 {
   647  			return errs[0]
   648  		}
   649  	}
   650  	return nil
   651  }
   652  
   653  func (k *kubeConfigFactory) IsExist(ctx context.Context, namespace, name string) (bool, error) {
   654  	var secret v1.Secret
   655  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
   656  		if apierrors.IsNotFound(err) {
   657  			return false, nil
   658  		}
   659  		return false, err
   660  	}
   661  	return true, nil
   662  }
   663  
   664  func (k *kubeConfigFactory) ListConfigs(ctx context.Context, namespace, template, scope string, withStatus bool) ([]*Config, error) {
   665  	var list = &v1.SecretList{}
   666  	requirement := fmt.Sprintf("%s=%s", types.LabelConfigCatalog, types.VelaCoreConfig)
   667  	if template != "" {
   668  		requirement = fmt.Sprintf("%s,%s=%s", requirement, types.LabelConfigType, template)
   669  	}
   670  	if scope != "" {
   671  		requirement = fmt.Sprintf("%s,%s=%s", requirement, types.LabelConfigScope, scope)
   672  	}
   673  	selector, err := labels.Parse(requirement)
   674  	if err != nil {
   675  		return nil, err
   676  	}
   677  	if err := k.cli.List(ctx, list,
   678  		client.MatchingLabelsSelector{Selector: selector},
   679  		client.InNamespace(namespace)); err != nil {
   680  		return nil, err
   681  	}
   682  	var configs []*Config
   683  	for i := range list.Items {
   684  		item := list.Items[i]
   685  		it, err := convertSecret2Config(&item)
   686  		if err != nil {
   687  			klog.Warningf("fail to parse the secret %s:%s", item.Name, err.Error())
   688  		}
   689  		if it != nil {
   690  			if withStatus {
   691  				if err := k.MergeDistributionStatus(ctx, it, it.Namespace); err != nil && !errors.Is(err, ErrNotFoundDistribution) {
   692  					klog.Warningf("fail to merge the status %s:%s", item.Name, err.Error())
   693  				}
   694  			}
   695  			configs = append(configs, it)
   696  		}
   697  	}
   698  	return configs, nil
   699  }
   700  
   701  func (k *kubeConfigFactory) DeleteConfig(ctx context.Context, namespace, name string) error {
   702  	var secret v1.Secret
   703  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: name}, &secret); err != nil {
   704  		if apierrors.IsNotFound(err) {
   705  			return fmt.Errorf("the config %s not found", name)
   706  		}
   707  		return fmt.Errorf("fail to delete the config %s:%w", name, err)
   708  	}
   709  	if secret.Labels[types.LabelConfigCatalog] != types.VelaCoreConfig {
   710  		return fmt.Errorf("found a secret but is not a config")
   711  	}
   712  
   713  	if objects, exist := secret.Data[SaveObjectReferenceKey]; exist {
   714  		var objectReferences []v1.ObjectReference
   715  		if err := json.Unmarshal(objects, &objectReferences); err != nil {
   716  			return err
   717  		}
   718  		for _, obj := range objectReferences {
   719  			if err := k.cli.Delete(ctx, convertObjectReference2Unstructured(obj)); err != nil && !apierrors.IsNotFound(err) {
   720  				return fmt.Errorf("fail to clear the object %s:%w", obj.Name, err)
   721  			}
   722  		}
   723  	}
   724  
   725  	return k.cli.Delete(ctx, &secret)
   726  }
   727  
   728  func (k *kubeConfigFactory) MergeDistributionStatus(ctx context.Context, config *Config, namespace string) error {
   729  	app := &v1beta1.Application{}
   730  	if err := k.cli.Get(ctx, pkgtypes.NamespacedName{Namespace: namespace, Name: DefaultDistributionName(config.Name)}, app); err != nil {
   731  		if apierrors.IsNotFound(err) {
   732  			return ErrNotFoundDistribution
   733  		}
   734  		return err
   735  	}
   736  	var targets []*ClusterTargetStatus
   737  	for _, policy := range app.Spec.Policies {
   738  		if policy.Type == v1alpha1.TopologyPolicyType {
   739  			status := workflowv1alpha1.WorkflowStepPhasePending
   740  			message := ""
   741  			if app.Status.Workflow != nil {
   742  				for _, step := range app.Status.Workflow.Steps {
   743  					if policy.Name == strings.Replace(step.Name, "deploy-", "", 1) {
   744  						status = step.Phase
   745  						message = step.Message
   746  					}
   747  				}
   748  			}
   749  			var spec v1alpha1.TopologyPolicySpec
   750  			if err := json.Unmarshal(policy.Properties.Raw, &spec); err == nil {
   751  				for _, clu := range spec.Clusters {
   752  					targets = append(targets, &ClusterTargetStatus{
   753  						ClusterTarget: ClusterTarget{
   754  							Namespace:   spec.Namespace,
   755  							ClusterName: clu,
   756  						},
   757  						Application: NamespacedName{Name: app.Name, Namespace: app.Namespace},
   758  						Status:      string(status),
   759  						Message:     message,
   760  					})
   761  				}
   762  			}
   763  		}
   764  	}
   765  	config.Targets = append(config.Targets, targets...)
   766  	return nil
   767  }
   768  
   769  func (k *kubeConfigFactory) CreateOrUpdateDistribution(ctx context.Context, ns, name string, ads *CreateDistributionSpec) error {
   770  	policies := convertTarget2TopologyPolicy(ads.Targets)
   771  	if len(policies) == 0 {
   772  		return ErrNoConfigOrTarget
   773  	}
   774  	// create the share policy
   775  	shareSpec := v1alpha1.SharedResourcePolicySpec{
   776  		Rules: []v1alpha1.SharedResourcePolicyRule{{
   777  			Selector: v1alpha1.ResourcePolicyRuleSelector{
   778  				CompNames: []string{name},
   779  			},
   780  		}},
   781  	}
   782  	properties, err := json.Marshal(shareSpec)
   783  	if err == nil {
   784  		policies = append(policies, v1beta1.AppPolicy{
   785  			Type: v1alpha1.SharedResourcePolicyType,
   786  			Name: "share-config",
   787  			Properties: &runtime.RawExtension{
   788  				Raw: properties,
   789  			},
   790  		})
   791  	}
   792  
   793  	var objects []map[string]string
   794  	for _, s := range ads.Configs {
   795  		objects = append(objects, map[string]string{
   796  			"name":      s.Name,
   797  			"namespace": s.Namespace,
   798  			"resource":  "secret",
   799  		})
   800  	}
   801  	if len(objects) == 0 {
   802  		return ErrNoConfigOrTarget
   803  	}
   804  
   805  	objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
   806  	if err != nil {
   807  		return err
   808  	}
   809  
   810  	reqByte, err := json.Marshal(ads)
   811  	if err != nil {
   812  		return err
   813  	}
   814  
   815  	distribution := &v1beta1.Application{
   816  		ObjectMeta: metav1.ObjectMeta{
   817  			Name:      name,
   818  			Namespace: ns,
   819  			Labels: map[string]string{
   820  				types.LabelSourceOfTruth: types.FromInner,
   821  				// This label will override the secret label, then change the catalog of the distributed secrets.
   822  				types.LabelConfigCatalog: types.CatalogConfigDistribution,
   823  			},
   824  			Annotations: map[string]string{
   825  				types.AnnotationConfigDistributionSpec: string(reqByte),
   826  				oam.AnnotationPublishVersion:           util.GenerateVersion("config"),
   827  			},
   828  		},
   829  		Spec: v1beta1.ApplicationSpec{
   830  			Components: []common.ApplicationComponent{
   831  				{
   832  					Name:       name,
   833  					Type:       v1alpha1.RefObjectsComponentType,
   834  					Properties: &runtime.RawExtension{Raw: objectsBytes},
   835  				},
   836  			},
   837  			Policies: policies,
   838  		},
   839  	}
   840  	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(distribution)
   841  	if err != nil {
   842  		return fmt.Errorf("fail to convert application to unstructured: %w", err)
   843  	}
   844  	us := &unstructured.Unstructured{Object: obj}
   845  	us.SetAPIVersion(v1beta1.SchemeGroupVersion.String())
   846  	us.SetKind(v1beta1.ApplicationKind)
   847  
   848  	return k.apiApply(ctx, []*unstructured.Unstructured{us}, []apply.ApplyOption{apply.DisableUpdateAnnotation(), apply.Quiet()})
   849  }
   850  
   851  func (k *kubeConfigFactory) ListDistributions(ctx context.Context, ns string) ([]*Distribution, error) {
   852  	var apps v1beta1.ApplicationList
   853  	if err := k.cli.List(ctx, &apps, client.MatchingLabels{
   854  		types.LabelSourceOfTruth: types.FromInner,
   855  		types.LabelConfigCatalog: types.CatalogConfigDistribution,
   856  	}, client.InNamespace(ns)); err != nil {
   857  		return nil, err
   858  	}
   859  	var list []*Distribution
   860  	for _, app := range apps.Items {
   861  		dis := &Distribution{
   862  			Name:        app.Name,
   863  			Namespace:   app.Namespace,
   864  			CreatedTime: app.CreationTimestamp.Time,
   865  			Application: pkgtypes.NamespacedName{
   866  				Namespace: app.Namespace,
   867  				Name:      app.Name,
   868  			},
   869  			Status: app.Status,
   870  		}
   871  		if spec, ok := app.Annotations[types.AnnotationConfigDistributionSpec]; ok {
   872  			var req CreateDistributionSpec
   873  			if err := json.Unmarshal([]byte(spec), &req); err == nil {
   874  				dis.Targets = req.Targets
   875  				dis.Configs = req.Configs
   876  			}
   877  		}
   878  		list = append(list, dis)
   879  	}
   880  	return list, nil
   881  }
   882  func (k *kubeConfigFactory) DeleteDistribution(ctx context.Context, ns, name string) error {
   883  	app := &v1beta1.Application{
   884  		ObjectMeta: metav1.ObjectMeta{
   885  			Namespace: ns,
   886  			Name:      name,
   887  		},
   888  	}
   889  	if err := k.cli.Delete(ctx, app); err != nil {
   890  		if apierrors.IsNotFound(err) {
   891  			return ErrNotFoundDistribution
   892  		}
   893  		return err
   894  	}
   895  	return nil
   896  }
   897  
   898  func convertTarget2TopologyPolicy(targets []*ClusterTarget) (policies []v1beta1.AppPolicy) {
   899  	for _, target := range targets {
   900  		policySpec := v1alpha1.TopologyPolicySpec{
   901  			Placement: v1alpha1.Placement{
   902  				Clusters: []string{target.ClusterName},
   903  			},
   904  			Namespace: target.Namespace,
   905  		}
   906  		properties, err := json.Marshal(policySpec)
   907  		if err == nil {
   908  			policies = append(policies, v1beta1.AppPolicy{
   909  				Type: v1alpha1.TopologyPolicyType,
   910  				Name: fmt.Sprintf("%s-%s", target.ClusterName, target.Namespace),
   911  				Properties: &runtime.RawExtension{
   912  					Raw: properties,
   913  				},
   914  			})
   915  		}
   916  	}
   917  	return
   918  }
   919  
   920  func convertSecret2Config(se *v1.Secret) (*Config, error) {
   921  	if se == nil || se.Labels == nil {
   922  		return nil, fmt.Errorf("this secret is not a valid config secret")
   923  	}
   924  	config := &Config{
   925  		Metadata: Metadata{
   926  			NamespacedName: NamespacedName{
   927  				Name:      se.Name,
   928  				Namespace: se.Namespace,
   929  			},
   930  		},
   931  		CreateTime: se.CreationTimestamp.Time,
   932  		Secret:     se,
   933  		Template: Template{
   934  			NamespacedName: NamespacedName{
   935  				Name: se.Labels[types.LabelConfigType],
   936  			},
   937  		},
   938  	}
   939  	if se.Annotations != nil {
   940  		config.Alias = se.Annotations[types.AnnotationConfigAlias]
   941  		config.Description = se.Annotations[types.AnnotationConfigDescription]
   942  		config.Template.Namespace = se.Annotations[types.AnnotationConfigTemplateNamespace]
   943  		config.Template.Sensitive = se.Annotations[types.AnnotationConfigSensitive] == "true"
   944  	}
   945  	if !config.Template.Sensitive && len(se.Data[SaveInputPropertiesKey]) > 0 {
   946  		var properties = map[string]interface{}{}
   947  		if err := yaml.Unmarshal(se.Data[SaveInputPropertiesKey], &properties); err != nil {
   948  			return nil, err
   949  		}
   950  		config.Properties = properties
   951  	}
   952  	if !config.Template.Sensitive {
   953  		config.Secret = se
   954  	} else {
   955  		seCope := se.DeepCopy()
   956  		seCope.Data = nil
   957  		seCope.StringData = nil
   958  		config.Secret = seCope
   959  	}
   960  	if content, ok := se.Data[SaveObjectReferenceKey]; ok {
   961  		var objectReferences []v1.ObjectReference
   962  		if err := json.Unmarshal(content, &objectReferences); err != nil {
   963  			klog.Warningf("the object references are invalid, config:%s", se.Name)
   964  		}
   965  		config.ObjectReferences = objectReferences
   966  	}
   967  	return config, nil
   968  }
   969  
   970  func convertObjectReference2Unstructured(ref v1.ObjectReference) *unstructured.Unstructured {
   971  	var obj unstructured.Unstructured
   972  	obj.SetAPIVersion(ref.APIVersion)
   973  	obj.SetNamespace(ref.Namespace)
   974  	obj.SetKind(ref.Kind)
   975  	obj.SetName(ref.Name)
   976  	return &obj
   977  }
   978  
   979  // DefaultDistributionName generate the distribution name by a config name
   980  func DefaultDistributionName(configName string) string {
   981  	return fmt.Sprintf("distribute-%s", configName)
   982  }