blob: 056885a444ddcf86db91bc069e9a8b54e2a821b8 [file] [log] [blame]
// 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
}