1package main 2 3import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "flag" 8 "fmt" 9 "github.com/variantdev/vals" 10 "gopkg.in/yaml.v3" 11 "os" 12) 13 14func flagUsage() { 15 text := `vals is a Helm-like configuration "Values" loader with support for various sources and merge strategies 16 17Usage: 18 vals [command] 19 20Available Commands: 21 eval Evaluate a JSON/YAML document and replace any template expressions in it and prints the result 22 exec Populates the environment variables and executes the command 23 env Renders environment variables to be consumed by eval or a tool like direnv 24 ksdecode Decode YAML document(s) by converting Secret resources' "data" to "stringData" for use with "vals eval" 25 26Use "vals [command] --help" for more infomation about a command 27` 28 29 fmt.Fprintf(os.Stderr, "%s\n", text) 30} 31 32func fatal(format string, args ...interface{}) { 33 fmt.Fprintf(os.Stderr, format+"\n", args...) 34 os.Exit(1) 35} 36 37func readOrFail(f *string) map[string]interface{} { 38 m, err := vals.Input(*f) 39 if err != nil { 40 fatal("%v", err) 41 } 42 return m 43} 44 45func writeOrFail(o *string, res interface{}) { 46 out, err := vals.Output(*o, res) 47 if err != nil { 48 fatal("%v", err) 49 } 50 fmt.Printf("%s", *out) 51} 52 53func main() { 54 flag.Usage = flagUsage 55 56 CmdEval := "eval" 57 CmdExec := "exec" 58 CmdEnv := "env" 59 CmdKsDecode := "ksdecode" 60 61 if len(os.Args) == 1 { 62 flag.Usage() 63 return 64 } 65 66 switch os.Args[1] { 67 case CmdEval: 68 evalCmd := flag.NewFlagSet(CmdEval, flag.ExitOnError) 69 f := evalCmd.String("f", "-", "YAML/JSON file to be evaluated. When set to \"-\", vals reads from STDIN") 70 o := evalCmd.String("o", "yaml", "Output type which is either \"yaml\" or \"json\"") 71 e := evalCmd.Bool("exclude-secret", false, "Leave secretref+<uri> as-is and only replace ref+<uri>") 72 evalCmd.Parse(os.Args[2:]) 73 74 m := readOrFail(f) 75 76 res, err := vals.Eval(m, vals.Options{ExcludeSecret: *e}) 77 if err != nil { 78 fatal("%v", err) 79 } 80 81 writeOrFail(o, res) 82 case CmdExec: 83 execCmd := flag.NewFlagSet(CmdExec, flag.ExitOnError) 84 f := execCmd.String("f", "", "YAML/JSON file to be loaded to set envvars") 85 execCmd.Parse(os.Args[2:]) 86 87 m := readOrFail(f) 88 89 err := vals.Exec(m, execCmd.Args()) 90 if err != nil { 91 fatal("%v", err) 92 } 93 case CmdEnv: 94 execEnv := flag.NewFlagSet(CmdEnv, flag.ExitOnError) 95 f := execEnv.String("f", "", "YAML/JSON file to be loaded to set envvars") 96 export := execEnv.Bool("export", false, "Prepend `export`s to each line") 97 execEnv.Parse(os.Args[2:]) 98 99 m := readOrFail(f) 100 101 env, err := vals.Env(m) 102 if err != nil { 103 fatal("%v", err) 104 } 105 for _, l := range env { 106 if *export { 107 l = "export " + l 108 } 109 fmt.Fprintln(os.Stdout, l) 110 } 111 case CmdKsDecode: 112 evalCmd := flag.NewFlagSet(CmdKsDecode, flag.ExitOnError) 113 f := evalCmd.String("f", "", "YAML/JSON file to be decoded") 114 o := evalCmd.String("o", "yaml", "Output type which is either \"yaml\" or \"json\"") 115 evalCmd.Parse(os.Args[2:]) 116 117 nodes, err := vals.Inputs(*f) 118 if err != nil { 119 fatal("%v", err) 120 } 121 122 var res []yaml.Node 123 for _, node := range nodes { 124 n, err := KsDecode(node) 125 if err != nil { 126 fatal("%v", err) 127 } 128 res = append(res, *n) 129 } 130 131 for i, node := range res { 132 buf := &bytes.Buffer{} 133 encoder := yaml.NewEncoder(buf) 134 encoder.SetIndent(2) 135 136 if err := encoder.Encode(&node); err != nil { 137 fatal("%v", err) 138 } 139 if *o == "json" { 140 var v interface{} 141 if err := json.Unmarshal(buf.Bytes(), &v); err != nil { 142 fatal("%v", err) 143 } 144 bs, err := json.Marshal(v) 145 if err != nil { 146 fatal("%v", err) 147 } 148 print(string(bs)) 149 } else { 150 print(buf.String()) 151 } 152 if i != len(res)-1 { 153 fmt.Println("---") 154 } 155 } 156 default: 157 flag.Usage() 158 } 159} 160 161func KsDecode(node yaml.Node) (*yaml.Node, error) { 162 if node.Kind != yaml.DocumentNode { 163 return nil, fmt.Errorf("unexpected kind of node: expected %d, got %d", yaml.DocumentNode, node.Kind) 164 } 165 166 var res yaml.Node 167 res = node 168 169 var kk yaml.Node 170 var vv yaml.Node 171 var ii int 172 173 isSecret := false 174 mappings := node.Content[0].Content 175 for i := 0; i < len(mappings); i += 2 { 176 j := i + 1 177 k := mappings[i] 178 v := mappings[j] 179 180 if k.Value == "kind" && v.Value == "Secret" { 181 isSecret = true 182 } 183 184 if k.Value == "data" { 185 ii = i 186 kk = *k 187 vv = *v 188 } 189 } 190 191 if isSecret { 192 kk.Value = "stringData" 193 194 v := vv 195 nestedMappings := v.Content 196 v.Content = make([]*yaml.Node, len(v.Content)) 197 for i := 0; i < len(nestedMappings); i += 2 { 198 b64 := nestedMappings[i+1].Value 199 decoded, err := base64.StdEncoding.DecodeString(b64) 200 if err != nil { 201 return nil, err 202 } 203 nestedMappings[i+1].Value = string(decoded) 204 205 v.Content[i] = nestedMappings[i] 206 v.Content[i+1] = nestedMappings[i+1] 207 } 208 209 res.Content[0].Content[ii] = &kk 210 res.Content[0].Content[ii+1] = &v 211 } 212 213 return &res, nil 214} 215