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 }