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
693350fa49
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
|
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