github.com/Venafi/vcert/v5@v5.10.2/pkg/playbook/app/parser/reader.go (about)

     1  /*
     2   * Copyright 2023 Venafi, Inc.
     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 parser
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"text/template"
    25  
    26  	"go.uber.org/zap"
    27  	"gopkg.in/yaml.v3"
    28  
    29  	"github.com/Venafi/vcert/v5/pkg/playbook/app/domain"
    30  )
    31  
    32  var errorTemplate = "%w: %s"
    33  
    34  // ReadPlaybook reads the file in location, parses the content and returns a Playbook object
    35  func ReadPlaybook(location string) (domain.Playbook, error) {
    36  	playbook := domain.NewPlaybook()
    37  
    38  	//Set location. Otherwise, use default
    39  	if location != "" {
    40  		playbook.Location = location
    41  	}
    42  
    43  	data, err := readFile(location)
    44  
    45  	if err != nil {
    46  		return playbook, err
    47  	}
    48  
    49  	data, err = parseConfigTemplate(data)
    50  	if err != nil {
    51  		return playbook, fmt.Errorf(errorTemplate, ErrTextTplParsing, err.Error())
    52  	}
    53  
    54  	err = yaml.Unmarshal(data, &playbook)
    55  	if err != nil {
    56  		return playbook, fmt.Errorf(errorTemplate, ErrFileUnmarshall, err.Error())
    57  	}
    58  
    59  	zap.L().Info("playbook successfully parsed")
    60  	return playbook, nil
    61  }
    62  
    63  // ReadPlaybookRaw reads the file in location and parses the content to a map.
    64  //
    65  // This is specially useful to avoid parsing the template values in the file
    66  func ReadPlaybookRaw(location string) (map[string]interface{}, error) {
    67  	data, err := readFile(location)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	dataMap := make(map[string]interface{})
    73  	err = yaml.Unmarshal(data, &dataMap)
    74  	if err != nil {
    75  		return nil, fmt.Errorf(errorTemplate, ErrFileUnmarshall, err.Error())
    76  	}
    77  
    78  	zap.L().Info("playbook data successfully parsed")
    79  	return dataMap, nil
    80  }
    81  
    82  func readFile(location string) ([]byte, error) {
    83  	var data []byte
    84  
    85  	if location == "" {
    86  		return data, ErrNoLocation
    87  	}
    88  
    89  	// The location is an invalid URL
    90  	zap.L().Debug("reading from local file system")
    91  	data, err := os.ReadFile(location)
    92  
    93  	if err != nil {
    94  		return data, fmt.Errorf(errorTemplate, ErrReadFile, err.Error())
    95  	}
    96  
    97  	return data, nil
    98  }
    99  
   100  func parseConfigTemplate(b []byte) ([]byte, error) {
   101  	// Valid functions for the config file template
   102  	fm := template.FuncMap{
   103  		"Env": func(es ...string) (string, error) {
   104  			if len(es) == 1 {
   105  				e := es[0]
   106  				value, found := os.LookupEnv(e)
   107  				if found {
   108  					return value, nil
   109  				}
   110  				return "", fmt.Errorf("environment variable not defined: %s", e)
   111  			} else if len(es) == 2 {
   112  				e := es[0]
   113  				d := es[1]
   114  				value, found := os.LookupEnv(e)
   115  				if found {
   116  					return value, nil
   117  				} else {
   118  					return d, nil
   119  				}
   120  			} else {
   121  				return "", fmt.Errorf("unsupported number of inputs provided: %d", len(es))
   122  			}
   123  		},
   124  		"Hostname": func() string {
   125  			hostname, err := os.Hostname()
   126  			if err != nil {
   127  				zap.L().Warn("failed to automatically determine hostname", zap.Error(err))
   128  				return ""
   129  			}
   130  			return hostname
   131  		},
   132  		"ToLower": strings.ToLower,
   133  		"ToUpper": strings.ToUpper,
   134  	}
   135  
   136  	// Parse the YAML config template file
   137  	tpl, err := template.New("config").Funcs(fm).Parse(string(b))
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	// Get a bytes buffer to store results in
   143  	buf := &bytes.Buffer{}
   144  
   145  	err = tpl.Execute(buf, nil)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	return buf.Bytes(), nil
   151  }