mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-05-21 00:24:08 +00:00
211 lines
5.3 KiB
Go
211 lines
5.3 KiB
Go
package chartrender
|
|
|
|
import (
|
|
"bufio"
|
|
"embed"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"helm.sh/helm/v3/pkg/chart"
|
|
"helm.sh/helm/v3/pkg/chart/loader"
|
|
"helm.sh/helm/v3/pkg/chartutil"
|
|
"helm.sh/helm/v3/pkg/engine"
|
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
"k8s.io/apimachinery/pkg/util/json"
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
"k8s.io/client-go/kubernetes/scheme"
|
|
"k8s.io/klog/v2"
|
|
|
|
operatorv1 "open-cluster-management.io/api/operator/v1"
|
|
|
|
clustermanagerchart "open-cluster-management.io/ocm/deploy/cluster-manager/chart"
|
|
klusterletchart "open-cluster-management.io/ocm/deploy/klusterlet/chart"
|
|
)
|
|
|
|
var chartScheme = runtime.NewScheme()
|
|
var decoder runtime.Decoder
|
|
|
|
func init() {
|
|
_ = scheme.AddToScheme(chartScheme)
|
|
_ = apiextensionsv1.AddToScheme(chartScheme)
|
|
_ = apiextensionsv1beta1.AddToScheme(chartScheme)
|
|
_ = operatorv1.AddToScheme(chartScheme)
|
|
|
|
decoder = serializer.NewCodecFactory(chartScheme).UniversalDeserializer()
|
|
}
|
|
|
|
func NewDefaultClusterManagerChartConfig() *clustermanagerchart.ChartConfig {
|
|
return &clustermanagerchart.ChartConfig{
|
|
ReplicaCount: 3,
|
|
CreateBootstrapToken: false,
|
|
}
|
|
}
|
|
|
|
func NewDefaultKlusterletChartConfig() *klusterletchart.ChartConfig {
|
|
return &klusterletchart.ChartConfig{
|
|
ReplicaCount: 3,
|
|
}
|
|
}
|
|
|
|
func RenderChart[T *clustermanagerchart.ChartConfig | *klusterletchart.ChartConfig](config T,
|
|
namespace string, chartName string, fs embed.FS) ([]runtime.Object, error) {
|
|
// chartName is the prefix of chart path here
|
|
operatorChart, err := LoadChart(fs, chartName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load %s chart: %w", chartName, err)
|
|
}
|
|
|
|
configValues, err := JsonStructToValues(config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating values for chartConfig: %v", err)
|
|
}
|
|
|
|
releaseOptions := chartutil.ReleaseOptions{
|
|
Name: chartName,
|
|
Namespace: namespace,
|
|
}
|
|
|
|
values, err := chartutil.ToRenderValues(operatorChart, configValues,
|
|
releaseOptions, &chartutil.Capabilities{})
|
|
if err != nil {
|
|
klog.Errorf("failed to render helm chart with values %v. err:%v", values, err)
|
|
return nil, err
|
|
}
|
|
|
|
objects, err := renderManifests(operatorChart, values)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error rendering cluster manager chart: %v", err)
|
|
}
|
|
|
|
return objects, nil
|
|
}
|
|
|
|
func getFiles(manifestFS embed.FS) ([]string, error) {
|
|
var res []string
|
|
err := fs.WalkDir(manifestFS, ".", 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
|
|
}
|
|
|
|
func stripPrefix(chartPrefix, path string) string {
|
|
prefixNoPathSeparatorSuffix := strings.TrimSuffix(chartPrefix, string(filepath.Separator))
|
|
chartPrefixLen := len(strings.Split(prefixNoPathSeparatorSuffix, string(filepath.Separator)))
|
|
pathValues := strings.Split(path, string(filepath.Separator))
|
|
return strings.Join(pathValues[chartPrefixLen:], string(filepath.Separator))
|
|
}
|
|
|
|
func LoadChart(chartFS embed.FS, chartPrefix string) (*chart.Chart, error) {
|
|
files, err := getFiles(chartFS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var bfs []*loader.BufferedFile
|
|
for _, fileName := range files {
|
|
b, err := fs.ReadFile(chartFS, fileName)
|
|
if err != nil {
|
|
klog.Errorf("failed to read file %v. err:%v", fileName, err)
|
|
return nil, err
|
|
}
|
|
if !strings.HasPrefix(fileName, chartPrefix) {
|
|
continue
|
|
}
|
|
bf := &loader.BufferedFile{
|
|
Name: stripPrefix(chartPrefix, fileName),
|
|
Data: b,
|
|
}
|
|
bfs = append(bfs, bf)
|
|
}
|
|
|
|
userChart, err := loader.LoadFiles(bfs)
|
|
if err != nil {
|
|
klog.Errorf("failed to load chart. err:%v", err)
|
|
return nil, err
|
|
}
|
|
return userChart, nil
|
|
}
|
|
|
|
// JsonStructToValues converts the given json struct to a Values
|
|
func JsonStructToValues(a interface{}) (chartutil.Values, error) {
|
|
raw, err := json.Marshal(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vals, err := chartutil.ReadValues(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return vals, nil
|
|
|
|
}
|
|
|
|
func renderManifests(chart *chart.Chart, values chartutil.Values) ([]runtime.Object, error) {
|
|
var objects []runtime.Object
|
|
crds := chart.CRDObjects()
|
|
for _, crd := range crds {
|
|
klog.V(4).Infof("%v/n", crd.File.Data)
|
|
|
|
object, _, err := decoder.Decode(crd.File.Data, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
objects = append(objects, object)
|
|
}
|
|
|
|
helmEngine := engine.Engine{
|
|
Strict: true,
|
|
LintMode: false,
|
|
}
|
|
|
|
templates, err := helmEngine.Render(chart, values)
|
|
if err != nil {
|
|
return objects, err
|
|
}
|
|
|
|
for k, data := range templates {
|
|
if len(data) == 0 {
|
|
continue
|
|
}
|
|
klog.V(4).Infof("rendered template: %v", data)
|
|
|
|
yamlReader := yaml.NewYAMLReader(bufio.NewReader(strings.NewReader(data)))
|
|
for {
|
|
b, err := yamlReader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(b) != 0 {
|
|
object, _, err := decoder.Decode(b, nil, nil)
|
|
if err != nil {
|
|
// In some conditions, resources will not be rendered
|
|
if runtime.IsMissingKind(err) {
|
|
klog.V(4).Infof("Skipping template %v, reason: %v", k, err)
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
objects = append(objects, object)
|
|
}
|
|
}
|
|
}
|
|
return objects, nil
|
|
}
|