| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package helm |
| |
| import ( |
| "fmt" |
| "io/fs" |
| "os" |
| "path/filepath" |
| "strings" |
| ) |
| |
| import ( |
| "helm.sh/helm/v3/pkg/chart" |
| "helm.sh/helm/v3/pkg/chart/loader" |
| "k8s.io/apimachinery/pkg/version" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/manifests" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/util" |
| ) |
| |
| const ( |
| // DefaultProfileFilename is the name of the default profile yaml file. |
| DefaultProfileFilename = "default.yaml" |
| ChartsSubdirName = "charts" |
| profilesRoot = "profiles" |
| ) |
| |
| // Renderer is a helm template renderer for a fs.FS. |
| type Renderer struct { |
| namespace string |
| componentName string |
| chart *chart.Chart |
| started bool |
| files fs.FS |
| dir string |
| // Kubernetes cluster version |
| version *version.Info |
| } |
| |
| // NewFileTemplateRenderer creates a TemplateRenderer with the given parameters and returns a pointer to it. |
| // helmChartDirPath must be an absolute file path to the root of the helm charts. |
| func NewGenericRenderer(files fs.FS, dir, componentName, namespace string, version *version.Info) *Renderer { |
| return &Renderer{ |
| namespace: namespace, |
| componentName: componentName, |
| dir: dir, |
| files: files, |
| version: version, |
| } |
| } |
| |
| // Run implements the TemplateRenderer interface. |
| func (h *Renderer) Run() error { |
| if err := h.loadChart(); err != nil { |
| return err |
| } |
| |
| h.started = true |
| return nil |
| } |
| |
| // RenderManifest renders the current helm templates with the current values and returns the resulting YAML manifest string. |
| func (h *Renderer) RenderManifest(values string) (string, error) { |
| if !h.started { |
| return "", fmt.Errorf("fileTemplateRenderer for %s not started in renderChart", h.componentName) |
| } |
| return renderChart(h.namespace, values, h.chart, nil, h.version) |
| } |
| |
| // RenderManifestFiltered filters templates to render using the supplied filter function. |
| func (h *Renderer) RenderManifestFiltered(values string, filter TemplateFilterFunc) (string, error) { |
| if !h.started { |
| return "", fmt.Errorf("fileTemplateRenderer for %s not started in renderChart", h.componentName) |
| } |
| return renderChart(h.namespace, values, h.chart, filter, h.version) |
| } |
| |
| func GetFilesRecursive(f fs.FS, root string) ([]string, error) { |
| res := []string{} |
| err := fs.WalkDir(f, root, func(path string, d fs.DirEntry, err error) error { |
| if err != nil { |
| return err |
| } |
| if d.IsDir() { |
| return nil |
| } |
| res = append(res, path) |
| return nil |
| }) |
| return res, err |
| } |
| |
| // loadChart implements the TemplateRenderer interface. |
| func (h *Renderer) loadChart() error { |
| fnames, err := GetFilesRecursive(h.files, h.dir) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return fmt.Errorf("component %q does not exist", h.componentName) |
| } |
| return fmt.Errorf("list files: %v", err) |
| } |
| var bfs []*loader.BufferedFile |
| for _, fname := range fnames { |
| b, err := fs.ReadFile(h.files, fname) |
| if err != nil { |
| return fmt.Errorf("read file: %v", err) |
| } |
| // Helm expects unix / separator, but on windows this will be \ |
| name := strings.ReplaceAll(stripPrefix(fname, h.dir), string(filepath.Separator), "/") |
| bf := &loader.BufferedFile{ |
| Name: name, |
| Data: b, |
| } |
| bfs = append(bfs, bf) |
| scope.Debugf("Chart loaded: %s", bf.Name) |
| } |
| |
| h.chart, err = loader.LoadFiles(bfs) |
| if err != nil { |
| return fmt.Errorf("load files: %v", err) |
| } |
| return nil |
| } |
| |
| func builtinProfileToFilename(name string) string { |
| if name == "" { |
| return DefaultProfileFilename |
| } |
| return name + ".yaml" |
| } |
| |
| func LoadValues(profileName string, chartsDir string) (string, error) { |
| path := strings.Join([]string{profilesRoot, builtinProfileToFilename(profileName)}, "/") |
| by, err := fs.ReadFile(manifests.BuiltinOrDir(chartsDir), path) |
| if err != nil { |
| return "", err |
| } |
| return string(by), nil |
| } |
| |
| func readProfiles(chartsDir string) (map[string]bool, error) { |
| profiles := map[string]bool{} |
| f := manifests.BuiltinOrDir(chartsDir) |
| dir, err := fs.ReadDir(f, profilesRoot) |
| if err != nil { |
| return nil, err |
| } |
| for _, f := range dir { |
| trimmedString := strings.TrimSuffix(f.Name(), ".yaml") |
| if f.Name() != trimmedString { |
| profiles[trimmedString] = true |
| } |
| } |
| return profiles, nil |
| } |
| |
| // stripPrefix removes the the given prefix from prefix. |
| func stripPrefix(path, prefix string) string { |
| pl := len(strings.Split(prefix, "/")) |
| pv := strings.Split(path, "/") |
| return strings.Join(pv[pl:], "/") |
| } |
| |
| // list all the profiles. |
| func ListProfiles(charts string) ([]string, error) { |
| profiles, err := readProfiles(charts) |
| if err != nil { |
| return nil, err |
| } |
| return util.StringBoolMapToSlice(profiles), nil |
| } |