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.
28 utils "github.com/onap/multicloud-k8s/src/k8splugin/internal"
30 pkgerrors "github.com/pkg/errors"
31 "helm.sh/helm/v3/pkg/action"
32 "helm.sh/helm/v3/pkg/chart/loader"
33 "helm.sh/helm/v3/pkg/cli"
34 helmOptions "helm.sh/helm/v3/pkg/cli/values"
35 "helm.sh/helm/v3/pkg/getter"
36 "helm.sh/helm/v3/pkg/releaseutil"
37 "k8s.io/apimachinery/pkg/runtime/schema"
38 "k8s.io/apimachinery/pkg/runtime/serializer/json"
39 "k8s.io/apimachinery/pkg/util/validation"
40 k8syaml "k8s.io/apimachinery/pkg/util/yaml"
43 // Template is the interface for all helm templating commands
44 // Any backend implementation will implement this interface and will
45 // access the functionality via this.
46 // FIXME Template is not referenced anywhere
47 type Template interface {
48 GenerateKubernetesArtifacts(
51 values []string) ([]KubernetesResourceTemplate, []*Hook, error)
54 // TemplateClient implements the Template interface
55 // It will also be used to maintain any localized state
56 type TemplateClient struct {
57 emptyRegex *regexp.Regexp
63 // NewTemplateClient returns a new instance of TemplateClient
64 func NewTemplateClient(k8sversion, namespace, releasename string) *TemplateClient {
65 return &TemplateClient{
66 // emptyRegex defines template content that could be considered empty yaml-wise
67 emptyRegex: regexp.MustCompile(`(?m)\A(^(\s*#.*|\s*)$\n?)*\z`),
68 // defaultKubeVersion is the default value of --kube-version flag
69 kubeVersion: k8sversion,
70 kubeNameSpace: namespace,
71 releaseName: releasename,
75 // Combines valueFiles and values into a single values stream.
76 // values takes precedence over valueFiles
77 func (h *TemplateClient) processValues(valueFiles []string, values []string) (map[string]interface{}, error) {
79 providers := getter.All(settings)
80 options := helmOptions.Options{
81 ValueFiles: valueFiles,
84 base, err := options.MergeValues(providers)
92 // GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template
93 func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string,
94 values []string) ([]KubernetesResourceTemplate, []*Hook, error) {
96 var outputDir, chartPath, namespace, releaseName string
97 var retData []KubernetesResourceTemplate
100 releaseName = h.releaseName
101 namespace = h.kubeNameSpace
103 // verify chart path exists
104 if _, err := os.Stat(inputPath); err == nil {
105 if chartPath, err = filepath.Abs(inputPath); err != nil {
106 return retData, hookList, err
109 return retData, hookList, err
112 //Create a temp directory in the system temp folder
113 outputDir, err := ioutil.TempDir("", "helm-tmpl-")
115 return retData, hookList, pkgerrors.Wrap(err, "Got error creating temp dir")
119 namespace = "default"
122 // get combined values and create config
123 rawVals, err := h.processValues(valueFiles, values)
125 return retData, hookList, err
128 if msgs := validation.IsDNS1123Label(releaseName); releaseName != "" && len(msgs) > 0 {
129 return retData, hookList, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";"))
132 // Initialize the install client
133 client := action.NewInstall(&action.Configuration{})
135 client.ClientOnly = true
136 client.ReleaseName = releaseName
137 client.IncludeCRDs = true
138 client.DisableHooks = true //to ensure no duplicates in case of defined pre/post install hooks
140 // Check chart dependencies to make sure all are present in /charts
141 chartRequested, err := loader.Load(chartPath)
143 return retData, hookList, err
146 if chartRequested.Metadata.Type != "" && chartRequested.Metadata.Type != "application" {
147 return retData, hookList, fmt.Errorf(
148 "chart %q has an unsupported type and is not installable: %q",
149 chartRequested.Metadata.Name,
150 chartRequested.Metadata.Type,
154 client.Namespace = namespace
155 release, err := client.Run(chartRequested, rawVals)
157 return retData, hookList, err
159 // SplitManifests returns integer-sortable so that manifests get output
160 // in the same order as the input by `BySplitManifestsOrder`.
161 rmap := releaseutil.SplitManifests(release.Manifest)
162 // We won't get any meaningful hooks from here
163 _, m, err := releaseutil.SortManifests(rmap, nil, releaseutil.InstallOrder)
165 return retData, hookList, err
167 for _, k := range m {
169 b := filepath.Base(k.Name)
170 if b == "NOTES.txt" {
173 if strings.HasPrefix(b, "_") {
176 // blank template after execution
177 if h.emptyRegex.MatchString(data) {
180 mfilePath := filepath.Join(outputDir, k.Name)
181 utils.EnsureDirectory(mfilePath)
182 err = ioutil.WriteFile(mfilePath, []byte(k.Content), 0600)
184 return retData, hookList, err
186 gvk, err := getGroupVersionKind(data)
188 return retData, hookList, err
190 kres := KubernetesResourceTemplate{
194 retData = append(retData, kres)
196 for _, h := range release.Hooks {
197 hFilePath := filepath.Join(outputDir, h.Name)
198 utils.EnsureDirectory(hFilePath)
199 err = ioutil.WriteFile(hFilePath, []byte(h.Manifest), 0600)
201 return retData, hookList, err
203 gvk, err := getGroupVersionKind(h.Manifest)
205 return retData, hookList, err
207 hookList = append(hookList, &Hook{*h, KubernetesResourceTemplate{gvk, hFilePath}})
209 return retData, hookList, nil
212 func getGroupVersionKind(data string) (schema.GroupVersionKind, error) {
213 out, err := k8syaml.ToJSON([]byte(data))
215 return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Converting yaml to json:\n"+data)
218 simpleMeta := json.SimpleMetaFactory{}
219 gvk, err := simpleMeta.Interpret(out)
221 return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Parsing apiversion and kind")