Skip to content

Commit

Permalink
add goe cli tool to load .env to current shell
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmood committed Nov 1, 2024
1 parent 25a503b commit ac8a966
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 43 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ jobs:
- uses: actions/checkout@v3

- name: lint
run: go run github.com/ysmood/golangci-lint@latest
run: go run github.com/ysmood/golangci-lint@v0.13.0
if: matrix.os == 'ubuntu-latest'

- name: test
run: go test -coverprofile="coverage.out" ./...

- name: check coverage
run: go run github.com/ysmood/got/cmd/check-cov@latest -min 80
run: go test ./...
39 changes: 17 additions & 22 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
run:
skip-dirs:
- pkg/envparse

linters:
enable-all: true
disable:
- depguard
- revive
- forbidigo
- gochecknoinits
- paralleltest
- wrapcheck
- gosec
- gochecknoglobals
- varnamelen
- musttag
- depguard
- forcetypeassert
- varnamelen
- ireturn
- prealloc
- stylecheck
- tagalign
- govet
- err113
- nlreturn
- wsl

# Deprecated ones:
- execinquery
- gomnd
- exportloopref

- structcheck
- interfacer
- deadcode
- varcheck
- ifshort
- exhaustivestruct
- golint
- maligned
- nosnakecase
- scopelint
issues:
exclude-files:
- cmd/goe/shell.go
- pkg/dotenv/parser.go
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ For usage check the [example](example/basic.go).

About the format of `.env`: [link](https://pkg.go.dev/github.com/hashicorp/go-envparse)

## CLI tool

Run the command below will load the the `.env` file in the current shell.

```bash
go run github.com/ysmood/goe/cli@latest
```

## Safely share .env file with team members

`.env` file usually contains sensitive information, like database password, API key, etc.
Expand Down
41 changes: 41 additions & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"errors"
"fmt"
"os"
"os/exec"

"github.com/ysmood/goe"
_ "github.com/ysmood/goe/load"
"github.com/ysmood/goe/pkg/utils"
)

var (
errGetShell = errors.New("failed to get shell")
errFailRun = errors.New("failed to run shell")
)

func main() {
shell, err := Shell()
if err != nil {
panic(fmt.Errorf("%w: %w", errGetShell, err))
}

cmd := exec.Command(shell)
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Env = os.Environ()

if err := cmd.Run(); err != nil {
var exitErr exec.ExitError
if errors.Is(err, &exitErr) {
os.Exit(exitErr.ExitCode())
}

panic(fmt.Errorf("%w: %w", errFailRun, err))
}

utils.Println(goe.Prefix, "Unloaded environment variables")
}
94 changes: 94 additions & 0 deletions cli/shell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"errors"
"fmt"
"os"
"os/exec"
"os/user"
"regexp"
"runtime"
"strings"
)

// Shell https://github.com/riywo/loginshell/blob/master/loginshell.go
func Shell() (string, error) {
switch runtime.GOOS {
case "plan9":
return plan9Shell()
case "linux":
return nixShell()
case "openbsd":
return nixShell()
case "freebsd":
return nixShell()
case "android":
return androidShell()
case "darwin":
return darwinShell()
case "windows":
return windowsShell()
}

return "", errors.New("undefined GOOS: " + runtime.GOOS)
}

func plan9Shell() (string, error) {
if _, err := os.Stat("/dev/osversion"); err != nil {
if os.IsNotExist(err) {
return "", err
}
return "", errors.New("/dev/osversion check failed")
}

return "/bin/rc", nil
}

func nixShell() (string, error) {
user, err := user.Current()
if err != nil {
return "", err
}

out, err := exec.Command("getent", "passwd", user.Uid).Output()
if err != nil {
return "", err
}

ent := strings.Split(strings.TrimSuffix(string(out), "\n"), ":")
return ent[6], nil
}

func androidShell() (string, error) {
shell := os.Getenv("SHELL")
if shell == "" {
return "", errors.New("shell not defined in android")
}
return shell, nil
}

func darwinShell() (string, error) {
dir := "Local/Default/Users/" + os.Getenv("USER")
out, err := exec.Command("dscl", "localhost", "-read", dir, "UserShell").Output()
if err != nil {
return "", err
}

re := regexp.MustCompile("UserShell: (/[^ ]+)\n")
matched := re.FindStringSubmatch(string(out))
shell := matched[1]
if shell == "" {
return "", fmt.Errorf("invalid output: %s", string(out))
}

return shell, nil
}

