pkg/helmshim/helm.go (111 lines of code) (raw):

package helmshim import ( "bytes" "encoding/json" "errors" "fmt" "io" "os" "github.com/Azure/eno/pkg/function" "helm.sh/helm/v3/pkg/action" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/yaml" ) var ( ErrChartNotFound = errors.New("the requested chart could not be loaded") ErrRenderAction = errors.New("the chart could not be rendered with the given values") ErrCannotParseChart = errors.New("helm produced a set of manifests that is not parseable") ErrConstructingValues = errors.New("error while constructing helm values") ) // MustRenderChart is the entrypoint to the Helm shim. // // The most basic shim cmd's main func only needs one line: // > helmshim.MustRenderChart(helmshim.ParseFlags()...) func MustRenderChart(opts ...RenderOption) { err := RenderChart(opts...) if err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) os.Exit(1) } } // isNullOrEmptyObject checks if the given unstructured object is equivalent to an empty K8S object. // This is used when then input helm chart includes an empty target (for example: empty yaml file with comments). func isNullOrEmptyObject(o *unstructured.Unstructured) bool { if o == nil { return true } if len(o.Object) > 0 { // if the object has any fields, it is not null return false } b, err := json.Marshal(o) if err != nil { return false } return string(b) == "null" || string(b) == "{}" } func RenderChart(opts ...RenderOption) error { a := action.NewInstall(&action.Configuration{}) a.ReleaseName = "eno-helm-shim" a.Namespace = "default" a.DryRun = true a.Replace = true a.ClientOnly = true a.IncludeCRDs = true o := &options{ Action: a, ValuesFunc: inputsToValues, ChartLoader: defaultChartLoader, } for _, opt := range opts { opt.apply(o) } if o.Reader == nil { var err error o.Reader, err = function.NewDefaultInputReader() if err != nil { return err } } var usingDefaultWriter bool if o.Writer == nil { usingDefaultWriter = true o.Writer = function.NewDefaultOutputWriter() } c, err := o.ChartLoader() if err != nil { return errors.Join(ErrChartNotFound, err) } vals, err := o.ValuesFunc(o.Reader) if err != nil { return errors.Join(ErrConstructingValues, err) } rel, err := a.Run(c, vals) if err != nil { return errors.Join(ErrRenderAction, err) } b := bytes.NewBufferString(rel.Manifest) // append manifest from hook for _, hook := range rel.Hooks { fmt.Fprintf(b, "---\n# Source: %s\n%s\n", hook.Name, hook.Manifest) } d := yaml.NewYAMLToJSONDecoder(b) for { m := &unstructured.Unstructured{} err = d.Decode(m) if err == io.EOF { break } else if err != nil { return errors.Join(ErrCannotParseChart, err) } if isNullOrEmptyObject(m) { continue } if err := o.Writer.Add(m); err != nil { return fmt.Errorf("adding object %s to output writer: %w", m, err) } } if usingDefaultWriter { return o.Writer.Write() } return nil } func inputsToValues(i *function.InputReader) (map[string]any, error) { m := map[string]any{} for k, o := range i.All() { m[k] = o.Object } return m, nil }