package chart // Author: Weisen Pan // Date: 2023-10-24 import ( "bytes" "fmt" "path" "strings" simontype "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/type" "helm.sh/helm/v3/knets_pkg/chart" "helm.sh/helm/v3/knets_pkg/chart/loader" "helm.sh/helm/v3/knets_pkg/chartutil" "helm.sh/helm/v3/knets_pkg/engine" "helm.sh/helm/v3/knets_pkg/releaseutil" ) // ProcessChart parses a chart and returns rendered resources. func ProcessChart(name string, chartPath string) ([]string, error) { // Load the requested chart from the provided path. chartRequested, err := loader.Load(chartPath) if err != nil { return nil, err } chartRequested.Metadata.Name = name // Check if the chart is installable. if err := checkIfInstallable(chartRequested); err != nil { return nil, err } // TODO: Define values to be processed. var vals map[string]interface{} // Process chart dependencies. if err := chartutil.ProcessDependencies(chartRequested, vals); err != nil { return nil, err } // Convert values and render resources. valuesToRender, err := ToRenderValues(chartRequested, vals) if err != nil { return nil, err } return renderResources(chartRequested, valuesToRender, true) } // checkIfInstallable validates if a chart can be installed. // Application chart type is the only type considered installable. func checkIfInstallable(ch *chart.Chart) error { switch ch.Metadata.Type { case "", "application": return nil } return fmt.Errorf("%s charts are not installable", ch.Metadata.Type) } // ToRenderValues composes the struct from the data coming from the Releases, Charts, and Values files. func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}) (chartutil.Values, error) { top := map[string]interface{}{ "Chart": chrt.Metadata, "Release": map[string]interface{}{ "Name": chrt.Name(), "Namespace": "default", "Revision": 1, "Service": "Helm", }, } vals, err := chartutil.CoalesceValues(chrt, chrtVals) if err != nil { return top, err } if err := chartutil.ValidateAgainstSchema(chrt, vals); err != nil { errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" return top, fmt.Errorf(errFmt, err.Error()) } top["Values"] = vals return top, nil } // renderResources renders the resources from the chart. func renderResources(ch *chart.Chart, values chartutil.Values, subNotes bool) ([]string, error) { files, err := engine.Render(ch, values) if err != nil { return nil, err } // NOTES.txt gets rendered like all the other files but is treated separately. var notesBuffer bytes.Buffer for k, v := range files { if strings.HasSuffix(k, simontype.NotesFileSuffix) { if subNotes || (k == path.Join(ch.Name(), "templates", simontype.NotesFileSuffix)) { // If the buffer contains data, add a newline before adding more. if notesBuffer.Len() > 0 { notesBuffer.WriteString("\n") } notesBuffer.WriteString(v) } delete(files, k) } } // Sort hooks and manifests and return only manifests. var yamlStr []string _, manifests, err := releaseutil.SortManifests(files, []string{}, releaseutil.InstallOrder) if err != nil { return nil, err } for _, item := range manifests { yamlStr = append(yamlStr, item.Content) } return yamlStr, nil }