func windowsShell() (string, error) {
consoleApp := os.Getenv("COMSPEC")
if consoleApp == "" {
consoleApp = "cmd.exe"
}

return consoleApp, nil
}
9 changes: 9 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package goe

import (
"fmt"
"reflect"
)

const (
DOTENV = ".env"
)

type info struct{}

var Prefix = fmt.Sprintf("[%s]", reflect.TypeOf(info{}).PkgPath())
4 changes: 2 additions & 2 deletions example/basic.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package main

import (
"fmt"
"strings"

"github.com/ysmood/goe"
_ "github.com/ysmood/goe/load" // load the .env file, it will auto decrypt the .env.goe file
"github.com/ysmood/goe/pkg/utils"
)

var (
Expand Down Expand Up @@ -42,7 +42,7 @@ func main() {
// It will output the env variables in "../.env"
// Output:
// 1 hello true [1 2] map[1:2 3:4] dev hello true 2023
fmt.Println(
utils.Println(
num, secret, isDev, list, numMap, expanded, string(bin),
strings.Contains(string(file), "goe"), time.Format("2006"),
)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/ysmood/goe

go 1.21
go 1.22

require (
github.com/alecthomas/participle/v2 v2.1.1
Expand Down
9 changes: 5 additions & 4 deletions goe.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package goe

import (
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -137,7 +138,7 @@ func GetListWithSep[T EnvType](name, separator string, defaultVal []T) ([]T, err
return defaultVal, nil
}

var out []T
var out []T //nolint: prealloc

for _, s := range strings.Split(Get(name, ""), separator) {
v, err := Parse[T](s)
Expand All @@ -161,7 +162,7 @@ func GetMap[K EnvKeyType, V EnvType](name string, defaultVal map[K]V) map[K]V {
return m
}

var ErrInvalidMapFormat = fmt.Errorf("invalid map format")
var ErrInvalidMapFormat = errors.New("invalid map format")

// GetMapWithSep returns env var with the name.
// It will override the key-value pairs in defaultVal with the parsed pairs.
Expand All @@ -172,7 +173,7 @@ func GetMapWithSep[K EnvKeyType, V EnvType](name, pairSep, kvSep string, default

for _, s := range strings.Split(str, pairSep) {
kv := strings.Split(s, kvSep)
if len(kv) != 2 { //nolint: gomnd
if len(kv) != 2 { //nolint: mnd
return nil, fmt.Errorf("%w: %s", ErrInvalidMapFormat, str)
}

Expand Down Expand Up @@ -227,7 +228,7 @@ func RequireWithParser[T any](name string, parser func(string) (T, error)) T {
return v
}

var ErrUnsupportedSliceType = fmt.Errorf("unsupported slice type")
var ErrUnsupportedSliceType = errors.New("unsupported slice type")

// Parse the str to the type T.
// It will auto detect the type of the env var and parse it.
Expand Down
2 changes: 1 addition & 1 deletion goe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestExample(t *testing.T) {

cmd := exec.Command("go", "run", "./example")
out, err := cmd.CombinedOutput()
g.Desc(string(out)).E(err)
g.Desc("%s", string(out)).E(err)

g.Has(string(out), `.env
1 hello true [1 2] map[1:2 3:4] dev hello true 2023`)
Expand Down
12 changes: 4 additions & 8 deletions load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@ import (
"errors"
"fmt"
"os"
"reflect"

"github.com/ysmood/goe"
"github.com/ysmood/goe/pkg/utils"
)

type info struct{}

var prefix = fmt.Sprintf("[%s]", reflect.TypeOf(info{}).PkgPath())

func init() {
err := load()
if err != nil {
fmt.Fprintln(os.Stderr, prefix, err)
fmt.Fprintln(os.Stderr, goe.Prefix, err)
os.Exit(1)
}
}
Expand All @@ -27,7 +23,7 @@ func load() error {
err := goe.Load(false, true, goe.DOTENV)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println(prefix + " .env file not found, skipped loading.")
utils.Println(goe.Prefix, ".env file not found, skipped loading.")

return nil
}
Expand All @@ -37,7 +33,7 @@ func load() error {

path, _ := goe.LookupFile(goe.DOTENV)

fmt.Println(prefix+" Loaded environment variables from:", path)
utils.Println(goe.Prefix, "Loaded environment variables from:", path)

return nil
}
7 changes: 7 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package utils

import "fmt"

func Println(a ...interface{}) {
_, _ = fmt.Println(a...) //nolint: forbidigo
}

0 comments on commit ac8a966

Please sign in to comment.