...

Source file src/github.com/linkerd/linkerd2/pkg/charts/charts.go

Documentation: github.com/linkerd/linkerd2/pkg/charts

     1  package charts
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"net/http"
     7  	"path"
     8  	"strings"
     9  
    10  	"github.com/linkerd/linkerd2/pkg/charts/static"
    11  	"github.com/linkerd/linkerd2/pkg/version"
    12  	"helm.sh/helm/v3/pkg/chart/loader"
    13  	"helm.sh/helm/v3/pkg/chartutil"
    14  	"helm.sh/helm/v3/pkg/engine"
    15  	"sigs.k8s.io/yaml"
    16  )
    17  
    18  const versionPlaceholder = "linkerdVersionValue"
    19  
    20  var (
    21  	// L5dPartials is the list of templates in partials chart
    22  	// Keep this slice synced with the contents of /charts/partials
    23  	L5dPartials = []string{
    24  		"charts/partials/" + chartutil.ChartfileName,
    25  		"charts/partials/templates/_affinity.tpl",
    26  		"charts/partials/templates/_capabilities.tpl",
    27  		"charts/partials/templates/_debug.tpl",
    28  		"charts/partials/templates/_helpers.tpl",
    29  		"charts/partials/templates/_metadata.tpl",
    30  		"charts/partials/templates/_nodeselector.tpl",
    31  		"charts/partials/templates/_network-validator.tpl",
    32  		"charts/partials/templates/_proxy-config-ann.tpl",
    33  		"charts/partials/templates/_proxy-init.tpl",
    34  		"charts/partials/templates/_proxy.tpl",
    35  		"charts/partials/templates/_pull-secrets.tpl",
    36  		"charts/partials/templates/_resources.tpl",
    37  		"charts/partials/templates/_tolerations.tpl",
    38  		"charts/partials/templates/_trace.tpl",
    39  		"charts/partials/templates/_validate.tpl",
    40  		"charts/partials/templates/_volumes.tpl",
    41  	}
    42  )
    43  
    44  // Chart holds the necessary info to render a Helm chart
    45  type Chart struct {
    46  	Name      string
    47  	Dir       string
    48  	Namespace string
    49  
    50  	// RawValues are yaml-formatted values entries. Either this or Values
    51  	// should be set, but not both
    52  	RawValues []byte
    53  
    54  	// Values are the config key-value entries. Either this or RawValues should
    55  	// be set, but not both
    56  	Values map[string]any
    57  
    58  	Files []*loader.BufferedFile
    59  	Fs    http.FileSystem
    60  }
    61  
    62  func (c *Chart) render(partialsFiles []*loader.BufferedFile) (bytes.Buffer, error) {
    63  	if err := FilesReader(c.Fs, c.Dir+"/", c.Files); err != nil {
    64  		return bytes.Buffer{}, err
    65  	}
    66  
    67  	// static.Templates is used as partials are always available there
    68  	if err := FilesReader(static.Templates, "", partialsFiles); err != nil {
    69  		return bytes.Buffer{}, err
    70  	}
    71  
    72  	// Create chart and render templates
    73  	chart, err := loader.LoadFiles(append(c.Files, partialsFiles...))
    74  	if err != nil {
    75  		return bytes.Buffer{}, err
    76  	}
    77  
    78  	releaseOptions := chartutil.ReleaseOptions{
    79  		Name:      c.Name,
    80  		IsInstall: true,
    81  		IsUpgrade: false,
    82  		Namespace: c.Namespace,
    83  	}
    84  
    85  	if len(c.RawValues) > 0 {
    86  		if c.Values != nil {
    87  			return bytes.Buffer{}, errors.New("either RawValues or Values should be set, but not both")
    88  		}
    89  		err = yaml.Unmarshal(c.RawValues, &c.Values)
    90  		if err != nil {
    91  			return bytes.Buffer{}, err
    92  		}
    93  	}
    94  
    95  	valuesToRender, err := chartutil.ToRenderValues(chart, c.Values, releaseOptions, nil)
    96  	if err != nil {
    97  		return bytes.Buffer{}, err
    98  	}
    99  	release, _ := valuesToRender["Release"].(map[string]interface{})
   100  	release["Service"] = "CLI"
   101  
   102  	renderedTemplates, err := engine.Render(chart, valuesToRender)
   103  	if err != nil {
   104  		return bytes.Buffer{}, err
   105  	}
   106  
   107  	// Merge templates and inject
   108  	var buf bytes.Buffer
   109  	for _, tmpl := range c.Files {
   110  		t := path.Join(releaseOptions.Name, tmpl.Name)
   111  		if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
   112  			return bytes.Buffer{}, err
   113  		}
   114  	}
   115  
   116  	return buf, nil
   117  }
   118  
   119  // Render returns a bytes buffer with the result of rendering a Helm chart
   120  func (c *Chart) Render() (bytes.Buffer, error) {
   121  
   122  	l5dPartials := []*loader.BufferedFile{}
   123  	for _, template := range L5dPartials {
   124  		l5dPartials = append(l5dPartials, &loader.BufferedFile{
   125  			Name: template,
   126  		})
   127  	}
   128  
   129  	return c.render(l5dPartials)
   130  }
   131  
   132  // RenderCNI returns a bytes buffer with the result of rendering a Helm chart
   133  func (c *Chart) RenderCNI() (bytes.Buffer, error) {
   134  	cniPartials := []*loader.BufferedFile{
   135  		{Name: "charts/partials/" + chartutil.ChartfileName},
   136  		{Name: "charts/partials/templates/_helpers.tpl"},
   137  		{Name: "charts/partials/templates/_metadata.tpl"},
   138  		{Name: "charts/partials/templates/_pull-secrets.tpl"},
   139  		{Name: "charts/partials/templates/_tolerations.tpl"},
   140  		{Name: "charts/partials/templates/_resources.tpl"},
   141  	}
   142  	return c.render(cniPartials)
   143  }
   144  
   145  // ReadFile updates the buffered file with the data read from disk
   146  func ReadFile(fs http.FileSystem, dir string, f *loader.BufferedFile) error {
   147  	filename := dir + f.Name
   148  	if dir == "" {
   149  		filename = filename[7:]
   150  	}
   151  	file, err := fs.Open(filename)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	defer file.Close()
   156  
   157  	buf := new(bytes.Buffer)
   158  	if _, err := buf.ReadFrom(file); err != nil {
   159  		return err
   160  	}
   161  
   162  	f.Data = buf.Bytes()
   163  	return nil
   164  }
   165  
   166  // FilesReader reads all the files from a directory
   167  func FilesReader(fs http.FileSystem, dir string, files []*loader.BufferedFile) error {
   168  	for _, f := range files {
   169  		if err := ReadFile(fs, dir, f); err != nil {
   170  			return err
   171  		}
   172  	}
   173  	return nil
   174  }
   175  
   176  // InsertVersion returns the chart values file contents passed in
   177  // with the version placeholder replaced with the current version
   178  func InsertVersion(data []byte) []byte {
   179  	dataWithVersion := strings.ReplaceAll(string(data), versionPlaceholder, version.Version)
   180  	return []byte(dataWithVersion)
   181  }
   182  
   183  // InsertVersionValues returns the chart values with the version placeholder
   184  // replaced with the current version.
   185  func InsertVersionValues(values chartutil.Values) (chartutil.Values, error) {
   186  	raw, err := values.YAML()
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	return chartutil.ReadValues(InsertVersion([]byte(raw)))
   191  }
   192  
   193  // OverrideFromFile overrides the given map with the given file from FS
   194  func OverrideFromFile(values map[string]interface{}, fs http.FileSystem, chartName, name string) (map[string]interface{}, error) {
   195  	// Load Values file
   196  	valuesOverride := loader.BufferedFile{
   197  		Name: name,
   198  	}
   199  	if err := ReadFile(fs, chartName+"/", &valuesOverride); err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	var valuesOverrideMap map[string]interface{}
   204  	err := yaml.Unmarshal(valuesOverride.Data, &valuesOverrideMap)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	return MergeMaps(valuesOverrideMap, values), nil
   209  }
   210  
   211  // MergeMaps returns the resultant map after merging given two maps of type map[string]interface{}
   212  // The inputs are not mutated and the second map i.e b's values take precedence during merge.
   213  // This gives semantically correct merge compared with `mergo.Merge` (with boolean values).
   214  // See https://github.com/imdario/mergo/issues/129
   215  func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
   216  	out := make(map[string]interface{}, len(a))
   217  	for k, v := range a {
   218  		out[k] = v
   219  	}
   220  	for k, v := range b {
   221  		if v, ok := v.(map[string]interface{}); ok {
   222  			if av, ok := out[k]; ok {
   223  				if av, ok := av.(map[string]interface{}); ok {
   224  					out[k] = MergeMaps(av, v)
   225  					continue
   226  				}
   227  			}
   228  		}
   229  		out[k] = v
   230  	}
   231  	return out
   232  }
   233  

View as plain text