pkg/tasks/template/render.go (138 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 // // http://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 template import ( "context" "fmt" "io" "io/fs" "github.com/hashicorp/vault/api" "github.com/elastic/harp/pkg/bundle" "github.com/elastic/harp/pkg/sdk/cmdutil" "github.com/elastic/harp/pkg/sdk/fsutil" "github.com/elastic/harp/pkg/sdk/types" "github.com/elastic/harp/pkg/tasks" tplcmdutil "github.com/elastic/harp/pkg/template/cmdutil" "github.com/elastic/harp/pkg/template/engine" "github.com/elastic/harp/pkg/vault/kv" ) // RenderTask implements single template rendering task. type RenderTask struct { InputReader tasks.ReaderProvider OutputWriter tasks.WriterProvider ValueFiles []string SecretLoaders []string Values []string StringValues []string FileValues []string LeftDelims string RightDelims string AltDelims bool RootPath string } // Run the task. func (t *RenderTask) Run(ctx context.Context) error { var ( reader io.Reader err error ) // Create input reader reader, err = t.InputReader(ctx) if err != nil { return fmt.Errorf("unable to create input reader: %w", err) } // Drain reader body, err := io.ReadAll(reader) if err != nil { return fmt.Errorf("unable to drain input template reader: %w", err) } var fileRootFS fs.FS if t.RootPath != "" { var errRootFS error fileRootFS, errRootFS = fsutil.From(t.RootPath) if errRootFS != nil { return fmt.Errorf("unable load files filesystem: %w", errRootFS) } } // Prepare render context renderCtx, err := prepareRenderContext(&renderContextConfig{ ValueFiles: t.ValueFiles, SecretLoaders: t.SecretLoaders, Values: t.Values, StringValues: t.StringValues, FileValues: t.FileValues, LeftDelims: t.LeftDelims, RightDelims: t.RightDelims, AltDelims: t.AltDelims, FileRootPath: fileRootFS, }) if err != nil { return fmt.Errorf("unable to prepare rendering context: %w", err) } // Compile and execute template out, err := engine.RenderContext(renderCtx, string(body)) if err != nil { return fmt.Errorf("unable to produce output content: %w", err) } // Create output writer writer, err := t.OutputWriter(ctx) if err != nil { return fmt.Errorf("unable to create output writer: %w", err) } // Write rendered content fmt.Fprintf(writer, "%s", out) // No error return nil } // ----------------------------------------------------------------------------- type renderContextConfig struct { ValueFiles []string SecretLoaders []string Values []string StringValues []string FileValues []string LeftDelims string RightDelims string AltDelims bool FileRootPath fs.FS } func prepareRenderContext(cfg *renderContextConfig) (engine.Context, error) { // Load values valueOpts := tplcmdutil.ValueOptions{ ValueFiles: cfg.ValueFiles, Values: cfg.Values, StringValues: cfg.StringValues, FileValues: cfg.FileValues, } values, err := valueOpts.MergeValues() if err != nil { return nil, fmt.Errorf("unable to process input values: %w", err) } // Load files var files engine.Files if !types.IsNil(cfg.FileRootPath) { var errFs error files, errFs = tplcmdutil.Files(cfg.FileRootPath, ".") if errFs != nil { return nil, fmt.Errorf("unable to process files: %w", errFs) } } // If alternative delimiters is used if cfg.AltDelims { cfg.LeftDelims = "[[" cfg.RightDelims = "]]" } // Process secret readers secretReaders := []engine.SecretReaderFunc{} for _, sr := range cfg.SecretLoaders { if sr == "vault" { // Initialize Vault connection vaultClient, errVault := api.NewClient(api.DefaultConfig()) if errVault != nil { return nil, fmt.Errorf("unable to initialize vault secret loader: %w", errVault) } secretReaders = append(secretReaders, kv.SecretGetter(vaultClient)) continue } // Read container containerReader, errLoader := cmdutil.Reader(sr) if errLoader != nil { return nil, fmt.Errorf("unable to read secret container: %w", errLoader) } // Load container b, errBundle := bundle.FromContainerReader(containerReader) if errBundle != nil { return nil, fmt.Errorf("unable to decode secret container: %w", err) } // Append secret loader secretReaders = append(secretReaders, bundle.SecretReader(b)) } // Create rendering context renderCtx := engine.NewContext( engine.WithName("template"), engine.WithDelims(cfg.LeftDelims, cfg.RightDelims), engine.WithValues(values), engine.WithFiles(files), engine.WithSecretReaders(secretReaders...), ) // No error return renderCtx, nil }