2 * Copyright 2018 Intel Corporation, Inc
3 * Copyright © 2021 Samsung Electronics
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
22 "github.com/onap/multicloud-k8s/src/k8splugin/internal/utils"
29 pkgerrors "github.com/pkg/errors"
30 "helm.sh/helm/v3/pkg/action"
31 "helm.sh/helm/v3/pkg/chart/loader"
32 "helm.sh/helm/v3/pkg/cli"
33 helmOptions "helm.sh/helm/v3/pkg/cli/values"
34 "helm.sh/helm/v3/pkg/getter"
35 "helm.sh/helm/v3/pkg/releaseutil"
36 "k8s.io/apimachinery/pkg/runtime/schema"
37 "k8s.io/apimachinery/pkg/runtime/serializer/json"
38 "k8s.io/apimachinery/pkg/util/validation"
39 k8syaml "k8s.io/apimachinery/pkg/util/yaml"
42 // Template is the interface for all helm templating commands
43 // Any backend implementation will implement this interface and will
44 // access the functionality via this.
45 // FIXME Template is not referenced anywhere
46 type Template interface {
47 GenerateKubernetesArtifacts(
50 values []string) ([]KubernetesResourceTemplate, []*Hook, error)
53 // TemplateClient implements the Template interface
54 // It will also be used to maintain any localized state
55 type TemplateClient struct {
56 emptyRegex *regexp.Regexp
62 // NewTemplateClient returns a new instance of TemplateClient
63 func NewTemplateClient(k8sversion, namespace, releasename string) *TemplateClient {
64 return &TemplateClient{
65 // emptyRegex defines template content that could be considered empty yaml-wise
66 emptyRegex: regexp.MustCompile(`(?m)\A(^(\s*#.*|\s*)$\n?)*\z`),
67 // defaultKubeVersion is the default value of --kube-version flag
68 kubeVersion: k8sversion,
69 kubeNameSpace: namespace,
70 releaseName: releasename,
74 // Combines valueFiles and values into a single values stream.
75 // values takes precedence over valueFiles
76 func (h *TemplateClient) processValues(valueFiles []string, values []string) (map[string]interface{}, error) {
78 providers := getter.All(settings)
79 options := helmOptions.Options{
80 ValueFiles: valueFiles,
83 base, err := options.MergeValues(providers)
91 // GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template
92 func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string,
93 values []string) ([]KubernetesResourceTemplate, []*Hook, error) {
95 var outputDir, chartPath, namespace, releaseName string
96 var retData []KubernetesResourceTemplate
99 releaseName = h.releaseName
100 namespace = h.kubeNameSpace
102 // verify chart path exists
103 if _, err := os.Stat(inputPath); err == nil {
104 if chartPath, err = filepath.Abs(inputPath); err != nil {
105 return retData, hookList, err
108 return retData, hookList, err
111 //Create a temp directory in the system temp folder
112 outputDir, err := ioutil.TempDir("", "helm-tmpl-")
114 return retData, hookList, pkgerrors.Wrap(err, "Got error creating temp dir")
118 namespace = "default"
121 // get combined values and create config
122 rawVals, err := h.processValues(valueFiles, values)
124 return retData, hookList, err
127 if msgs := validation.IsDNS1123Label(releaseName); releaseName != "" && len(msgs) > 0 {
128 return retData, hookList, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";"))
131 // Initialize the install client
132 client := action.NewInstall(&action.Configuration{})
134 client.ClientOnly = true
135 client.ReleaseName = releaseName
136 client.IncludeCRDs = true
137 client.DisableHooks = true //to ensure no duplicates in case of defined pre/post install hooks
139 // Check chart dependencies to make sure all are present in /charts
140 chartRequested, err := loader.Load(chartPath)
142 return retData, hookList, err
145 if chartRequested.Metadata.Type != "" && chartRequested.Metadata.Type != "application" {
146 return retData, hookList, fmt.Errorf(
147 "chart %q has an unsupported type and is not installable: %q",
148 chartRequested.Metadata.Name,
149 chartRequested.Metadata.Type,
153 client.Namespace = namespace
154 release, err := client.Run(chartRequested, rawVals)
156 return retData, hookList, err
158 // SplitManifests returns integer-sortable so that manifests get output
159 // in the same order as the input by `BySplitManifestsOrder`.
160 rmap := releaseutil.SplitManifests(release.Manifest)
161 // We won't get any meaningful hooks from here
162 _, m, err := releaseutil.SortManifests(rmap, nil, releaseutil.InstallOrder)
164 return retData, hookList, err
166 for _, k := range m {
168 b := filepath.Base(k.Name)
169 if b == "NOTES.txt" {
172 if strings.HasPrefix(b, "_") {
175 // blank template after execution
176 if h.emptyRegex.MatchString(data) {
179 mfilePath := filepath.Join(outputDir, k.Name)
180 utils.EnsureDirectory(mfilePath)
181 err = ioutil.WriteFile(mfilePath, []byte(k.Content), 0600)
183 return retData, hookList, err
185 gvk, err := getGroupVersionKind(data)
187 return retData, hookList, err
189 kres := KubernetesResourceTemplate{
193 retData = append(retData, kres)
195 for _, h := range release.Hooks {
196 hFilePath := filepath.Join(outputDir, h.Name)
197 utils.EnsureDirectory(hFilePath)
198 err = ioutil.WriteFile(hFilePath, []byte(h.Manifest), 0600)
200 return retData, hookList, err
202 gvk, err := getGroupVersionKind(h.Manifest)
204 return retData, hookList, err
206 hookList = append(hookList, &Hook{*h, KubernetesResourceTemplate{gvk, hFilePath}})
208 return retData, hookList, nil
211 func getGroupVersionKind(data string) (schema.GroupVersionKind, error) {
212 out, err := k8syaml.ToJSON([]byte(data))
214 return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Converting yaml to json:\n"+data)
217 simpleMeta := json.SimpleMetaFactory{}
218 gvk, err := simpleMeta.Interpret(out)
220 return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Parsing apiversion and kind")