github.com/openshift-online/ocm-sdk-go@v0.1.473/configuration/tags.go (about) 1 /* 2 Copyright (c) 2020 Red Hat, 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 // This file contains the code that is used to process tags like `!variable` and `!file` inside 18 // configuration files. 19 20 package configuration 21 22 import ( 23 "bytes" 24 "os" 25 "os/exec" 26 "strings" 27 28 "gopkg.in/yaml.v3" 29 ) 30 31 // tagRegistryEntry stores the description of one tag. When a tag is detected in a node the 32 // corresponding function will be called passing the node, so that the function can modify 33 // it as needed. 34 type tagRegistryEntry struct { 35 name string 36 kind yaml.Kind 37 process func(*yaml.Node) error 38 } 39 40 // registerTag adds a tag to the registry. 41 func (b *Builder) registerTag(name string, kind yaml.Kind, process func(*yaml.Node) error) { 42 b.tags = append(b.tags, tagRegistryEntry{ 43 name: name, 44 kind: kind, 45 process: process, 46 }) 47 } 48 49 // lookupTag tries to find an entry in the tag registry corresponding to the given name. 50 func (b *Builder) lookupTag(name string) (result tagRegistryEntry, ok bool) { 51 for _, entry := range b.tags { 52 if strings.HasPrefix(entry.name, name) { 53 result = entry 54 ok = true 55 break 56 } 57 } 58 return 59 } 60 61 // processTreeTags process recursively the tags present in the given YAML tree and returns the 62 // result. 63 func (b *Builder) processTreeTags(tree *yaml.Node) error { 64 err := b.processNodeTags(tree) 65 if err != nil { 66 return err 67 } 68 for _, node := range tree.Content { 69 err = b.processTreeTags(node) 70 if err != nil { 71 return err 72 } 73 } 74 return nil 75 } 76 77 // processNodeTags process the tags present in the given YAML node. 78 func (b *Builder) processNodeTags(node *yaml.Node) error { 79 tag := node.ShortTag() 80 if strings.HasPrefix(tag, "!!") { 81 return nil 82 } 83 names := b.parseTag(tag) 84 for _, name := range names { 85 entry, ok := b.lookupTag(name) 86 if ok { 87 if entry.kind != node.Kind { 88 return b.nodeError( 89 node, 90 "tag '%s' expects %s node but found %s", 91 name, 92 b.kindString(entry.kind), 93 b.kindString(node.Kind), 94 ) 95 } 96 err := entry.process(node) 97 if err != nil { 98 return err 99 } 100 } else { 101 return b.nodeError(node, "unsupported tag '%s'", name) 102 } 103 } 104 return nil 105 } 106 107 // processVariableTag is the implementation fo the `variable` tag: replaces an environment variable 108 // reference with its value. 109 func (b *Builder) processVariableTag(node *yaml.Node) error { 110 variable := node.Value 111 result, ok := os.LookupEnv(variable) 112 if !ok { 113 return b.nodeError(node, "can't find environment variable '%s'", variable) 114 } 115 node.SetString(result) 116 return nil 117 } 118 119 // processFileTag is the implementation of the `file` tag: replaces a file reference with the 120 // content of the file. 121 func (b *Builder) processFileTag(node *yaml.Node) error { 122 file := node.Value 123 data, err := os.ReadFile(file) // #nosec G304 124 if err != nil { 125 return b.nodeError(node, "%w", err) 126 } 127 result := string(data) 128 node.SetString(result) 129 b.titleTree(file, node) 130 return nil 131 } 132 133 // processScriptTag is the implementation of the `script` tag: replaces a script script with 134 // the result of executing it. 135 func (b *Builder) processScriptTag(node *yaml.Node) error { 136 shell, ok := os.LookupEnv("SHELL") 137 if !ok { 138 shell = "/bin/sh" 139 } 140 script := node.Value 141 stdin := strings.NewReader(script) 142 stdout := &bytes.Buffer{} 143 stderr := &bytes.Buffer{} 144 cmd := &exec.Cmd{ 145 Path: shell, 146 Stdin: stdin, 147 Stdout: stdout, 148 Stderr: stderr, 149 } 150 err := cmd.Run() 151 _, exit := err.(*exec.ExitError) 152 if exit { 153 err = nil 154 } 155 if err != nil { 156 return b.nodeError(node, "%w", err) 157 } 158 code := cmd.ProcessState.ExitCode() 159 if code != 0 || stderr.Len() > 0 { 160 return b.nodeError( 161 node, 162 "script '%s' finished with exit code %d, wrote '%s' to stdout and '%s' "+ 163 "to stderr", 164 b.quoteForError(script), 165 code, 166 b.quoteForError(stdout.String()), 167 b.quoteForError(stderr.String()), 168 ) 169 } 170 result := stdout.String() 171 node.SetString(result) 172 return nil 173 } 174 175 // processTrimTag is the implementation of the `trim` tag: trims leading and trailing white 176 // space, including line breaks and tabs. 177 func (b *Builder) processTrimTag(node *yaml.Node) error { 178 value := node.Value 179 value = strings.TrimSpace(value) 180 node.SetString(value) 181 return nil 182 } 183 184 // processYamlTag is the implementation of the `yaml` tag: parses the value as a YAML 185 // document and replaces the current node with the result. 186 func (b *Builder) processYamlTag(node *yaml.Node) error { 187 buffer := []byte(node.Value) 188 var parsed yaml.Node 189 err := yaml.Unmarshal(buffer, &parsed) 190 if err != nil { 191 return b.nodeError(node, "can't parse '%s': %w", buffer, err) 192 } 193 b.titleTree(b.titles[node], &parsed) 194 err = b.processTreeTags(&parsed) 195 if err != nil { 196 return err 197 } 198 *node = *parsed.Content[0] 199 return nil 200 } 201 202 // processStrTag is the implementation of the `string` tag: it explicitly sets the node kind 203 // to `!!str`. 204 func (b *Builder) processStringTag(node *yaml.Node) error { 205 node.Tag = "!!str" 206 return nil 207 } 208 209 // processIntegerTag is the implementation of the `integer` tag: it explicitly sets the node kind 210 // to `!!int`. 211 func (b *Builder) processIntegerTag(node *yaml.Node) error { 212 node.Tag = "!!int" 213 return nil 214 } 215 216 // processBooleanTag is the implementation of the `bool` tag: it explicitly sets the node kind 217 // to `!!bool`. 218 func (b *Builder) processBooleanTag(node *yaml.Node) error { 219 node.Tag = "!!bool" 220 return nil 221 } 222 223 // processFloatTag is the implementation of the `float` tag: it explicitly sets the node type 224 // to `!!float`. 225 func (b *Builder) processFloatTag(node *yaml.Node) error { 226 node.Tag = "!!float" 227 return nil 228 } 229 230 // parseTag extracts the coponents from the given tags. For example, the tag `!file/int` will be 231 // parsed as a slice containing `file` and `int`. 232 func (b *Builder) parseTag(tag string) []string { 233 if !strings.HasPrefix(tag, "!") { 234 return nil 235 } 236 return strings.Split(tag[1:], "/") 237 }