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