istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/helm/renderer.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package helm
    16  
    17  import (
    18  	"fmt"
    19  	"io/fs"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"helm.sh/helm/v3/pkg/chart"
    25  	"helm.sh/helm/v3/pkg/chart/loader"
    26  	"k8s.io/apimachinery/pkg/version"
    27  
    28  	"istio.io/istio/manifests"
    29  	"istio.io/istio/operator/pkg/util"
    30  )
    31  
    32  const (
    33  	// DefaultProfileFilename is the name of the default profile yaml file.
    34  	DefaultProfileFilename = "default.yaml"
    35  	ChartsSubdirName       = "charts"
    36  	profilesRoot           = "profiles"
    37  )
    38  
    39  // Renderer is a helm template renderer for a fs.FS.
    40  type Renderer struct {
    41  	namespace     string
    42  	componentName string
    43  	chart         *chart.Chart
    44  	started       bool
    45  	files         fs.FS
    46  	dir           string
    47  	// Kubernetes cluster version
    48  	version *version.Info
    49  }
    50  
    51  // NewFileTemplateRenderer creates a TemplateRenderer with the given parameters and returns a pointer to it.
    52  // helmChartDirPath must be an absolute file path to the root of the helm charts.
    53  func NewGenericRenderer(files fs.FS, dir, componentName, namespace string, version *version.Info) *Renderer {
    54  	return &Renderer{
    55  		namespace:     namespace,
    56  		componentName: componentName,
    57  		dir:           dir,
    58  		files:         files,
    59  		version:       version,
    60  	}
    61  }
    62  
    63  // Run implements the TemplateRenderer interface.
    64  func (h *Renderer) Run() error {
    65  	if err := h.loadChart(); err != nil {
    66  		return err
    67  	}
    68  
    69  	h.started = true
    70  	return nil
    71  }
    72  
    73  // RenderManifest renders the current helm templates with the current values and returns the resulting YAML manifest string.
    74  func (h *Renderer) RenderManifest(values string) (string, error) {
    75  	if !h.started {
    76  		return "", fmt.Errorf("fileTemplateRenderer for %s not started in renderChart", h.componentName)
    77  	}
    78  	return renderChart(h.namespace, values, h.chart, nil, h.version)
    79  }
    80  
    81  // RenderManifestFiltered filters templates to render using the supplied filter function.
    82  func (h *Renderer) RenderManifestFiltered(values string, filter TemplateFilterFunc) (string, error) {
    83  	if !h.started {
    84  		return "", fmt.Errorf("fileTemplateRenderer for %s not started in renderChart", h.componentName)
    85  	}
    86  	return renderChart(h.namespace, values, h.chart, filter, h.version)
    87  }
    88  
    89  func GetFilesRecursive(f fs.FS, root string) ([]string, error) {
    90  	res := []string{}
    91  	err := fs.WalkDir(f, root, func(path string, d fs.DirEntry, err error) error {
    92  		if err != nil {
    93  			return err
    94  		}
    95  		if d.IsDir() {
    96  			return nil
    97  		}
    98  		res = append(res, path)
    99  		return nil
   100  	})
   101  	return res, err
   102  }
   103  
   104  // loadChart implements the TemplateRenderer interface.
   105  func (h *Renderer) loadChart() error {
   106  	fnames, err := GetFilesRecursive(h.files, h.dir)
   107  	if err != nil {
   108  		if os.IsNotExist(err) {
   109  			return fmt.Errorf("component %q does not exist", h.componentName)
   110  		}
   111  		return fmt.Errorf("list files: %v", err)
   112  	}
   113  	var bfs []*loader.BufferedFile
   114  	for _, fname := range fnames {
   115  		b, err := fs.ReadFile(h.files, fname)
   116  		if err != nil {
   117  			return fmt.Errorf("read file: %v", err)
   118  		}
   119  		// Helm expects unix / separator, but on windows this will be \
   120  		name := strings.ReplaceAll(stripPrefix(fname, h.dir), string(filepath.Separator), "/")
   121  		bf := &loader.BufferedFile{
   122  			Name: name,
   123  			Data: b,
   124  		}
   125  		bfs = append(bfs, bf)
   126  		scope.Debugf("Chart loaded: %s", bf.Name)
   127  	}
   128  
   129  	h.chart, err = loader.LoadFiles(bfs)
   130  	if err != nil {
   131  		return fmt.Errorf("load files: %v", err)
   132  	}
   133  	return nil
   134  }
   135  
   136  func builtinProfileToFilename(name string) string {
   137  	if name == "" {
   138  		return DefaultProfileFilename
   139  	}
   140  	return name + ".yaml"
   141  }
   142  
   143  func LoadValues(profileName string, chartsDir string) (string, error) {
   144  	path := strings.Join([]string{profilesRoot, builtinProfileToFilename(profileName)}, "/")
   145  	by, err := fs.ReadFile(manifests.BuiltinOrDir(chartsDir), path)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  	return string(by), nil
   150  }
   151  
   152  func readProfiles(chartsDir string) (map[string]bool, error) {
   153  	profiles := map[string]bool{}
   154  	f := manifests.BuiltinOrDir(chartsDir)
   155  	dir, err := fs.ReadDir(f, profilesRoot)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	for _, f := range dir {
   160  		trimmedString := strings.TrimSuffix(f.Name(), ".yaml")
   161  		if f.Name() != trimmedString {
   162  			profiles[trimmedString] = true
   163  		}
   164  	}
   165  	return profiles, nil
   166  }
   167  
   168  // stripPrefix removes the given prefix from prefix.
   169  func stripPrefix(path, prefix string) string {
   170  	pl := len(strings.Split(prefix, "/"))
   171  	pv := strings.Split(path, "/")
   172  	return strings.Join(pv[pl:], "/")
   173  }
   174  
   175  // list all the profiles.
   176  func ListProfiles(charts string) ([]string, error) {
   177  	profiles, err := readProfiles(charts)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	return util.StringBoolMapToSlice(profiles), nil
   182  }