github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/util/env_template.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package util
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  	"reflect"
    25  	"sort"
    26  	"strings"
    27  	"text/template"
    28  
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    30  )
    31  
    32  // For testing
    33  var (
    34  	OSEnviron = os.Environ
    35  	funcsMap  = template.FuncMap{
    36  		"default": defaultFunc,
    37  	}
    38  )
    39  
    40  // ExpandEnvTemplate parses and executes template s with an optional environment map
    41  func ExpandEnvTemplate(s string, envMap map[string]string) (string, error) {
    42  	tmpl, err := ParseEnvTemplate(s)
    43  	if err != nil {
    44  		return "", fmt.Errorf("unable to parse template: %q: %w", s, err)
    45  	}
    46  	return ExecuteEnvTemplate(tmpl, envMap)
    47  }
    48  
    49  // ExpandEnvTemplateOrFail parses and executes template s with an optional environment map, and errors if a reference cannot be satisfied.
    50  func ExpandEnvTemplateOrFail(s string, envMap map[string]string) (string, error) {
    51  	tmpl, err := ParseEnvTemplate(s)
    52  	if err != nil {
    53  		return "", fmt.Errorf("unable to parse template: %q: %w", s, err)
    54  	}
    55  	tmpl = tmpl.Option("missingkey=error")
    56  	return ExecuteEnvTemplate(tmpl, envMap)
    57  }
    58  
    59  // ParseEnvTemplate is a simple wrapper to parse an env template
    60  func ParseEnvTemplate(t string) (*template.Template, error) {
    61  	return template.New("envTemplate").Funcs(funcsMap).Parse(t)
    62  }
    63  
    64  // ExecuteEnvTemplate executes an envTemplate based on OS environment variables and a custom map
    65  func ExecuteEnvTemplate(envTemplate *template.Template, customMap map[string]string) (string, error) {
    66  	envMap := map[string]string{}
    67  	for _, env := range OSEnviron() {
    68  		kvp := strings.SplitN(env, "=", 2)
    69  		envMap[kvp[0]] = kvp[1]
    70  	}
    71  
    72  	for k, v := range customMap {
    73  		envMap[k] = v
    74  	}
    75  
    76  	var buf bytes.Buffer
    77  	log.Entry(context.TODO()).Debugf("Executing template %v with environment %v", envTemplate, envMap)
    78  	if err := envTemplate.Execute(&buf, envMap); err != nil {
    79  		return "", err
    80  	}
    81  	return buf.String(), nil
    82  }
    83  
    84  // EvaluateEnvTemplateMap parses and executes all map values as templates based on OS environment variables
    85  func EvaluateEnvTemplateMap(args map[string]*string) (map[string]*string, error) {
    86  	return EvaluateEnvTemplateMapWithEnv(args, nil)
    87  }
    88  
    89  // EvaluateEnvTemplateMapWithEnv parses and executes all map values as templates based on OS and custom environment variables
    90  func EvaluateEnvTemplateMapWithEnv(args map[string]*string, env map[string]string) (map[string]*string, error) {
    91  	if args == nil {
    92  		return nil, nil
    93  	}
    94  
    95  	evaluated := map[string]*string{}
    96  	for k, v := range args {
    97  		if v == nil {
    98  			evaluated[k] = nil
    99  			continue
   100  		}
   101  
   102  		value, err := ExpandEnvTemplate(*v, env)
   103  		if err != nil {
   104  			return nil, fmt.Errorf("unable to get value for key %q: %w", k, err)
   105  		}
   106  
   107  		evaluated[k] = &value
   108  	}
   109  
   110  	return evaluated, nil
   111  }
   112  
   113  // MapToFlag parses all map values and returns them as `key=value` with the given flag
   114  // Example: --my-flag key0=value0 --my-flag key1=value1  --my-flag key2=value2
   115  func MapToFlag(m map[string]*string, flag string) ([]string, error) {
   116  	kv, err := EvaluateEnvTemplateMap(m)
   117  	if err != nil {
   118  		return nil, fmt.Errorf("unable to evaluate build args: %w", err)
   119  	}
   120  
   121  	var keys []string
   122  	for k := range kv {
   123  		keys = append(keys, k)
   124  	}
   125  	sort.Strings(keys)
   126  
   127  	var kvFlags []string
   128  	for _, k := range keys {
   129  		v := kv[k]
   130  		if v == nil {
   131  			kvFlags = append(kvFlags, flag, k)
   132  		} else {
   133  			kvFlags = append(kvFlags, flag, fmt.Sprintf("%s=%s", k, *v))
   134  		}
   135  	}
   136  
   137  	return kvFlags, nil
   138  }
   139  
   140  // defaultFunc is a template function that behaves as sprig's default function.
   141  // See https://masterminds.github.io/sprig/defaults.html#default
   142  func defaultFunc(dflt, value interface{}) interface{} {
   143  	if value == nil {
   144  		return dflt
   145  	}
   146  	v := reflect.ValueOf(value)
   147  	switch v.Kind() {
   148  	case reflect.Array, reflect.Slice:
   149  		if v.Len() == 0 {
   150  			return dflt
   151  		}
   152  	case reflect.Ptr:
   153  		if v.IsNil() {
   154  			return dflt
   155  		}
   156  	default:
   157  		if v.IsZero() {
   158  			return dflt
   159  		}
   160  	}
   161  	return value
   162  }