diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 46e55055..2d35851f 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://github.com/actions/checkout@v4 - name: Annotate locations with typos uses: "https://github.com/codespell-project/codespell-problem-matcher@v1" - name: Codespell @@ -25,14 +25,31 @@ jobs: check_hidden: true skip: ./.git - compile: - name: Make sure build does not fail + code_quality: + name: Code quality runs-on: ubuntu-latest needs: - codespell if: ${{ success() }} steps: - - uses: https://code.forgejo.org/actions/checkout@v4 + - name: pull + uses: https://github.com/actions/checkout@v4 + - name: download golangci-lint + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh + - name: install golangci-lint + run: sh ./install.sh -s v1.62.2 + - name: lint + run: ./bin/golangci-lint run + + compile: + name: Make sure build does not fail + runs-on: ubuntu-latest + needs: + - codespell + - lint + if: ${{ success() }} + steps: + - uses: https://github.com/actions/checkout@v4 name: checkout - name: install alsa devel run: apt update && apt install libasound2-dev -y @@ -52,6 +69,7 @@ jobs: runs-on: ubuntu-latest needs: - codespell + - lint - compile if: ${{ failure() }} env: diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..ecb3b0b9 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,12 @@ +linters: + enable-all: true + disable: + - wrapcheck + - exhaustruct + - varnamelen + - gochecknoglobals + - depguard + - gochecknoinits + - forbidigo + - revive + - gosec diff --git a/Makefile b/Makefile index 9f591702..f63703be 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ GO_BUILD_LD_FLAGS=-ldflags="-s -w -X 'git.dayanhub.com/sagi/envoid/internal/vari 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 + #GOARCH=amd64 GOOS=windows go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-windows main.go .PHONY: clean clean: diff --git a/cmd/env.go b/cmd/env.go index 8f5be042..39a74ff2 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -37,12 +37,15 @@ var rmEnvCmd = &cobra.Command{ return err } defer ds.Close() + err = ds.RemoveEnv(*envFlags.rmEnvName) if err != nil { return err } + project.RemoveEnv(*envFlags.rmEnvName) configuration.Save() + return nil }, } @@ -91,6 +94,7 @@ var addEnvCmd = &cobra.Command{ return err } configuration.Save() + return nil }, } @@ -119,21 +123,27 @@ var lsEnvCmd = &cobra.Command{ 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)") + + 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 + // 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) diff --git a/cmd/get.go b/cmd/get.go index 9eb9b090..36279a60 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -1,10 +1,10 @@ package cmd import ( + "errors" "fmt" "os" - "errors" "git.dayanhub.com/sagi/envoid/internal/datastore" intErrors "git.dayanhub.com/sagi/envoid/internal/errors" "github.com/spf13/cobra" @@ -68,12 +68,14 @@ var getCmd = &cobra.Command{ } } 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) diff --git a/cmd/import.go b/cmd/import.go index 56b7a83a..79e1a13e 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -39,8 +39,9 @@ var importCmd = &cobra.Command{ } envs = []*types.Environment{e} } + if len(args) != 1 { - return fmt.Errorf("Needs a file to parse") + return errors.NewInvalidCommandError("Missing a file to parse") } file, err := os.Open(args[0]) @@ -70,6 +71,7 @@ var importCmd = &cobra.Command{ func init() { importFlags.envName = importCmd.Flags().StringP("environment", "e", "", "environments name") + err := importCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete) if err != nil { panic(err) diff --git a/cmd/init.go b/cmd/init.go index d3df77ea..3e0bc0f5 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -18,6 +18,7 @@ var initCmd = &cobra.Command{ _, 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() @@ -30,7 +31,7 @@ var initCmd = &cobra.Command{ var pass string var envNames []string - //check if we are importing an existing file + // 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) @@ -71,6 +72,7 @@ var initCmd = &cobra.Command{ } configuration.Save() fmt.Println("✅ Done") + return nil }, } diff --git a/cmd/printenv.go b/cmd/printenv.go index a21f6f6c..5f630d19 100644 --- a/cmd/printenv.go +++ b/cmd/printenv.go @@ -44,13 +44,13 @@ var printenvCmd = &cobra.Command{ } env = e } - datastore, err := datastore.NewDataStore() + ds, err := datastore.NewDataStore() if err != nil { fmt.Printf("Error: %e", err) } - defer datastore.Close() - vars, err := datastore.GetAll(env.Name) + defer ds.Close() + vars, err := ds.GetAll(env.Name) if err != nil { return err } @@ -68,12 +68,14 @@ var printenvCmd = &cobra.Command{ 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) diff --git a/cmd/project.go b/cmd/project.go index de5924de..2ca77953 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -25,9 +25,11 @@ var lsProjectCmd = &cobra.Command{ 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", @@ -36,22 +38,27 @@ var rmProjectCmd = &cobra.Command{ 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) } diff --git a/cmd/rm.go b/cmd/rm.go index 05c5b214..9e2d6729 100644 --- a/cmd/rm.go +++ b/cmd/rm.go @@ -55,6 +55,7 @@ var rmCmd = &cobra.Command{ func init() { rmFlags.envName = rmCmd.Flags().StringP("environment", "e", "", "environments name") + err := rmCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete) if err != nil { panic(err) diff --git a/cmd/root.go b/cmd/root.go index 4ab2197b..fdc27d5b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,22 +5,26 @@ import ( "os" "git.dayanhub.com/sagi/envoid/internal/config" + "git.dayanhub.com/sagi/envoid/internal/errors" "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 ( + configuration *config.Config = nil + workingDir string + 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 +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), } @@ -33,6 +37,7 @@ var docCmd = &cobra.Command{ if err != nil { return err } + return nil }, } @@ -46,8 +51,9 @@ func Execute() { 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 errors.NewNoEnvProvidedError() } + return nil } @@ -56,25 +62,34 @@ func initProject() error { if err != nil { return err } + project = p + return nil } -func validEnvironmentNamesComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +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) { +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 } @@ -90,10 +105,12 @@ func init() { 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 } diff --git a/cmd/set.go b/cmd/set.go index 4c7baec4..c1ce8f73 100644 --- a/cmd/set.go +++ b/cmd/set.go @@ -21,13 +21,15 @@ var setCmd = &cobra.Command{ Use: "set [flags] ", Short: "sets a variable in environment(s)", Long: "", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 2 { + Args: func(_ *cobra.Command, args []string) error { + expectedArgs := 2 + if len(args) != expectedArgs { return errors.NewInvalidCommandError("expected 2 args. ") } + return nil }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { err := initProject() if err != nil { return err @@ -57,6 +59,7 @@ var setCmd = &cobra.Command{ if err != nil { fmt.Printf("Error: %s\n", err.Error()) } + return nil }, } @@ -65,13 +68,15 @@ var setEncryptCmd = &cobra.Command{ Use: "encrypt ", Short: "encrypts an existing variable in environment(s)", Long: "", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 1 { + Args: func(_ *cobra.Command, args []string) error { + expectedArgs := 1 + if len(args) != expectedArgs { return errors.NewInvalidCommandError("expected 1 args. ") } + return nil }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { err := initProject() if err != nil { return err @@ -117,9 +122,11 @@ var setEncryptCmd = &cobra.Command{ 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) } diff --git a/docs/envoid.md b/docs/envoid.md index 7060f2ce..a52f1c27 100644 --- a/docs/envoid.md +++ b/docs/envoid.md @@ -6,7 +6,8 @@ envoid is an easy to use .env manager for personal (non production) use 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 +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 @@ -27,4 +28,4 @@ envoid works offline and creates different encrypted environments for each proje * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_completion.md b/docs/envoid_completion.md index 4e823ce2..d345b197 100644 --- a/docs/envoid_completion.md +++ b/docs/envoid_completion.md @@ -22,4 +22,4 @@ See each sub-command's help for details on how to use the generated script. * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_completion_bash.md b/docs/envoid_completion_bash.md index b6c60345..056040e1 100644 --- a/docs/envoid_completion_bash.md +++ b/docs/envoid_completion_bash.md @@ -41,4 +41,4 @@ envoid completion bash * [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_completion_fish.md b/docs/envoid_completion_fish.md index 54743fe9..e6831ebc 100644 --- a/docs/envoid_completion_fish.md +++ b/docs/envoid_completion_fish.md @@ -32,4 +32,4 @@ envoid completion fish [flags] * [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_completion_powershell.md b/docs/envoid_completion_powershell.md index e33c5b6c..5d22604e 100644 --- a/docs/envoid_completion_powershell.md +++ b/docs/envoid_completion_powershell.md @@ -29,4 +29,4 @@ envoid completion powershell [flags] * [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_completion_zsh.md b/docs/envoid_completion_zsh.md index 3fbb428e..5c696f97 100644 --- a/docs/envoid_completion_zsh.md +++ b/docs/envoid_completion_zsh.md @@ -43,4 +43,4 @@ envoid completion zsh [flags] * [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_env.md b/docs/envoid_env.md index ccd4e3ae..a8754b4f 100644 --- a/docs/envoid_env.md +++ b/docs/envoid_env.md @@ -15,4 +15,4 @@ manage environments * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_env_add.md b/docs/envoid_env_add.md index cb8cba9c..2ea65efa 100644 --- a/docs/envoid_env_add.md +++ b/docs/envoid_env_add.md @@ -18,4 +18,4 @@ envoid env add [flags] * [envoid env](envoid_env.md) - manage environments -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_env_ls.md b/docs/envoid_env_ls.md index 1eaf89df..8d429ded 100644 --- a/docs/envoid_env_ls.md +++ b/docs/envoid_env_ls.md @@ -16,4 +16,4 @@ envoid env ls [flags] * [envoid env](envoid_env.md) - manage environments -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_env_rm.md b/docs/envoid_env_rm.md index 3a53af9f..b1039892 100644 --- a/docs/envoid_env_rm.md +++ b/docs/envoid_env_rm.md @@ -17,4 +17,4 @@ envoid env rm [flags] * [envoid env](envoid_env.md) - manage environments -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_get.md b/docs/envoid_get.md index e6ee348c..c2094df9 100644 --- a/docs/envoid_get.md +++ b/docs/envoid_get.md @@ -17,4 +17,4 @@ envoid get [flags] * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_import.md b/docs/envoid_import.md index 6a3672db..43aeb26a 100644 --- a/docs/envoid_import.md +++ b/docs/envoid_import.md @@ -21,4 +21,4 @@ envoid import [flags] * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_init.md b/docs/envoid_init.md index ede4d132..35b8fdba 100644 --- a/docs/envoid_init.md +++ b/docs/envoid_init.md @@ -16,4 +16,4 @@ envoid init [flags] * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_printenv.md b/docs/envoid_printenv.md index 7a0013ec..56ccf41a 100644 --- a/docs/envoid_printenv.md +++ b/docs/envoid_printenv.md @@ -17,4 +17,4 @@ envoid printenv [flags] * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_project.md b/docs/envoid_project.md index a4b11ba1..f0b67a8c 100644 --- a/docs/envoid_project.md +++ b/docs/envoid_project.md @@ -14,4 +14,4 @@ manage project * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_project_ls.md b/docs/envoid_project_ls.md index ab0721ee..4131cea3 100644 --- a/docs/envoid_project_ls.md +++ b/docs/envoid_project_ls.md @@ -16,4 +16,4 @@ envoid project ls [flags] * [envoid project](envoid_project.md) - manage project -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_project_rm.md b/docs/envoid_project_rm.md index 2d997fc5..701a0158 100644 --- a/docs/envoid_project_rm.md +++ b/docs/envoid_project_rm.md @@ -17,4 +17,4 @@ envoid project rm [flags] * [envoid project](envoid_project.md) - manage project -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_rm.md b/docs/envoid_rm.md index 36737859..28e5a196 100644 --- a/docs/envoid_rm.md +++ b/docs/envoid_rm.md @@ -17,4 +17,4 @@ envoid rm [flags] * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_set.md b/docs/envoid_set.md index e9ace4fc..922868de 100644 --- a/docs/envoid_set.md +++ b/docs/envoid_set.md @@ -19,4 +19,4 @@ envoid set [flags] * [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 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/docs/envoid_set_encrypt.md b/docs/envoid_set_encrypt.md index 65ce51fe..12e2c32b 100644 --- a/docs/envoid_set_encrypt.md +++ b/docs/envoid_set_encrypt.md @@ -22,4 +22,4 @@ envoid set encrypt [flags] * [envoid set](envoid_set.md) - sets a variable in environment(s) -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated by spf13/cobra on 16-Dec-2024 diff --git a/go.sum b/go.sum index 10ea5dfb..34ba17fc 100644 --- a/go.sum +++ b/go.sum @@ -24,21 +24,13 @@ 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/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.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/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.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= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/internal/common/str_convert.go b/internal/common/str_convert.go index 56e4dbc8..0be130e1 100644 --- a/internal/common/str_convert.go +++ b/internal/common/str_convert.go @@ -6,5 +6,6 @@ func StrToSnakeCase(s string) string { snake := strings.ToLower(s) snake = strings.ReplaceAll(snake, " ", "_") snake = strings.ReplaceAll(snake, "\t", "_") + return snake } diff --git a/internal/config/config.go b/internal/config/config.go index a81d00a0..e7e9b6f9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/fs" "os" "path" @@ -14,33 +15,40 @@ import ( type Config struct { PWD string `json:"-"` - Projects []*types.Project `json:"projects" default:"[]"` + Projects []*types.Project `default:"[]" json:"projects"` } 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 } @@ -54,7 +62,9 @@ func (c *Config) NewProject(name string, path string, password string) (*types.P Environments: []*types.Environment{}, } configStruct.Projects = append(configStruct.Projects, p) + SaveConfig() + return p, nil } @@ -79,46 +89,53 @@ func init() { 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) + var folderPermissions fs.FileMode = 0o700 + + err := os.MkdirAll(configDir, folderPermissions) 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) } + + defer configFile.Close() } + configStruct, err = loadConfig() if err != nil { fmt.Printf("[ERROR] Failed to load config file @ %s. %e\n", configPath, err) - os.Exit(1) + configFile.Close() + panic("") } } 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 } @@ -127,7 +144,9 @@ func loadConfig() (*Config, error) { if err != nil { return nil, err } + c.PWD = pwd + return c, nil } @@ -137,7 +156,10 @@ func SaveConfig() { fmt.Printf("[ERROR] Failed to convert config to json. %e\n", err) os.Exit(1) } - err = os.WriteFile(configPath, yml, 0600) + + var filePermissions fs.FileMode = 0o600 + + err = os.WriteFile(configPath, yml, filePermissions) if err != nil { fmt.Printf("[ERROR] Failed to save config file @ %s. %e\n", configPath, err) os.Exit(1) diff --git a/internal/datastore/datastore.go b/internal/datastore/datastore.go index 24a47265..4f4e47c2 100644 --- a/internal/datastore/datastore.go +++ b/internal/datastore/datastore.go @@ -18,84 +18,98 @@ import ( "golang.org/x/sync/errgroup" ) -type datastore struct { +type Datastore struct { db *db } -func NewDataStore() (*datastore, error) { +func NewDataStore() (*Datastore, error) { db, err := newDB() if err != nil { return nil, err } - return &datastore{ + 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) CreateEnv(name string) error { + tableName := envNameToTableName(name) + + return d.db.createTableIfNotExists(tableName) } -func (d *datastore) DoesFileExists() bool { +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) { +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) +func (d *Datastore) CreateEnvOffExsisting(newEnv string, baseEnv string) error { + tableNameName := envNameToTableName(newEnv) + tableNameBase := envNameToTableName(baseEnv) + + err := d.CreateEnv(newEnv) if err != nil { return err } - err = d.db.copyContentFromTo(table_name_base, table_name_new) + + err = d.db.copyContentFromTo(tableNameBase, tableNameName) if err != nil { return err } + return nil } -func (d *datastore) Close() error { +func (d *Datastore) Close() error { return d.db.close() } -func (d *datastore) SetValue(key string, value string, encrypted *bool, envs []*types.Environment) error { +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 { + tableName := envNameToTableName(env.Name) + + if err := d.db.setVar(tableName, 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) +func (d *Datastore) GetAll(envName string) ([]*types.EnvVar, error) { + tableName := envNameToTableName(envName) + + vars, err := d.db.getAll(tableName) if err != nil { return vars, err } + g := new(errgroup.Group) + for _, v := range vars { g.Go(func() error { if v.Encrypted { @@ -103,42 +117,50 @@ func (d *datastore) GetAll(envName string) ([]*types.EnvVar, error) { 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) { +func (d *Datastore) RemoveVar(key string, envs []*types.Environment) { for _, env := range envs { - table_name := envNameToTableName(env.Name) - d.db.rmVar(table_name, key) + tableName := envNameToTableName(env.Name) + d.db.rmVar(tableName, key) } } -func (d *datastore) GetVar(envName string, key string) (*types.EnvVar, error) { - table_name := envNameToTableName(envName) - v, err := d.db.getVar(table_name, key) +func (d *Datastore) GetVar(envName string, key string) (*types.EnvVar, error) { + tableName := envNameToTableName(envName) + + v, err := d.db.getVar(tableName, 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 (d *Datastore) RemoveEnv(envName string) error { + tableName := envNameToTableName(envName) + + return d.db.deleteTable(tableName) } func enc(s string) (*string, error) { @@ -146,6 +168,7 @@ func enc(s string) (*string, error) { proj, _ := conf.GetProject(conf.PWD) key, salt, err := deriveKey([]byte(proj.Password), nil) data := []byte(s) + if err != nil { return nil, err } @@ -179,6 +202,7 @@ func dec(s string) (string, error) { conf := config.GetConfig() proj, _ := conf.GetProject(conf.PWD) + key, _, err := deriveKey([]byte(proj.Password), salt) if err != nil { return "", err @@ -207,14 +231,19 @@ func dec(s string) (string, error) { } func deriveKey(password, salt []byte) ([]byte, []byte, error) { + saltSize := 32 if salt == nil { - salt = make([]byte, 32) + salt = make([]byte, saltSize) if _, err := rand.Read(salt); err != nil { return nil, nil, err } } - key, err := scrypt.Key(password, salt, 1048576, 8, 1, 32) + N := 1048576 + r := 8 + p := 1 + + key, err := scrypt.Key(password, salt, N, r, p, saltSize) if err != nil { return nil, nil, err } diff --git a/internal/datastore/db.go b/internal/datastore/db.go index 2ee2abe3..dc8e58e5 100644 --- a/internal/datastore/db.go +++ b/internal/datastore/db.go @@ -1,9 +1,8 @@ package datastore import ( - "fmt" - "database/sql" + "fmt" "git.dayanhub.com/sagi/envoid/internal/common" "git.dayanhub.com/sagi/envoid/internal/errors" @@ -20,6 +19,7 @@ func newDB() (*db, error) { con, err := sql.Open("sqlite", variables.DBFileName) if err != nil { fmt.Printf("%v\n", err) + return nil, err } @@ -32,72 +32,91 @@ 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)) +func (d *db) createTableIfNotExists(tableName string) error { + q := "CREATE TABLE IF NOT EXISTS " + tableName + + " (key TEXT PRIMARY KEY NOT NULL,value BLOB NOT NULL,encrypted BOOL NOT NULL);" + + _, err := d.con.Exec(q) 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 { + + rows, err := d.con.Query(q, variables.DBTablePrefix+"_%%") + if err != nil || rows.Err() != nil { return tables, err } + defer rows.Close() + 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)) +func (d *db) copyContentFromTo(tableNameTarget string, tableNameDest string) error { + _, err := d.con.Exec( + fmt.Sprintf("INSERT INTO %s (key, value, encrypted) SELECT * FROM %s", + tableNameDest, tableNameTarget), + ) 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)) +func (d *db) deleteTable(tableName string) error { + _, err := d.con.Exec("DROP TABLE IF EXISTS " + tableName) + return err } -func (d *db) getVar(table_name string, key string) (*types.EnvVar, error) { - q := fmt.Sprintf("SELECT * FROM %s WHERE key=?", table_name) +func (d *db) getVar(tableName string, key string) (*types.EnvVar, error) { + q := "SELECT * FROM " + tableName + " WHERE key=?" 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) { +func (d *db) getAll(tableName string) ([]*types.EnvVar, error) { entries := []*types.EnvVar{} - rows, err := d.con.Query(fmt.Sprintf("SELECT * FROM %s", table_name)) - if err != nil { + + rows, err := d.con.Query("SELECT * FROM " + tableName) + if err != nil || rows.Err() != nil { return entries, err } + defer rows.Close() 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, @@ -105,27 +124,33 @@ func (d *db) getAll(table_name string) ([]*types.EnvVar, error) { } entries = append(entries, e) } - return entries, nil + 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) +func (d *db) setVar(tableName string, key string, value string, encrypted bool) error { + q := "INSERT INTO " + + tableName + + " (key,value,encrypted) values (?,?,?) ON CONFLICT (key) DO UPDATE SET value=?, encrypted=?" + _, 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) +func (d *db) rmVar(tableName string, key string) { + q := "DELETE FROM " + tableName + " WHERE key = ?" _, _ = d.con.Exec(q, key) } func envNameToTableName(envName string) string { envSanke := common.StrToSnakeCase(envName) + return fmt.Sprintf("%s_%s", variables.DBTablePrefix, envSanke) } diff --git a/internal/errors/env_already_exists.go b/internal/errors/env_already_exists.go index 7315a18b..6e21e84a 100644 --- a/internal/errors/env_already_exists.go +++ b/internal/errors/env_already_exists.go @@ -1,13 +1,11 @@ package errors -import "fmt" - type EnvironmentExistsError struct { name string } func (e *EnvironmentExistsError) Error() string { - return fmt.Sprintf("environment %s already exists", e.name) + return "environment " + e.name + " already exists" } func NewEnvironmentExistsError(name string) *EnvironmentExistsError { diff --git a/internal/errors/invalid_command.go b/internal/errors/invalid_command.go index 40688221..9a890ee4 100644 --- a/internal/errors/invalid_command.go +++ b/internal/errors/invalid_command.go @@ -1,13 +1,11 @@ package errors -import "fmt" - type InvalidCommandError struct { msg string } func (e *InvalidCommandError) Error() string { - return fmt.Sprintf("invalid command. %v", e.msg) + return "invalid command. " + e.msg } func NewInvalidCommandError(msg string) *InvalidCommandError { diff --git a/internal/errors/invalid_password.go b/internal/errors/invalid_password.go index cd713a71..d53cfbf6 100644 --- a/internal/errors/invalid_password.go +++ b/internal/errors/invalid_password.go @@ -1,7 +1,6 @@ package errors -type InvalidPasswordError struct { -} +type InvalidPasswordError struct{} func (e *InvalidPasswordError) Error() string { return "invalid password. is your environment set correctly?" diff --git a/internal/errors/key_not_found.go b/internal/errors/key_not_found.go index c4f71357..14a30f6d 100644 --- a/internal/errors/key_not_found.go +++ b/internal/errors/key_not_found.go @@ -1,13 +1,11 @@ package errors -import "fmt" - type NoKeyFoundError struct { key string } func (e *NoKeyFoundError) Error() string { - return fmt.Sprintf("key %s not found", e.key) + return "key " + e.key + " not found" } func NewNoKeyFoundError(key string) *NoKeyFoundError { diff --git a/internal/errors/no_config_found.go b/internal/errors/no_config_found.go index 71aee7ce..e0e37185 100644 --- a/internal/errors/no_config_found.go +++ b/internal/errors/no_config_found.go @@ -1,13 +1,11 @@ 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) + return "no config file found in " + e.path } func NewNoConfigFoundError(path string) *NoConfigFoundError { diff --git a/internal/errors/no_env_provided.go b/internal/errors/no_env_provided.go new file mode 100644 index 00000000..72f7c318 --- /dev/null +++ b/internal/errors/no_env_provided.go @@ -0,0 +1,11 @@ +package errors + +type NoEnvProvidedError struct{} + +func (e *NoEnvProvidedError) Error() string { + return "You have more than 1 environment. please provide environment name" +} + +func NewNoEnvProvidedError() *NoEnvProvidedError { + return &NoEnvProvidedError{} +} diff --git a/internal/errors/project_is_empty.go b/internal/errors/project_is_empty.go index 6a0e006d..9d9d9406 100644 --- a/internal/errors/project_is_empty.go +++ b/internal/errors/project_is_empty.go @@ -1,13 +1,11 @@ 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) + return "Project " + e.name + " does not have any environments." } func NewProjectEmptyError(name string) *ProjectEmptyError { diff --git a/internal/prompt/password.go b/internal/prompt/password.go index 99d0f791..8c8d3729 100644 --- a/internal/prompt/password.go +++ b/internal/prompt/password.go @@ -9,15 +9,20 @@ import ( ) func PasswordPrompt(label string) string { - var s string + var str string + for { fmt.Fprint(os.Stderr, label+" ") - b, _ := term.ReadPassword(int(syscall.Stdin)) - s = string(b) - if s != "" { + + b, _ := term.ReadPassword(syscall.Stdin) + + str = string(b) + if str != "" { break } } + fmt.Println() - return s + + return str } diff --git a/internal/prompt/string.go b/internal/prompt/string.go index b7b063b9..7cd50650 100644 --- a/internal/prompt/string.go +++ b/internal/prompt/string.go @@ -8,14 +8,18 @@ import ( ) func StringPrompt(label string) string { - var s string + var str string + r := bufio.NewReader(os.Stdin) + for { fmt.Fprint(os.Stderr, label+" ") - s, _ = r.ReadString('\n') - if s != "" { + + str, _ = r.ReadString('\n') + if str != "" { break } } - return strings.TrimSpace(s) + + return strings.TrimSpace(str) } diff --git a/internal/types/project.go b/internal/types/project.go index 6902cd10..b326f381 100644 --- a/internal/types/project.go +++ b/internal/types/project.go @@ -12,11 +12,12 @@ type Project struct { Path string `json:"path"` Name string `json:"name"` Password string `json:"password"` - Environments []*Environment `json:"envorinments" default:"[]"` + Environments []*Environment `default:"[]" json:"envorinments"` } 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) @@ -24,32 +25,39 @@ func (p *Project) GetEnv(name string) (*Environment, error) { 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 } diff --git a/internal/variables/variables.go b/internal/variables/variables.go index b692a64f..399b78f4 100644 --- a/internal/variables/variables.go +++ b/internal/variables/variables.go @@ -1,6 +1,6 @@ package variables var ( - Commit string = "HEAD" - Version string = "development" + Commit = "HEAD" + Version = "development" )