Initial commit
All checks were successful
Codespell / Check for spelling errors (push) Successful in 23s
All checks were successful
Codespell / Check for spelling errors (push) Successful in 23s
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
This commit is contained in:
commit
d72005afe1
59 changed files with 2277 additions and 0 deletions
27
.forgejo/workflows/codespell.yml
Normal file
27
.forgejo/workflows/codespell.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: Codespell
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codespell:
|
||||||
|
name: Check for spelling errors
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Annotate locations with typos
|
||||||
|
uses: "https://github.com/codespell-project/codespell-problem-matcher@v1"
|
||||||
|
- name: Codespell
|
||||||
|
uses: "https://github.com/codespell-project/actions-codespell@v2"
|
||||||
|
with:
|
||||||
|
check_filenames: true
|
||||||
|
check_hidden: true
|
||||||
|
skip: ./vendor,./.git
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
build/
|
||||||
|
vendor/
|
||||||
|
# test secrets
|
||||||
|
.secrets
|
46
Makefile
Normal file
46
Makefile
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
BINARY_NAME=envoid
|
||||||
|
VERSION=1.0.0-alpha
|
||||||
|
BUILD_FOLDER=build
|
||||||
|
|
||||||
|
GO_BUILD_LD_FLAGS=-ldflags="-s -w -X 'git.dayanhub.com/sagi/envoid/internal/variables.Commit=$(shell git rev-parse --short HEAD)' \
|
||||||
|
-X 'git.dayanhub.com/sagi/envoid/internal/variables.Version=${VERSION}'"
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
GOARCH=amd64 GOOS=linux go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-linux main.go
|
||||||
|
GOARCH=amd64 GOOS=darwin go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-darwin main.go
|
||||||
|
GOARCH=amd64 GOOS=windows go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-windows main.go
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
go clean
|
||||||
|
rm -rf ${BUILD_FOLDER}
|
||||||
|
rm -rf ./docs
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
.PHONY: test_coverage
|
||||||
|
test_coverage:
|
||||||
|
go test ./... -coverprofile=coverage.out
|
||||||
|
|
||||||
|
.PHONY: dep
|
||||||
|
dep:
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
|
|
||||||
|
.PHONY: vet
|
||||||
|
vet:
|
||||||
|
go vet
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
golangci-lint run --enable-all
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: doc-gen
|
||||||
|
doc-gen:
|
||||||
|
rm -rf ./docs
|
||||||
|
mkdir ./docs
|
||||||
|
go run . doc
|
||||||
|
|
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Envoid
|
||||||
|
|
||||||
|
##### The `.env`(s) manager you never knew you needed
|
||||||
|
|
||||||
|
⚠ WIP !
|
||||||
|
|
||||||
|
[Documentation](docs/envoid.md)
|
145
cmd/env.go
Normal file
145
cmd/env.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type envCmdFlags struct {
|
||||||
|
rmEnvName *string
|
||||||
|
addEnvName *string
|
||||||
|
baseEnvName *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var envFlags = &envCmdFlags{}
|
||||||
|
|
||||||
|
var envCmd = &cobra.Command{
|
||||||
|
Use: "env",
|
||||||
|
Short: "manage environments",
|
||||||
|
Long: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var rmEnvCmd = &cobra.Command{
|
||||||
|
Use: "rm",
|
||||||
|
Short: "removes an environment",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := initProject(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ds.Close()
|
||||||
|
err = ds.RemoveEnv(*envFlags.rmEnvName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
project.RemoveEnv(*envFlags.rmEnvName)
|
||||||
|
configuration.Save()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var addEnvCmd = &cobra.Command{
|
||||||
|
Use: "add",
|
||||||
|
Short: "adds an environment",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := initProject(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(*envFlags.addEnvName)) == 0 {
|
||||||
|
return errors.NewInvalidFlagValueError("environment", *envFlags.addEnvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := project.GetEnv(*envFlags.addEnvName); err == nil {
|
||||||
|
return errors.NewEnvironmentExistsError(*envFlags.addEnvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ds.Close()
|
||||||
|
|
||||||
|
if len(*envFlags.baseEnvName) != 0 {
|
||||||
|
_, err = project.GetEnv(*envFlags.baseEnvName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ds.CreateEnvOffExsisting(*envFlags.addEnvName, *envFlags.baseEnvName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ds.CreateEnv(*envFlags.addEnvName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = project.NewEnv(*envFlags.addEnvName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configuration.Save()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var lsEnvCmd = &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "list all environments in this project",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := initProject(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return errors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, env := range project.Environments {
|
||||||
|
fmt.Printf("%s\n", env.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// add
|
||||||
|
envFlags.addEnvName = addEnvCmd.Flags().StringP("environment", "e", "", "environment name")
|
||||||
|
err := addEnvCmd.MarkFlagRequired("environment")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
envFlags.baseEnvName = addEnvCmd.Flags().StringP("base-environment", "b", "", "base environment name (copy data from base)")
|
||||||
|
err = addEnvCmd.RegisterFlagCompletionFunc("base-environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
//rm
|
||||||
|
envFlags.rmEnvName = rmEnvCmd.Flags().StringP("environment", "e", "", "environment name")
|
||||||
|
err = rmEnvCmd.MarkFlagRequired("environment")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = rmEnvCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// ls
|
||||||
|
envCmd.AddCommand(addEnvCmd)
|
||||||
|
envCmd.AddCommand(rmEnvCmd)
|
||||||
|
envCmd.AddCommand(lsEnvCmd)
|
||||||
|
}
|
81
cmd/get.go
Normal file
81
cmd/get.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type getCmdFlags struct {
|
||||||
|
envName *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var getFlags = getCmdFlags{}
|
||||||
|
|
||||||
|
var getCmd = &cobra.Command{
|
||||||
|
Use: "get <key>",
|
||||||
|
Short: "Gets a variable",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := initProject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return intErrors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) != 1 {
|
||||||
|
return intErrors.NewInvalidCommandError("expected 1 args. <key>")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := args[0]
|
||||||
|
|
||||||
|
env := project.Environments[0]
|
||||||
|
err = checkAmbiguousEnv(*getFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(*getFlags.envName) > 0 {
|
||||||
|
e, err := project.GetEnv(*getFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env = e
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ds.Close()
|
||||||
|
|
||||||
|
envVar, err := ds.GetVar(env.Name, key)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %s\n", err.Error())
|
||||||
|
if errors.Is(err, &intErrors.NoKeyFoundError{}) {
|
||||||
|
os.Exit(1)
|
||||||
|
} else if errors.Is(err, intErrors.NewInvalidPasswordError()) {
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(envVar.Value)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getFlags.envName = getCmd.Flags().StringP("environment", "e", "", "environment name")
|
||||||
|
err := getCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
77
cmd/import.go
Normal file
77
cmd/import.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type importCmdFlags struct {
|
||||||
|
envName *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var importFlags = importCmdFlags{}
|
||||||
|
|
||||||
|
var importCmd = &cobra.Command{
|
||||||
|
Use: "import <file>",
|
||||||
|
Short: "import a .env file into environment(s)",
|
||||||
|
Long: "This will not encrypt any value. You can then use `set encrypt KEY_NAME` to encrypt",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := initProject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return errors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
envs := project.Environments
|
||||||
|
if len(*importFlags.envName) != 0 {
|
||||||
|
e, err := project.GetEnv(*importFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
envs = []*types.Environment{e}
|
||||||
|
}
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("Needs a file to parse")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := godotenv.Parse(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ds.Close()
|
||||||
|
for k, v := range m {
|
||||||
|
err = ds.SetValue(k, v, nil, envs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
importFlags.envName = importCmd.Flags().StringP("environment", "e", "", "environments name")
|
||||||
|
err := importCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
79
cmd/init.go
Normal file
79
cmd/init.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/prompt"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/variables"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initCmd = &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "creates a new project for this path. default env is set to `local`",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
_, err := configuration.GetProject(workingDir)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("Project already exists. please remove if you wish to override\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ds.Close()
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var pass string
|
||||||
|
var envNames []string
|
||||||
|
|
||||||
|
//check if we are importing an existing file
|
||||||
|
if ds.DoesFileExists() {
|
||||||
|
fmt.Printf("Project was not found locally. But found %s file in path.\n", variables.DBFileName)
|
||||||
|
fmt.Printf("Importing Project for %s\n", workingDir)
|
||||||
|
|
||||||
|
name = prompt.StringPrompt("Project Name:")
|
||||||
|
pass = prompt.PasswordPrompt("Project Password (Same password that was used originally):")
|
||||||
|
|
||||||
|
envNames, err = ds.ListEnvironments()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d environments. importing %v\n", len(envNames), envNames)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// New Project
|
||||||
|
fmt.Printf("Creating new Project @ %s\n", workingDir)
|
||||||
|
name = prompt.StringPrompt("Project Name:")
|
||||||
|
pass = prompt.PasswordPrompt("Project Password:")
|
||||||
|
envs := strings.TrimSpace(prompt.StringPrompt("Please provide environment names separated by `,`. (stage,prod):"))
|
||||||
|
if len(envs) == 0 {
|
||||||
|
envs = "local"
|
||||||
|
}
|
||||||
|
envNames = strings.Split(envs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err = configuration.NewProject(name, workingDir, pass)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, eName := range envNames {
|
||||||
|
if err := project.NewEnv(eName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ds.CreateEnv(eName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configuration.Save()
|
||||||
|
fmt.Println("✅ Done")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
}
|
81
cmd/printenv.go
Normal file
81
cmd/printenv.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type printenvCmdFlags struct {
|
||||||
|
envName *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var printenvFlags = printenvCmdFlags{}
|
||||||
|
|
||||||
|
var printenvCmd = &cobra.Command{
|
||||||
|
Use: "printenv",
|
||||||
|
Short: "prints the whole environment in a .env format",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := initProject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return intErrors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) != 0 {
|
||||||
|
return intErrors.NewInvalidCommandError("expected 0 args.")
|
||||||
|
}
|
||||||
|
err = checkAmbiguousEnv(*printenvFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env := project.Environments[0]
|
||||||
|
if len(*printenvFlags.envName) > 0 {
|
||||||
|
e, err := project.GetEnv(*printenvFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env = e
|
||||||
|
}
|
||||||
|
datastore, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer datastore.Close()
|
||||||
|
vars, err := datastore.GetAll(env.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, v := range vars {
|
||||||
|
if v.Encrypted {
|
||||||
|
fmt.Println("# SENSITIVE VAR BELOW")
|
||||||
|
}
|
||||||
|
if len(v.Value) != len(strings.ReplaceAll(v.Value, " ", "")) {
|
||||||
|
// value contain spaces. need to wrap with ""
|
||||||
|
fmt.Printf("%s=\"%s\"\n", v.Key, v.Value)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s=%s\n", v.Key, v.Value)
|
||||||
|
}
|
||||||
|
if v.Encrypted {
|
||||||
|
fmt.Println("###")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
printenvFlags.envName = printenvCmd.Flags().StringP("environment", "e", "", "environments name")
|
||||||
|
err := printenvCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
57
cmd/project.go
Normal file
57
cmd/project.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type projectCmdFlags struct {
|
||||||
|
projectPath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectFlags = projectCmdFlags{}
|
||||||
|
|
||||||
|
var projectCmd = &cobra.Command{
|
||||||
|
Use: "project",
|
||||||
|
Short: "manage project",
|
||||||
|
Long: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var lsProjectCmd = &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "list all projects for this user",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
for _, proj := range configuration.Projects {
|
||||||
|
fmt.Printf("%s\t%s\n", proj.Name, proj.Path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var rmProjectCmd = &cobra.Command{
|
||||||
|
Use: "rm",
|
||||||
|
Short: "remove a project definition. the `.envoid` file will not be removed",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
project, err := configuration.GetProject(*projectFlags.projectPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configuration.RemoveProject(project)
|
||||||
|
configuration.Save()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
projectFlags.projectPath = rmProjectCmd.Flags().StringP("project-path", "p", "", "project path to remove")
|
||||||
|
err := rmProjectCmd.MarkFlagRequired("project-path")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = rmProjectCmd.RegisterFlagCompletionFunc("project-path", validProjectPathComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
projectCmd.AddCommand(lsProjectCmd)
|
||||||
|
projectCmd.AddCommand(rmProjectCmd)
|
||||||
|
}
|
62
cmd/rm.go
Normal file
62
cmd/rm.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rmCmdFlags struct {
|
||||||
|
envName *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var rmFlags = rmCmdFlags{}
|
||||||
|
|
||||||
|
var rmCmd = &cobra.Command{
|
||||||
|
Use: "rm <key>",
|
||||||
|
Short: "removes a variable from environment(s)",
|
||||||
|
Long: "",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := initProject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return errors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.NewInvalidCommandError("expected 1 args. <key>")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := args[0]
|
||||||
|
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %e", err)
|
||||||
|
}
|
||||||
|
defer ds.Close()
|
||||||
|
|
||||||
|
envs := project.Environments
|
||||||
|
if len(*rmFlags.envName) != 0 {
|
||||||
|
e, err := project.GetEnv(*rmFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
envs = []*types.Environment{e}
|
||||||
|
}
|
||||||
|
ds.RemoveVar(key, envs)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rmFlags.envName = rmCmd.Flags().StringP("environment", "e", "", "environments name")
|
||||||
|
err := rmCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
99
cmd/root.go
Normal file
99
cmd/root.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/config"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/variables"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/cobra/doc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configuration *config.Config = nil
|
||||||
|
var workingDir string
|
||||||
|
var project *types.Project = nil
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "envoid [command]",
|
||||||
|
Short: "envoid is an easy to use .env manager for personal (non production) use",
|
||||||
|
Long: `envoid is an easy to use .env manager for personal (non production) use.
|
||||||
|
|
||||||
|
envoid works offline and creates different encrypted environments for each project. It's mainly used to store,encrypt (with a passphrase) and share
|
||||||
|
.env variables`,
|
||||||
|
Version: fmt.Sprintf("%s commit %s", variables.Version, variables.Commit),
|
||||||
|
}
|
||||||
|
|
||||||
|
var docCmd = &cobra.Command{
|
||||||
|
Use: "doc",
|
||||||
|
Hidden: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := doc.GenMarkdownTree(rootCmd, "./docs")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAmbiguousEnv(envName string) error {
|
||||||
|
if len(envName) == 0 && len(project.Environments) > 1 {
|
||||||
|
return fmt.Errorf("You have more than 1 environment. please provide environment name")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initProject() error {
|
||||||
|
p, err := configuration.GetProject(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
project = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validEnvironmentNamesComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
envs := []string{}
|
||||||
|
if err := initProject(); err == nil {
|
||||||
|
for _, e := range project.Environments {
|
||||||
|
envs = append(envs, e.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envs, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func validProjectPathComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
paths := []string{}
|
||||||
|
for _, p := range configuration.Projects {
|
||||||
|
paths = append(paths, fmt.Sprintf("%s\t%s", p.Path, p.Name))
|
||||||
|
}
|
||||||
|
return paths, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(docCmd)
|
||||||
|
rootCmd.AddCommand(initCmd)
|
||||||
|
rootCmd.AddCommand(setCmd)
|
||||||
|
rootCmd.AddCommand(rmCmd)
|
||||||
|
rootCmd.AddCommand(getCmd)
|
||||||
|
rootCmd.AddCommand(envCmd)
|
||||||
|
rootCmd.AddCommand(printenvCmd)
|
||||||
|
rootCmd.AddCommand(importCmd)
|
||||||
|
rootCmd.AddCommand(projectCmd)
|
||||||
|
|
||||||
|
configuration = config.GetConfig()
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[error] %s. please run 'ssecret init'\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
workingDir = pwd
|
||||||
|
}
|
125
cmd/set.go
Normal file
125
cmd/set.go
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/common"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/datastore"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type setCmdFlags struct {
|
||||||
|
envName *string
|
||||||
|
encrypt *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var setFlags = setCmdFlags{}
|
||||||
|
|
||||||
|
var setCmd = &cobra.Command{
|
||||||
|
Use: "set [flags] <key> <value>",
|
||||||
|
Short: "sets a variable in environment(s)",
|
||||||
|
Long: "",
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 2 {
|
||||||
|
return errors.NewInvalidCommandError("expected 2 args. <key> <value>")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := initProject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return errors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
key := args[0]
|
||||||
|
val := args[1]
|
||||||
|
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ds.Close()
|
||||||
|
envs := project.Environments
|
||||||
|
if len(*setFlags.envName) != 0 {
|
||||||
|
e, err := project.GetEnv(*setFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
envs = []*types.Environment{e}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.SetValue(key, val, setFlags.encrypt, envs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %s\n", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var setEncryptCmd = &cobra.Command{
|
||||||
|
Use: "encrypt <key>",
|
||||||
|
Short: "encrypts an existing variable in environment(s)",
|
||||||
|
Long: "",
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.NewInvalidCommandError("expected 1 args. <key>")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := initProject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if project.IsEmpty() {
|
||||||
|
return errors.NewProjectEmptyError(project.Name)
|
||||||
|
}
|
||||||
|
key := args[0]
|
||||||
|
|
||||||
|
ds, err := datastore.NewDataStore()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ds.Close()
|
||||||
|
envs := project.Environments
|
||||||
|
if len(*setFlags.envName) != 0 {
|
||||||
|
e, err := project.GetEnv(*setFlags.envName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
envs = []*types.Environment{e}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, env := range envs {
|
||||||
|
// Get value
|
||||||
|
v, err := ds.GetVar(env.Name, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !v.Encrypted {
|
||||||
|
err := ds.SetValue(v.Key, v.Value, common.BoolP(true), []*types.Environment{env})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setFlags.envName = setCmd.PersistentFlags().StringP("environment", "e", "", "environments name")
|
||||||
|
setFlags.encrypt = setCmd.Flags().BoolP("secret", "s", false, "value is a secret. encrypt this value")
|
||||||
|
err := setCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
setCmd.AddCommand(setEncryptCmd)
|
||||||
|
}
|
30
docs/envoid.md
Normal file
30
docs/envoid.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
## envoid
|
||||||
|
|
||||||
|
envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
envoid is an easy to use .env manager for personal (non production) use.
|
||||||
|
|
||||||
|
envoid works offline and creates different encrypted environments for each project. It's mainly used to store,encrypt (with a passphrase) and share
|
||||||
|
.env variables
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for envoid
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
|
||||||
|
* [envoid env](envoid_env.md) - manage environments
|
||||||
|
* [envoid get](envoid_get.md) - Gets a variable
|
||||||
|
* [envoid import](envoid_import.md) - import a .env file into environment(s)
|
||||||
|
* [envoid init](envoid_init.md) - creates a new project for this path. default env is set to `local`
|
||||||
|
* [envoid printenv](envoid_printenv.md) - prints the whole environment in a .env format
|
||||||
|
* [envoid project](envoid_project.md) - manage project
|
||||||
|
* [envoid rm](envoid_rm.md) - removes a variable from environment(s)
|
||||||
|
* [envoid set](envoid_set.md) - sets a variable in environment(s)
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
25
docs/envoid_completion.md
Normal file
25
docs/envoid_completion.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
## envoid completion
|
||||||
|
|
||||||
|
Generate the autocompletion script for the specified shell
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate the autocompletion script for envoid for the specified shell.
|
||||||
|
See each sub-command's help for details on how to use the generated script.
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for completion
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
* [envoid completion bash](envoid_completion_bash.md) - Generate the autocompletion script for bash
|
||||||
|
* [envoid completion fish](envoid_completion_fish.md) - Generate the autocompletion script for fish
|
||||||
|
* [envoid completion powershell](envoid_completion_powershell.md) - Generate the autocompletion script for powershell
|
||||||
|
* [envoid completion zsh](envoid_completion_zsh.md) - Generate the autocompletion script for zsh
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
44
docs/envoid_completion_bash.md
Normal file
44
docs/envoid_completion_bash.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
## envoid completion bash
|
||||||
|
|
||||||
|
Generate the autocompletion script for bash
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate the autocompletion script for the bash shell.
|
||||||
|
|
||||||
|
This script depends on the 'bash-completion' package.
|
||||||
|
If it is not installed already, you can install it via your OS's package manager.
|
||||||
|
|
||||||
|
To load completions in your current shell session:
|
||||||
|
|
||||||
|
source <(envoid completion bash)
|
||||||
|
|
||||||
|
To load completions for every new session, execute once:
|
||||||
|
|
||||||
|
#### Linux:
|
||||||
|
|
||||||
|
envoid completion bash > /etc/bash_completion.d/envoid
|
||||||
|
|
||||||
|
#### macOS:
|
||||||
|
|
||||||
|
envoid completion bash > $(brew --prefix)/etc/bash_completion.d/envoid
|
||||||
|
|
||||||
|
You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid completion bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for bash
|
||||||
|
--no-descriptions disable completion descriptions
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
35
docs/envoid_completion_fish.md
Normal file
35
docs/envoid_completion_fish.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
## envoid completion fish
|
||||||
|
|
||||||
|
Generate the autocompletion script for fish
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate the autocompletion script for the fish shell.
|
||||||
|
|
||||||
|
To load completions in your current shell session:
|
||||||
|
|
||||||
|
envoid completion fish | source
|
||||||
|
|
||||||
|
To load completions for every new session, execute once:
|
||||||
|
|
||||||
|
envoid completion fish > ~/.config/fish/completions/envoid.fish
|
||||||
|
|
||||||
|
You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid completion fish [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for fish
|
||||||
|
--no-descriptions disable completion descriptions
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
32
docs/envoid_completion_powershell.md
Normal file
32
docs/envoid_completion_powershell.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
## envoid completion powershell
|
||||||
|
|
||||||
|
Generate the autocompletion script for powershell
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate the autocompletion script for powershell.
|
||||||
|
|
||||||
|
To load completions in your current shell session:
|
||||||
|
|
||||||
|
envoid completion powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
|
To load completions for every new session, add the output of the above command
|
||||||
|
to your powershell profile.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid completion powershell [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for powershell
|
||||||
|
--no-descriptions disable completion descriptions
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
46
docs/envoid_completion_zsh.md
Normal file
46
docs/envoid_completion_zsh.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
## envoid completion zsh
|
||||||
|
|
||||||
|
Generate the autocompletion script for zsh
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate the autocompletion script for the zsh shell.
|
||||||
|
|
||||||
|
If shell completion is not already enabled in your environment you will need
|
||||||
|
to enable it. You can execute the following once:
|
||||||
|
|
||||||
|
echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||||
|
|
||||||
|
To load completions in your current shell session:
|
||||||
|
|
||||||
|
source <(envoid completion zsh)
|
||||||
|
|
||||||
|
To load completions for every new session, execute once:
|
||||||
|
|
||||||
|
#### Linux:
|
||||||
|
|
||||||
|
envoid completion zsh > "${fpath[1]}/_envoid"
|
||||||
|
|
||||||
|
#### macOS:
|
||||||
|
|
||||||
|
envoid completion zsh > $(brew --prefix)/share/zsh/site-functions/_envoid
|
||||||
|
|
||||||
|
You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid completion zsh [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for zsh
|
||||||
|
--no-descriptions disable completion descriptions
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
18
docs/envoid_env.md
Normal file
18
docs/envoid_env.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
## envoid env
|
||||||
|
|
||||||
|
manage environments
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for env
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
* [envoid env add](envoid_env_add.md) - adds an environment
|
||||||
|
* [envoid env ls](envoid_env_ls.md) - list all environments in this project
|
||||||
|
* [envoid env rm](envoid_env_rm.md) - removes an environment
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
21
docs/envoid_env_add.md
Normal file
21
docs/envoid_env_add.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
## envoid env add
|
||||||
|
|
||||||
|
adds an environment
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid env add [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-b, --base-environment string base environment name (copy data from base)
|
||||||
|
-e, --environment string environment name
|
||||||
|
-h, --help help for add
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid env](envoid_env.md) - manage environments
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
19
docs/envoid_env_ls.md
Normal file
19
docs/envoid_env_ls.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
## envoid env ls
|
||||||
|
|
||||||
|
list all environments in this project
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid env ls [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for ls
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid env](envoid_env.md) - manage environments
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
20
docs/envoid_env_rm.md
Normal file
20
docs/envoid_env_rm.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
## envoid env rm
|
||||||
|
|
||||||
|
removes an environment
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid env rm [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environment name
|
||||||
|
-h, --help help for rm
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid env](envoid_env.md) - manage environments
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
20
docs/envoid_get.md
Normal file
20
docs/envoid_get.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
## envoid get
|
||||||
|
|
||||||
|
Gets a variable
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid get <key> [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environment name
|
||||||
|
-h, --help help for get
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
24
docs/envoid_import.md
Normal file
24
docs/envoid_import.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
## envoid import
|
||||||
|
|
||||||
|
import a .env file into environment(s)
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
This will not encrypt any value. You can then use `set encrypt KEY_NAME` to encrypt
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid import <file> [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environments name
|
||||||
|
-h, --help help for import
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
19
docs/envoid_init.md
Normal file
19
docs/envoid_init.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
## envoid init
|
||||||
|
|
||||||
|
creates a new project for this path. default env is set to `local`
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid init [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for init
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
20
docs/envoid_printenv.md
Normal file
20
docs/envoid_printenv.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
## envoid printenv
|
||||||
|
|
||||||
|
prints the whole environment in a .env format
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid printenv [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environments name
|
||||||
|
-h, --help help for printenv
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
17
docs/envoid_project.md
Normal file
17
docs/envoid_project.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
## envoid project
|
||||||
|
|
||||||
|
manage project
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for project
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
* [envoid project ls](envoid_project_ls.md) - list all projects for this user
|
||||||
|
* [envoid project rm](envoid_project_rm.md) - remove a project definition. the `.envoid` file will not be removed
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
19
docs/envoid_project_ls.md
Normal file
19
docs/envoid_project_ls.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
## envoid project ls
|
||||||
|
|
||||||
|
list all projects for this user
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid project ls [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for ls
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid project](envoid_project.md) - manage project
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
20
docs/envoid_project_rm.md
Normal file
20
docs/envoid_project_rm.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
## envoid project rm
|
||||||
|
|
||||||
|
remove a project definition. the `.envoid` file will not be removed
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid project rm [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for rm
|
||||||
|
-p, --project-path string project path to remove
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid project](envoid_project.md) - manage project
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
20
docs/envoid_rm.md
Normal file
20
docs/envoid_rm.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
## envoid rm
|
||||||
|
|
||||||
|
removes a variable from environment(s)
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid rm <key> [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environments name
|
||||||
|
-h, --help help for rm
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
22
docs/envoid_set.md
Normal file
22
docs/envoid_set.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
## envoid set
|
||||||
|
|
||||||
|
sets a variable in environment(s)
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid set [flags] <key> <value>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environments name
|
||||||
|
-h, --help help for set
|
||||||
|
-s, --secret value is a secret. encrypt this value
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
|
||||||
|
* [envoid set encrypt](envoid_set_encrypt.md) - encrypts an existing variable in environment(s)
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
25
docs/envoid_set_encrypt.md
Normal file
25
docs/envoid_set_encrypt.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
## envoid set encrypt
|
||||||
|
|
||||||
|
encrypts an existing variable in environment(s)
|
||||||
|
|
||||||
|
```
|
||||||
|
envoid set encrypt <key> [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for encrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
-e, --environment string environments name
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [envoid set](envoid_set.md) - sets a variable in environment(s)
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 12-Dec-2024
|
30
go.mod
Normal file
30
go.mod
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module git.dayanhub.com/sagi/envoid
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/creasty/defaults v1.8.0
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/spf13/cobra v1.8.1
|
||||||
|
golang.org/x/crypto v0.29.0
|
||||||
|
golang.org/x/sync v0.9.0
|
||||||
|
golang.org/x/term v0.26.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.5.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
modernc.org/libc v1.37.6 // indirect
|
||||||
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
|
modernc.org/memory v1.7.2 // indirect
|
||||||
|
modernc.org/sqlite v1.28.0 // indirect
|
||||||
|
)
|
47
go.sum
Normal file
47
go.sum
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||||
|
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
|
||||||
|
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
||||||
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
|
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||||
|
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||||
|
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
||||||
|
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
9
internal/common/pointers.go
Normal file
9
internal/common/pointers.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
func BoolP(val bool) *bool {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringP(val string) *string {
|
||||||
|
return &val
|
||||||
|
}
|
10
internal/common/str_convert.go
Normal file
10
internal/common/str_convert.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func StrToSnakeCase(s string) string {
|
||||||
|
snake := strings.ToLower(s)
|
||||||
|
snake = strings.ReplaceAll(snake, " ", "_")
|
||||||
|
snake = strings.ReplaceAll(snake, "\t", "_")
|
||||||
|
return snake
|
||||||
|
}
|
145
internal/config/config.go
Normal file
145
internal/config/config.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"github.com/creasty/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
PWD string `json:"-"`
|
||||||
|
Projects []*types.Project `json:"projects" default:"[]"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var configPath string
|
||||||
|
var configStruct *Config
|
||||||
|
|
||||||
|
func (c *Config) GetProject(projectPath string) (*types.Project, error) {
|
||||||
|
var p *types.Project
|
||||||
|
for _, project := range configStruct.Projects {
|
||||||
|
if project.Path == projectPath {
|
||||||
|
p = project
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p == nil {
|
||||||
|
return nil, errors.NewProjectNotFoundError(projectPath)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) RemoveProject(project *types.Project) {
|
||||||
|
projects := []*types.Project{}
|
||||||
|
for _, p := range c.Projects {
|
||||||
|
if p.Name != project.Name && p.Path != project.Path {
|
||||||
|
projects = append(projects, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Projects = projects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) NewProject(name string, path string, password string) (*types.Project, error) {
|
||||||
|
encPass := base64.RawStdEncoding.EncodeToString([]byte(password))
|
||||||
|
|
||||||
|
p := &types.Project{
|
||||||
|
Name: name,
|
||||||
|
Path: path,
|
||||||
|
Password: encPass,
|
||||||
|
Environments: []*types.Environment{},
|
||||||
|
}
|
||||||
|
configStruct.Projects = append(configStruct.Projects, p)
|
||||||
|
SaveConfig()
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfig() *Config {
|
||||||
|
return configStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Save() {
|
||||||
|
SaveConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateConfigIfNotExists() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
userConfigDir, err := os.UserConfigDir()
|
||||||
|
configDir := path.Join(userConfigDir, "envoid")
|
||||||
|
configPath = path.Join(configDir, "config.json")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[ERROR] Failed to fetch user config directory. %e\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||||
|
err := os.MkdirAll(configDir, 0700)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var configFile *os.File
|
||||||
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
|
configFile, err = os.Create(configPath)
|
||||||
|
defer func() {
|
||||||
|
err = configFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[ERROR] Failed to create config file @ %s. %e\n", configPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configStruct, err = loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[ERROR] Failed to load config file @ %s. %e\n", configPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() (*Config, error) {
|
||||||
|
c := &Config{}
|
||||||
|
err := defaults.Set(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
file, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(file) == 0 {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(file, c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.PWD = pwd
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveConfig() {
|
||||||
|
yml, err := json.Marshal(configStruct)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[ERROR] Failed to convert config to json. %e\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(configPath, yml, 0600)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[ERROR] Failed to save config file @ %s. %e\n", configPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
223
internal/datastore/datastore.go
Normal file
223
internal/datastore/datastore.go
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/common"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/config"
|
||||||
|
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/variables"
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type datastore struct {
|
||||||
|
db *db
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataStore() (*datastore, error) {
|
||||||
|
db, err := newDB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &datastore{
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) CreateEnv(name string) error {
|
||||||
|
table_name := envNameToTableName(name)
|
||||||
|
return d.db.createTableIfNotExists(table_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) DoesFileExists() bool {
|
||||||
|
pwd := config.GetConfig().PWD
|
||||||
|
filePath := fmt.Sprintf("%s/%s", pwd, variables.DBFileName)
|
||||||
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) ListEnvironments() ([]string, error) {
|
||||||
|
return d.db.listTables()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) CreateEnvOffExsisting(new_env string, base_env string) error {
|
||||||
|
table_name_new := envNameToTableName(new_env)
|
||||||
|
table_name_base := envNameToTableName(base_env)
|
||||||
|
err := d.CreateEnv(new_env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.db.copyContentFromTo(table_name_base, table_name_new)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) Close() error {
|
||||||
|
return d.db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) SetValue(key string, value string, encrypted *bool, envs []*types.Environment) error {
|
||||||
|
if encrypted == nil {
|
||||||
|
encrypted = common.BoolP(false)
|
||||||
|
}
|
||||||
|
if *encrypted {
|
||||||
|
v, err := enc(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value = *v
|
||||||
|
}
|
||||||
|
for _, env := range envs {
|
||||||
|
table_name := envNameToTableName(env.Name)
|
||||||
|
if err := d.db.setVar(table_name, key, value, *encrypted); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) GetAll(envName string) ([]*types.EnvVar, error) {
|
||||||
|
table_name := envNameToTableName(envName)
|
||||||
|
vars, err := d.db.getAll(table_name)
|
||||||
|
if err != nil {
|
||||||
|
return vars, err
|
||||||
|
}
|
||||||
|
g := new(errgroup.Group)
|
||||||
|
for _, v := range vars {
|
||||||
|
g.Go(func() error {
|
||||||
|
if v.Encrypted {
|
||||||
|
if v.Value, err = dec(v.Value); err != nil {
|
||||||
|
return &intErrors.InvalidPasswordError{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
return vars, err
|
||||||
|
}
|
||||||
|
sort.SliceStable(vars, func(i, j int) bool {
|
||||||
|
return vars[i].Key < vars[j].Key
|
||||||
|
})
|
||||||
|
return vars, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) RemoveVar(key string, envs []*types.Environment) {
|
||||||
|
for _, env := range envs {
|
||||||
|
table_name := envNameToTableName(env.Name)
|
||||||
|
d.db.rmVar(table_name, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) GetVar(envName string, key string) (*types.EnvVar, error) {
|
||||||
|
table_name := envNameToTableName(envName)
|
||||||
|
v, err := d.db.getVar(table_name, key)
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
if v.Encrypted {
|
||||||
|
if v.Value, err = dec(v.Value); err != nil {
|
||||||
|
return v, &intErrors.InvalidPasswordError{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *datastore) RemoveEnv(envName string) error {
|
||||||
|
table_name := envNameToTableName(envName)
|
||||||
|
return d.db.deleteTable(table_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enc(s string) (*string, error) {
|
||||||
|
conf := config.GetConfig()
|
||||||
|
proj, _ := conf.GetProject(conf.PWD)
|
||||||
|
key, salt, err := deriveKey([]byte(proj.Password), nil)
|
||||||
|
data := []byte(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockCipher, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(blockCipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err = rand.Read(nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
||||||
|
|
||||||
|
ciphertext = append(ciphertext, salt...)
|
||||||
|
str := string(ciphertext)
|
||||||
|
|
||||||
|
return &str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dec(s string) (string, error) {
|
||||||
|
data := []byte(s)
|
||||||
|
salt, data := data[len(data)-32:], data[:len(data)-32]
|
||||||
|
|
||||||
|
conf := config.GetConfig()
|
||||||
|
proj, _ := conf.GetProject(conf.PWD)
|
||||||
|
key, _, err := deriveKey([]byte(proj.Password), salt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockCipher, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(blockCipher)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||||
|
|
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
str := string(plaintext)
|
||||||
|
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
||||||
|
if salt == nil {
|
||||||
|
salt = make([]byte, 32)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := scrypt.Key(password, salt, 1048576, 8, 1, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, salt, nil
|
||||||
|
}
|
135
internal/datastore/db.go
Normal file
135
internal/datastore/db.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/common"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/types"
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/variables"
|
||||||
|
_ "github.com/glebarez/go-sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type db struct {
|
||||||
|
con *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDB() (*db, error) {
|
||||||
|
con, err := sql.Open("sqlite", variables.DBFileName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &db{
|
||||||
|
con: con,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) close() error {
|
||||||
|
return d.con.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) createTableIfNotExists(table_name string) error {
|
||||||
|
_, err := d.con.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY NOT NULL, value BLOB NOT NULL, encrypted BOOL NOT NULL);", table_name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) listTables() ([]string, error) {
|
||||||
|
tables := []string{}
|
||||||
|
q := "SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%' AND name LIKE ?"
|
||||||
|
rows, err := d.con.Query(q, fmt.Sprintf("%s_%%", variables.DBTablePrefix))
|
||||||
|
if err != nil {
|
||||||
|
return tables, err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var table string
|
||||||
|
if err := rows.Scan(&table); err != nil {
|
||||||
|
return tables, err
|
||||||
|
}
|
||||||
|
tables = append(tables, tableNameToEnvName(table))
|
||||||
|
}
|
||||||
|
return tables, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) copyContentFromTo(table_name_target string, table_name_dest string) error {
|
||||||
|
_, err := d.con.Exec(fmt.Sprintf("INSERT INTO %s (key, value, encrypted) SELECT * FROM %s", table_name_dest, table_name_target))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) deleteTable(table_name string) error {
|
||||||
|
_, err := d.con.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", table_name))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) getVar(table_name string, key string) (*types.EnvVar, error) {
|
||||||
|
q := fmt.Sprintf("SELECT * FROM %s WHERE key=?", table_name)
|
||||||
|
row := d.con.QueryRow(q, key)
|
||||||
|
envVar := &types.EnvVar{}
|
||||||
|
err := row.Scan(&envVar.Key, &envVar.Value, &envVar.Encrypted)
|
||||||
|
if err != nil {
|
||||||
|
return envVar, errors.NewNoKeyFoundError(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return envVar, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) getAll(table_name string) ([]*types.EnvVar, error) {
|
||||||
|
entries := []*types.EnvVar{}
|
||||||
|
rows, err := d.con.Query(fmt.Sprintf("SELECT * FROM %s", table_name))
|
||||||
|
if err != nil {
|
||||||
|
return entries, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
key := ""
|
||||||
|
value := ""
|
||||||
|
enc := common.BoolP(false)
|
||||||
|
err := rows.Scan(&key, &value, enc)
|
||||||
|
if err != nil {
|
||||||
|
return entries, err
|
||||||
|
}
|
||||||
|
e := &types.EnvVar{
|
||||||
|
Key: key,
|
||||||
|
Value: value,
|
||||||
|
Encrypted: *enc,
|
||||||
|
}
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) setVar(table_name string, key string, value string, encrypted bool) error {
|
||||||
|
q := fmt.Sprintf("INSERT INTO %s (key, value, encrypted) values ( ? , ? , ? ) ON CONFLICT (key) DO UPDATE SET value = ?, encrypted = ?", table_name)
|
||||||
|
_, err := d.con.Exec(q, key, value, encrypted, value, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return errors.NewSecretExsistsError(key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *db) rmVar(table_name string, key string) {
|
||||||
|
q := fmt.Sprintf("DELETE FROM %s WHERE key = ?", table_name)
|
||||||
|
_, _ = d.con.Exec(q, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envNameToTableName(envName string) string {
|
||||||
|
envSanke := common.StrToSnakeCase(envName)
|
||||||
|
return fmt.Sprintf("%s_%s", variables.DBTablePrefix, envSanke)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableNameToEnvName(tableName string) string {
|
||||||
|
// remove '<prefix>_'
|
||||||
|
return tableName[len(variables.DBTablePrefix)+1:]
|
||||||
|
}
|
15
internal/errors/env_already_exists.go
Normal file
15
internal/errors/env_already_exists.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type EnvironmentExistsError struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EnvironmentExistsError) Error() string {
|
||||||
|
return fmt.Sprintf("environment %s already exists", e.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnvironmentExistsError(name string) *EnvironmentExistsError {
|
||||||
|
return &EnvironmentExistsError{name: name}
|
||||||
|
}
|
16
internal/errors/env_not_found.go
Normal file
16
internal/errors/env_not_found.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type EnvironmentNotFoundError struct {
|
||||||
|
path string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EnvironmentNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("No env '%s' definition for path %s. Did you initialize?", e.name, e.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnvironmentNotFoundError(path string, name string) *EnvironmentNotFoundError {
|
||||||
|
return &EnvironmentNotFoundError{path: path, name: name}
|
||||||
|
}
|
15
internal/errors/invalid_command.go
Normal file
15
internal/errors/invalid_command.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type InvalidCommandError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidCommandError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid command. %v", e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInvalidCommandError(msg string) *InvalidCommandError {
|
||||||
|
return &InvalidCommandError{msg: msg}
|
||||||
|
}
|
16
internal/errors/invalid_flag_value.go
Normal file
16
internal/errors/invalid_flag_value.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type InvalidFlagValueError struct {
|
||||||
|
flag string
|
||||||
|
invalidValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidFlagValueError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid value '%s' for flag --%s", e.invalidValue, e.flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInvalidFlagValueError(flag string, invalidValue string) *InvalidFlagValueError {
|
||||||
|
return &InvalidFlagValueError{flag: flag, invalidValue: invalidValue}
|
||||||
|
}
|
12
internal/errors/invalid_password.go
Normal file
12
internal/errors/invalid_password.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
type InvalidPasswordError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidPasswordError) Error() string {
|
||||||
|
return "invalid password. is your environment set correctly?"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInvalidPasswordError() *InvalidPasswordError {
|
||||||
|
return &InvalidPasswordError{}
|
||||||
|
}
|
15
internal/errors/key_not_found.go
Normal file
15
internal/errors/key_not_found.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type NoKeyFoundError struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *NoKeyFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("key %s not found", e.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoKeyFoundError(key string) *NoKeyFoundError {
|
||||||
|
return &NoKeyFoundError{key: key}
|
||||||
|
}
|
15
internal/errors/no_config_found.go
Normal file
15
internal/errors/no_config_found.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type NoConfigFoundError struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *NoConfigFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("no config file found in %s", e.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoConfigFoundError(path string) *NoConfigFoundError {
|
||||||
|
return &NoConfigFoundError{path: path}
|
||||||
|
}
|
15
internal/errors/project_is_empty.go
Normal file
15
internal/errors/project_is_empty.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type ProjectEmptyError struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ProjectEmptyError) Error() string {
|
||||||
|
return fmt.Sprintf("Project %s does not have any environments.", e.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectEmptyError(name string) *ProjectEmptyError {
|
||||||
|
return &ProjectEmptyError{name: name}
|
||||||
|
}
|
15
internal/errors/project_not_found.go
Normal file
15
internal/errors/project_not_found.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type ProjectNotFoundError struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ProjectNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("No project found for path '%s'. Did you initialize?", e.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectNotFoundError(path string) *ProjectNotFoundError {
|
||||||
|
return &ProjectNotFoundError{path: path}
|
||||||
|
}
|
15
internal/errors/secret_exists.go
Normal file
15
internal/errors/secret_exists.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type SecretExistsError struct {
|
||||||
|
secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SecretExistsError) Error() string {
|
||||||
|
return fmt.Sprintf("secret %s already exists", e.secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSecretExsistsError(secret string) *SecretExistsError {
|
||||||
|
return &SecretExistsError{secret: secret}
|
||||||
|
}
|
23
internal/prompt/password.go
Normal file
23
internal/prompt/password.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package prompt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PasswordPrompt(label string) string {
|
||||||
|
var s string
|
||||||
|
for {
|
||||||
|
fmt.Fprint(os.Stderr, label+" ")
|
||||||
|
b, _ := term.ReadPassword(int(syscall.Stdin))
|
||||||
|
s = string(b)
|
||||||
|
if s != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
return s
|
||||||
|
}
|
21
internal/prompt/string.go
Normal file
21
internal/prompt/string.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package prompt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StringPrompt(label string) string {
|
||||||
|
var s string
|
||||||
|
r := bufio.NewReader(os.Stdin)
|
||||||
|
for {
|
||||||
|
fmt.Fprint(os.Stderr, label+" ")
|
||||||
|
s, _ = r.ReadString('\n')
|
||||||
|
if s != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
7
internal/types/env_var.go
Normal file
7
internal/types/env_var.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type EnvVar struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
Encrypted bool
|
||||||
|
}
|
6
internal/types/environment.go
Normal file
6
internal/types/environment.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type Environment struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"-"`
|
||||||
|
}
|
58
internal/types/project.go
Normal file
58
internal/types/project.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.dayanhub.com/sagi/envoid/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Project struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Environments []*Environment `json:"envorinments" default:"[]"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) GetEnv(name string) (*Environment, error) {
|
||||||
|
var e *Environment
|
||||||
|
for _, env := range p.Environments {
|
||||||
|
if env.Name == name {
|
||||||
|
passByte, err := base64.RawStdEncoding.DecodeString(p.Password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[ERROR] Failed to decode project password @ %s. %e\n", p.Path, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
e = &Environment{
|
||||||
|
Name: env.Name,
|
||||||
|
Password: string(passByte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e == nil {
|
||||||
|
return nil, errors.NewEnvironmentNotFoundError(p.Path, name)
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
func (p *Project) NewEnv(name string) error {
|
||||||
|
e := &Environment{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
p.Environments = append(p.Environments, e)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) RemoveEnv(name string) {
|
||||||
|
environments := []*Environment{}
|
||||||
|
for _, env := range p.Environments {
|
||||||
|
if env.Name != name {
|
||||||
|
environments = append(environments, env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Environments = environments
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) IsEmpty() bool {
|
||||||
|
return len(p.Environments) == 0
|
||||||
|
}
|
7
internal/variables/constants.go
Normal file
7
internal/variables/constants.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package variables
|
||||||
|
|
||||||
|
const (
|
||||||
|
DBFileName = ".envoid"
|
||||||
|
DBTablePrefix = "envoid"
|
||||||
|
ConfigFolder = "envoid"
|
||||||
|
)
|
6
internal/variables/variables.go
Normal file
6
internal/variables/variables.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package variables
|
||||||
|
|
||||||
|
var (
|
||||||
|
Commit string = "HEAD"
|
||||||
|
Version string = "development"
|
||||||
|
)
|
9
main.go
Normal file
9
main.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.dayanhub.com/sagi/envoid/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
6
renovate.json
Normal file
6
renovate.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"reviewers": [
|
||||||
|
"sagi"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue