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  }