sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/repository/template.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes 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 repository
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/pkg/errors"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  
    26  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    27  	yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
    28  	utilyaml "sigs.k8s.io/cluster-api/util/yaml"
    29  )
    30  
    31  // Template wraps a YAML file that defines the cluster objects (Cluster, Machines etc.).
    32  // It is important to notice that clusterctl applies a set of processing steps to the “raw” cluster template YAML read
    33  // from the provider repositories:
    34  // 1. Checks for all the variables in the cluster template YAML file and replace with corresponding config values
    35  // 2. Ensure all the cluster objects are deployed in the target namespace.
    36  type Template interface {
    37  	// Variables used by the template.
    38  	// This value is derived from the template YAML.
    39  	Variables() []string
    40  
    41  	// VariableMap used by the template with their default values. If the value is `nil`, there is no
    42  	// default and the variable is required.
    43  	// This value is derived from the template YAML.
    44  	VariableMap() map[string]*string
    45  
    46  	// TargetNamespace where the template objects will be installed.
    47  	TargetNamespace() string
    48  
    49  	// Yaml returns yaml defining all the cluster template objects as a byte array.
    50  	Yaml() ([]byte, error)
    51  
    52  	// Objs returns the cluster template as a list of Unstructured objects.
    53  	Objs() []unstructured.Unstructured
    54  }
    55  
    56  // template implements Template.
    57  type template struct {
    58  	variables       []string
    59  	variableMap     map[string]*string
    60  	targetNamespace string
    61  	objs            []unstructured.Unstructured
    62  }
    63  
    64  // Ensures template implements the Template interface.
    65  var _ Template = &template{}
    66  
    67  func (t *template) Variables() []string {
    68  	return t.variables
    69  }
    70  
    71  func (t *template) VariableMap() map[string]*string {
    72  	return t.variableMap
    73  }
    74  
    75  func (t *template) TargetNamespace() string {
    76  	return t.targetNamespace
    77  }
    78  
    79  func (t *template) Objs() []unstructured.Unstructured {
    80  	return t.objs
    81  }
    82  
    83  func (t *template) Yaml() ([]byte, error) {
    84  	return utilyaml.FromUnstructured(t.objs)
    85  }
    86  
    87  // TemplateInput is an input struct for NewTemplate.
    88  type TemplateInput struct {
    89  	RawArtifact           []byte
    90  	ConfigVariablesClient config.VariablesClient
    91  	Processor             yaml.Processor
    92  	TargetNamespace       string
    93  	SkipTemplateProcess   bool
    94  }
    95  
    96  // NewTemplate returns a new objects embedding a cluster template YAML file.
    97  func NewTemplate(input TemplateInput) (Template, error) {
    98  	variables, err := input.Processor.GetVariables(input.RawArtifact)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	variableMap, err := input.Processor.GetVariableMap(input.RawArtifact)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	if input.SkipTemplateProcess {
   109  		return &template{
   110  			variables:       variables,
   111  			variableMap:     variableMap,
   112  			targetNamespace: input.TargetNamespace,
   113  		}, nil
   114  	}
   115  
   116  	processedYaml, err := input.Processor.Process(input.RawArtifact, input.ConfigVariablesClient.Get)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	// Transform the yaml in a list of objects, so following transformation can work on typed objects (instead of working on a string/slice of bytes).
   122  	objs, err := utilyaml.ToUnstructured(processedYaml)
   123  	if err != nil {
   124  		return nil, errors.Wrap(err, "failed to parse yaml")
   125  	}
   126  
   127  	if input.TargetNamespace != "" {
   128  		// Ensures all the template components are deployed in the target namespace (applies only to namespaced objects)
   129  		// This is required in order to ensure a cluster and all the related objects are in a single namespace, that is a requirement for
   130  		// the clusterctl move operation (and also for many controller reconciliation loops).
   131  		objs, err = fixTargetNamespace(objs, input.TargetNamespace)
   132  		if err != nil {
   133  			return nil, errors.Wrap(err, "failed to set the TargetNamespace in the template")
   134  		}
   135  	}
   136  
   137  	return &template{
   138  		variables:       variables,
   139  		variableMap:     variableMap,
   140  		targetNamespace: input.TargetNamespace,
   141  		objs:            objs,
   142  	}, nil
   143  }
   144  
   145  // MergeTemplates merges the provided Templates into one Template.
   146  // Notes on the merge operation:
   147  //   - The merge operation returns an error if all the templates do not have the same TargetNamespace.
   148  //   - The Variables of the resulting template is a union of all Variables in the templates.
   149  //   - The default value is picked from the first template that defines it.
   150  //     The defaults of the same variable in the subsequent templates will be ignored.
   151  //     (e.g when merging a cluster template and its ClusterClass, the default value from the template takes precedence)
   152  //   - The Objs of the final template will be a union of all the Objs in the templates.
   153  func MergeTemplates(templates ...Template) (Template, error) {
   154  	templates = filterNilTemplates(templates...)
   155  	if len(templates) == 0 {
   156  		return nil, nil
   157  	}
   158  
   159  	merged := &template{
   160  		variables:       []string{},
   161  		variableMap:     map[string]*string{},
   162  		objs:            []unstructured.Unstructured{},
   163  		targetNamespace: templates[0].TargetNamespace(),
   164  	}
   165  
   166  	for _, tmpl := range templates {
   167  		merged.variables = sets.List(sets.Set[string]{}.Insert(merged.variables...).Union(sets.Set[string]{}.Insert(tmpl.Variables()...)))
   168  
   169  		for key, val := range tmpl.VariableMap() {
   170  			if v, ok := merged.variableMap[key]; !ok || v == nil {
   171  				merged.variableMap[key] = val
   172  			}
   173  		}
   174  
   175  		if merged.targetNamespace != tmpl.TargetNamespace() {
   176  			return nil, fmt.Errorf("cannot merge templates with different targetNamespaces")
   177  		}
   178  
   179  		merged.objs = append(merged.objs, tmpl.Objs()...)
   180  	}
   181  
   182  	return merged, nil
   183  }
   184  
   185  func filterNilTemplates(templates ...Template) []Template {
   186  	res := []Template{}
   187  	for _, tmpl := range templates {
   188  		if tmpl != nil {
   189  			res = append(res, tmpl)
   190  		}
   191  	}
   192  	return res
   193  }