pkg/options/gotmpl/go_template_applier.go (100 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package gotmpl creates objects from ObjectTemplate objects for // ObjectTemplates of type "go-template". Once the options are applied to the // go template, the ObjectTemplate is removed from the component's list of // objects. // // The templates are assumed to be YAML. package gotmpl import ( "bytes" "fmt" "regexp" bundle "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/apis/bundle/v1alpha1" "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/converter" "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/internal" "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/options" "github.com/GoogleCloudPlatform/k8s-cluster-bundle/pkg/options/openapi" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var ( multiDoc = regexp.MustCompile("(^|\n)---(\n|$)") onlyWhitespace = regexp.MustCompile(`^\s*$`) ) // ApplierConfig is a config option that can be passed to NewApplier. type ApplierConfig func(*applier) // applier applies options via go-templating for objects stored as raw strings // in config maps. This applier only applies to `RawTextFiles` that have been // inlined as part of the component inlining process. type applier struct { goTmplOptions []string useSafeYAMLTemplater bool } // WithGoTmplOptions modifies NewApplier so that the returned Applier uses the // specified Go text/template options. func WithGoTmplOptions(goTmplOptions ...string) ApplierConfig { return func(a *applier) { a.goTmplOptions = append(a.goTmplOptions, goTmplOptions...) } } // WithSafeYAML modifies the applier behavior to use the safetext YAML // templater. // // This overrides behavior specified in the template. func WithSafeYAMLTemplaterOverride() ApplierConfig { return func(a *applier) { a.useSafeYAMLTemplater = true } } // NewApplier creates a new options applier instance using the specified // ApplierConfigs. func NewApplier(opts ...ApplierConfig) options.Applier { a := &applier{} for _, opt := range opts { opt(a) } return a } // ApplyOptions applies options to the raw-text that's been inlined. To be // precise, this method looks for ConfigMaps objects with the inline-annotation. // // Each key-value pair in the config map is treated as a separate go template // that represents a single object. Once parameters are applied via the go // template, the objects are parsed as unstructured objects and added to the // component's object list. The original ConfigMap is not included in the final // component. func (m *applier) ApplyOptions(comp *bundle.Component, opts options.JSONOptions) (*bundle.Component, error) { // Make a copy to avoid confusing behavior. comp = comp.DeepCopy() matched, notMatched := options.PartitionObjectTemplates(comp.Spec.Objects, string(bundle.TemplateTypeGo)) newObjs, err := options.ApplyCommon(comp.ComponentReference(), matched, opts, m.applyOptions) if err != nil { return comp, err } comp.Spec.Objects = append(notMatched, newObjs...) return comp, nil } func (m *applier) applyOptions(obj *unstructured.Unstructured, ref bundle.ComponentReference, opts options.JSONOptions) ([]*unstructured.Unstructured, error) { objTmpl := &bundle.ObjectTemplate{} err := converter.FromUnstructured(obj).ToObject(objTmpl) if err != nil { return nil, err } if objTmpl.OptionsSchema != nil { opts, err = openapi.ApplyDefaults(opts, objTmpl.OptionsSchema) if err != nil { return nil, fmt.Errorf("applying schema defaults for object template named %q: %v", obj.GetName(), err) } } tmplFuncs := make(map[string]interface{}) useSafeYAMLTemplater := m.useSafeYAMLTemplater if internal.HasSafeYAMLAnnotation(objTmpl.ObjectMeta) { useSafeYAMLTemplater = true } tmpl, err := internal.NewTemplater(ref.ComponentName+"-"+obj.GetName(), objTmpl.Template, tmplFuncs, useSafeYAMLTemplater) if err != nil { return nil, fmt.Errorf("error parsing template for object %q: %v", obj.GetName(), err) } for _, goTmplOpt := range m.goTmplOptions { tmpl.Option(goTmplOpt) } var buf bytes.Buffer err = tmpl.Execute(&buf, opts) if err != nil { return nil, fmt.Errorf("error executing template for object %v: %v", obj.GetName(), err) } // performed by go-yaml: https://godoc.org/gopkg.in/yaml.v2 var out []*unstructured.Unstructured yamlStr := buf.String() if multiDoc.MatchString(yamlStr) { docs := multiDoc.Split(string(yamlStr), -1) for _, doc := range docs { if onlyWhitespace.MatchString(doc) { continue } uns, err := converter.FromYAMLString(doc).ToUnstructured() if err != nil { return nil, err } out = append(out, uns) } } else { uns, err := converter.FromYAMLString(yamlStr).ToUnstructured() if err != nil { return nil, err } out = append(out, uns) } return out, err }