initial commit
This commit is contained in:
commit
740b8c6df2
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
loadenv
|
||||
*.env
|
19
LICENSE.md
Normal file
19
LICENSE.md
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2022 Mila Ringwald
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
69
README.md
Normal file
69
README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# loadenv
|
||||
|
||||
Loads environment files before running a command without polluting your environment
|
||||
|
||||
## Source code
|
||||
|
||||
You can find the source code here: [git.milar.in](https://git.milar.in/milarin/loadenv)
|
||||
|
||||
## Installation
|
||||
|
||||
If you have Go installed, you can simply go install the program: `go install git.milar.in/milarin/loadenv@latest`
|
||||
|
||||
There are pre-compiled executables for various platforms on the [repository](https://git.milar.in/milarin/loadenv/releases).
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the MIT License. See [LICENSE.md](https://git.milar.in/milarin/loadenv/src/branch/main/LICENSE.md)
|
||||
|
||||
## Usage
|
||||
|
||||
`loadenv` loads the environment file provided via `-f` into the environment and runs the command provided after the arguments.
|
||||
If no file is provided, `.env` is used by default.
|
||||
If no command is provided, `loadenv` prints all environment variables to stdout.
|
||||
|
||||
---
|
||||
|
||||
The variables will only be available to the given command. They will be deleted after the command exits.
|
||||
`loadenv` can be useful when working with programs which heavily use environment variables.
|
||||
|
||||
```sh
|
||||
$ cat .env
|
||||
# production server configuration
|
||||
DATABASE_HOST=prod-server.com
|
||||
DATABASE_USER=milarin
|
||||
DATABASE_PASS=my-super-secure-password
|
||||
|
||||
$ # show the 3 last added env variables
|
||||
$ loadenv | tail -n 3
|
||||
DATABASE_HOST=prod-server.com
|
||||
DATABASE_USER=milarin
|
||||
DATABASE_PASS=my-super-secure-password
|
||||
|
||||
$ cat dev.env
|
||||
# development server configuration
|
||||
DATABASE_HOST=dev-server.com
|
||||
DATABASE_USER=milarin
|
||||
DATABASE_PASS=my-super-secure-password
|
||||
|
||||
$ # load dev.env into environment and run the command env. show last 3 lines
|
||||
$ loadenv -f dev.env env | tail -n 3
|
||||
DATABASE_HOST=dev-server.com
|
||||
DATABASE_USER=milarin
|
||||
DATABASE_PASS=my-super-secure-password
|
||||
```
|
||||
|
||||
environment variables in that file will be expanded:
|
||||
|
||||
```sh
|
||||
$ env | grep USER # current value of $USER
|
||||
USER=milarin
|
||||
|
||||
$ cat .env
|
||||
# expand $USER reference
|
||||
USERNAME=$USER
|
||||
|
||||
$ loadenv | tail -n 1
|
||||
USERNAME=milarin
|
||||
```
|
||||
|
69
expand_files.go
Normal file
69
expand_files.go
Normal file
@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func expandEnvFiles() error {
|
||||
for _, path := range FlagExpandEnvFiles {
|
||||
tmpFile, err := os.CreateTemp("", "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
ExpandEnvFiles[path] = tmpFile.Name()
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
|
||||
if _, err := io.Copy(tmpFile, bytes.NewReader(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newFile.Close()
|
||||
|
||||
if _, err := newFile.WriteString(os.ExpandEnv(string(data))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreEnvFiles() error {
|
||||
for originalFilePath, tmpFilePath := range ExpandEnvFiles {
|
||||
originalFile, err := os.Create(originalFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer originalFile.Close()
|
||||
|
||||
tmpFile, err := os.Open(tmpFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
if _, err := io.Copy(originalFile, tmpFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
git.milar.in/milarin/buildinfo v1.0.0 h1:tw98GupUYl/0a/3aPGuezhE4wseycOSsbcLp70hy60U=
|
||||
git.milar.in/milarin/buildinfo v1.0.0/go.mod h1:arI9ZoENOgcZcanv25k9y4dKDUhPp0buJrlVerGruas=
|
100
main.go
Normal file
100
main.go
Normal file
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
EnvVarRegex = regexp.MustCompile(`^(.*?)=(.*?)$`)
|
||||
EnvCommentRegex = regexp.MustCompile(`^[ \t]*#.*?$`)
|
||||
ExpandEnvFiles = map[string]string{}
|
||||
)
|
||||
|
||||
var ( // flags
|
||||
FlagEnvFilePath = flag.String("f", ".env", "environment file")
|
||||
|
||||
FlagExpandEnvFiles = SliceFlag{}
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Var(&FlagExpandEnvFiles, "e", "files in which to expand environment variables")
|
||||
flag.Parse()
|
||||
|
||||
envFile, err := os.Open(*FlagEnvFilePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not open file '%s': %s\n", *FlagEnvFilePath, err.(*os.PathError).Err)
|
||||
return
|
||||
}
|
||||
defer envFile.Close()
|
||||
|
||||
parseEnvFile(envFile)
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) == 0 {
|
||||
for _, envVar := range os.Environ() {
|
||||
fmt.Println(envVar)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := expandEnvFiles(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not expand files: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer restoreFiles()
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() { <-sigs; exit(1) }()
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
exit(exitErr.ExitCode())
|
||||
return
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseEnvFile(r io.Reader) {
|
||||
s := bufio.NewScanner(r)
|
||||
l := 0
|
||||
|
||||
for s.Scan() {
|
||||
l++
|
||||
|
||||
matches := EnvVarRegex.FindStringSubmatch(s.Text())
|
||||
if len(matches) != 0 {
|
||||
os.Setenv(matches[1], matches[2])
|
||||
} else if s.Text() != "" && !EnvCommentRegex.MatchString(s.Text()) {
|
||||
panic(fmt.Sprintf("invalid env syntax on line %d", l))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func exit(errorCode int) {
|
||||
restoreFiles()
|
||||
os.Exit(errorCode)
|
||||
}
|
||||
|
||||
func restoreFiles() {
|
||||
if err := restoreEnvFiles(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not restore expanded files: %s\n", err.Error())
|
||||
}
|
||||
}
|
14
sliceflag.go
Normal file
14
sliceflag.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
type SliceFlag []string
|
||||
|
||||
func (f *SliceFlag) String() string {
|
||||
return strings.Join(*f, ",")
|
||||
}
|
||||
|
||||
func (f *SliceFlag) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user