istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/bootstrap/instance.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bootstrap
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path"
    23  	"strings"
    24  	"text/template"
    25  
    26  	"github.com/Masterminds/sprig/v3"
    27  
    28  	"istio.io/istio/pkg/env"
    29  	"istio.io/istio/pkg/log"
    30  	"istio.io/istio/pkg/model"
    31  )
    32  
    33  const (
    34  	// EnvoyFileTemplate is a template for the root config JSON
    35  	EnvoyFileTemplate = "envoy-rev.%s"
    36  	DefaultCfgDir     = "./var/lib/istio/envoy/envoy_bootstrap_tmpl.json"
    37  )
    38  
    39  // TODO(nmittler): Move this to application code. This shouldn't be declared in a library.
    40  var overrideVar = env.Register("ISTIO_BOOTSTRAP", "", "")
    41  
    42  // Instance of a configured Envoy bootstrap writer.
    43  type Instance interface {
    44  	// WriteTo writes the content of the Envoy bootstrap to the given writer.
    45  	WriteTo(templateFile string, w io.Writer) error
    46  
    47  	// CreateFile generates an Envoy bootstrap file.
    48  	CreateFile() (string, error)
    49  }
    50  
    51  // New creates a new Instance of an Envoy bootstrap writer.
    52  func New(cfg Config) Instance {
    53  	return &instance{
    54  		Config: cfg,
    55  	}
    56  }
    57  
    58  type instance struct {
    59  	Config
    60  }
    61  
    62  func (i *instance) WriteTo(templateFile string, w io.Writer) error {
    63  	// Get the input bootstrap template.
    64  	t, err := newTemplate(templateFile)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	// Create the parameters for the template.
    70  	templateParams, err := i.toTemplateParams()
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	// Execute the template.
    76  	return t.Execute(w, templateParams)
    77  }
    78  
    79  func toJSON(i any) string {
    80  	if i == nil {
    81  		return "{}"
    82  	}
    83  
    84  	ba, err := json.Marshal(i)
    85  	if err != nil {
    86  		log.Warnf("Unable to marshal %v: %v", i, err)
    87  		return "{}"
    88  	}
    89  
    90  	return string(ba)
    91  }
    92  
    93  // GetEffectiveTemplatePath gets the template file that should be used for bootstrap
    94  func GetEffectiveTemplatePath(pc *model.NodeMetaProxyConfig) string {
    95  	var templateFilePath string
    96  	switch {
    97  	case pc.CustomConfigFile != "":
    98  		templateFilePath = pc.CustomConfigFile
    99  	case pc.ProxyBootstrapTemplatePath != "":
   100  		templateFilePath = pc.ProxyBootstrapTemplatePath
   101  	default:
   102  		templateFilePath = DefaultCfgDir
   103  	}
   104  	override := overrideVar.Get()
   105  	if len(override) > 0 {
   106  		templateFilePath = override
   107  	}
   108  	return templateFilePath
   109  }
   110  
   111  func (i *instance) CreateFile() (string, error) {
   112  	// Create the output file.
   113  	if err := os.MkdirAll(i.Metadata.ProxyConfig.ConfigPath, 0o700); err != nil {
   114  		return "", err
   115  	}
   116  
   117  	templateFile := GetEffectiveTemplatePath(i.Metadata.ProxyConfig)
   118  
   119  	outputFilePath := configFile(i.Metadata.ProxyConfig.ConfigPath, templateFile)
   120  	outputFile, err := os.Create(outputFilePath)
   121  	if err != nil {
   122  		return "", err
   123  	}
   124  	defer func() { _ = outputFile.Close() }()
   125  
   126  	// Write the content of the file.
   127  	if err := i.WriteTo(templateFile, outputFile); err != nil {
   128  		return "", err
   129  	}
   130  
   131  	return outputFilePath, err
   132  }
   133  
   134  func configFile(config string, templateFile string) string {
   135  	suffix := "json"
   136  	// Envoy will interpret the file extension to determine the type. We should detect yaml inputs
   137  	if strings.HasSuffix(templateFile, ".yaml.tmpl") || strings.HasSuffix(templateFile, ".yaml") {
   138  		suffix = "yaml"
   139  	}
   140  	return path.Join(config, fmt.Sprintf(EnvoyFileTemplate, suffix))
   141  }
   142  
   143  func newTemplate(templateFilePath string) (*template.Template, error) {
   144  	cfgTmpl, err := os.ReadFile(templateFilePath)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	funcMap := template.FuncMap{
   150  		"toJSON": toJSON,
   151  	}
   152  	return template.New("bootstrap").Funcs(funcMap).Funcs(sprig.GenericFuncMap()).Parse(string(cfgTmpl))
   153  }