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 }