github.com/yourbase/yb@v0.7.1/cmd/yb/envflag.go (about) 1 // Copyright 2020 YourBase Inc. 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 // https://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 // SPDX-License-Identifier: Apache-2.0 16 17 package main 18 19 import ( 20 "fmt" 21 "os" 22 "strings" 23 24 "github.com/spf13/pflag" 25 "github.com/yourbase/commons/ini" 26 "github.com/yourbase/yb/internal/biome" 27 ) 28 29 const ( 30 envLiteral = false 31 envFile = true 32 ) 33 34 type commandLineEnv struct { 35 envType bool 36 key string 37 value string 38 } 39 40 // envFromCommandLine builds a biome environment from command line flag 41 // arguments, reading .env files as requested. 42 func envFromCommandLine(args []commandLineEnv) (biome.Environment, error) { 43 env := biome.Environment{Vars: make(map[string]string)} 44 for _, a := range args { 45 switch a.envType { 46 case envLiteral: 47 env.Vars[a.key] = a.value 48 case envFile: 49 f, err := os.Open(a.value) 50 if err != nil { 51 return biome.Environment{}, fmt.Errorf("load env file: %w", err) 52 } 53 parsed, err := ini.Parse(f, nil) 54 f.Close() // Any errors from here are not actionable. 55 if err != nil { 56 return biome.Environment{}, fmt.Errorf("load env file: %s: %w", a.value, err) 57 } 58 if parsed.HasSections() { 59 return biome.Environment{}, fmt.Errorf("load env file: %s: sections not permitted", a.value) 60 } 61 for k, values := range parsed.Section("") { 62 env.Vars[k] = values[len(values)-1] 63 } 64 default: 65 panic("unreachable") 66 } 67 } 68 return env, nil 69 } 70 71 // envFlagsVar registers the --env and --env-file flags. 72 func envFlagsVar(flags *pflag.FlagSet, env *[]commandLineEnv) { 73 flags.VarP(envLiteralFlag{env}, "env", "e", "Set an environment variable (can be passed multiple times)") 74 flags.VarP(envFileFlag{env}, "env-file", "E", "Load environment variables from a .env file (can be passed multiple times)") 75 } 76 77 type envLiteralFlag struct { 78 env *[]commandLineEnv 79 } 80 81 func (f envLiteralFlag) String() string { 82 var parts []string 83 for _, e := range *f.env { 84 if e.envType == envLiteral { 85 parts = append(parts, e.key+"="+e.value) 86 } 87 } 88 return strings.Join(parts, " ") 89 } 90 91 func (f envLiteralFlag) Set(arg string) error { 92 i := strings.IndexByte(arg, '=') 93 if i == -1 { 94 return fmt.Errorf("invalid environment variable %q: missing '='", arg) 95 } 96 if i == 0 { 97 return fmt.Errorf("invalid environment variable %q: missing variable name", arg) 98 } 99 *f.env = append(*f.env, commandLineEnv{ 100 envType: envLiteral, 101 key: arg[:i], 102 value: arg[i+1:], 103 }) 104 return nil 105 } 106 107 func (f envLiteralFlag) Type() string { 108 return "key=value" 109 } 110 111 type envFileFlag struct { 112 env *[]commandLineEnv 113 } 114 115 func (f envFileFlag) String() string { 116 var parts []string 117 for _, e := range *f.env { 118 if e.envType == envFile { 119 parts = append(parts, e.key+"="+e.value) 120 } 121 } 122 return strings.Join(parts, string(os.PathListSeparator)) 123 } 124 125 func (f envFileFlag) Set(arg string) error { 126 *f.env = append(*f.env, commandLineEnv{ 127 envType: envFile, 128 value: arg, 129 }) 130 return nil 131 } 132 133 func (f envFileFlag) Type() string { 134 return "path" 135 }