Compare commits

...

4 commits

Author SHA1 Message Date
29e31c5b21
Added lint to the CI. formatted all code accordingly
All checks were successful
CI / check for spelling errors (pull_request) Successful in 21s
CI / code quality (lint/tests) (pull_request) Successful in 2m6s
CI / make sure build does not fail (pull_request) Successful in 2m22s
CI / notify-fail (pull_request) Has been skipped
CI / check for spelling errors (push) Successful in 21s
CI / code quality (lint/tests) (push) Successful in 2m4s
CI / make sure build does not fail (push) Successful in 2m24s
CI / notify-fail (push) Has been skipped
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
2024-12-17 13:10:42 +02:00
7ae4c4869e
CI changes: merged some flows into one with multiple jobs
All checks were successful
ci/ci / Check for spelling errors (pull_request) Successful in 21s
ci/ci / Make sure build does not fail (pull_request) Successful in 2m48s
ci/ci / notify-fail (pull_request) Has been skipped
ci/ci / Check for spelling errors (push) Successful in 22s
ci/ci / Make sure build does not fail (push) Successful in 2m51s
ci/ci / notify-fail (push) Has been skipped
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
2024-12-16 18:07:31 +02:00
a16af482de Update module golang.org/x/sync to v0.10.0
All checks were successful
build bin / Make sure build does not fail (pull_request) Successful in 2m54s
Codespell / Check for spelling errors (pull_request) Successful in 22s
build bin / Make sure build does not fail (push) Successful in 2m55s
Codespell / Check for spelling errors (push) Successful in 21s
2024-12-15 16:18:39 +00:00
fd263d8280 Update module golang.org/x/crypto to v0.31.0
All checks were successful
build bin / Make sure build does not fail (pull_request) Successful in 2m52s
Codespell / Check for spelling errors (pull_request) Successful in 22s
build bin / Make sure build does not fail (push) Successful in 3m21s
Codespell / Check for spelling errors (push) Successful in 22s
2024-12-15 15:59:57 +00:00
51 changed files with 396 additions and 204 deletions

View file

@ -1,31 +0,0 @@
name: build bin
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
build:
name: Make sure build does not fail
runs-on: ubuntu-latest
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
name: checkout
- name: install alsa devel
run: apt update && apt install libasound2-dev -y
- uses: https://code.forgejo.org/actions/setup-go@v5
name: install go
with:
go-version-file: './go.mod'
- run: go version
name: Go version
- run: make dep
name: install dependencies
- run: make build
name: build app

93
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,93 @@
name: CI
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: ./.git
codequality:
name: code quality (lint/tests)
runs-on: ubuntu-latest
needs:
- codespell
if: ${{ success() }}
steps:
- name: checkout
uses: actions/checkout@v4
- uses: https://code.forgejo.org/actions/setup-go@v5
name: install go
with:
go-version-file: './go.mod'
- name: install dependencies
run: make dep
- name: download golangci-lint
run: wget https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh
- name: install golangci-lint
run: sh ./install.sh v1.62.2
- name: lint
run: ./bin/golangci-lint run
compile:
name: make sure build does not fail
runs-on: ubuntu-latest
needs:
- codespell
- codequality
if: ${{ success() }}
steps:
- uses: actions/checkout@v4
name: checkout
- name: install alsa devel
run: apt update && apt install libasound2-dev -y
- uses: https://code.forgejo.org/actions/setup-go@v5
name: install go
with:
go-version-file: './go.mod'
- run: go version
name: Go version
- run: make dep
name: install dependencies
- run: make build
name: build app
notify-fail:
name: notify-fail
runs-on: ubuntu-latest
needs:
- codespell
- codequality
- compile
if: ${{ failure() }}
env:
SERVER_URL: ${{ secrets.GOTIFY_SERVER_URL }}
TOKEN: ${{ secrets.GOTIFY_TOKEN }}
steps:
- name: gotify
run: |-
curl "${SERVER_URL}?token=${TOKEN}" \
-F "title='CI failed'" \
-F "message='Something failed at ${GITHUB_REPOSITORY}/${GITHUB_REF} for user ${GITHUB_ACTOR}'" \
-F "priority=7" &> /dev/null

View file

@ -1,27 +0,0 @@
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: ./.git

12
.golangci.yml Normal file
View file

@ -0,0 +1,12 @@
linters:
enable-all: true
disable:
- wrapcheck
- exhaustruct
- varnamelen
- gochecknoglobals
- depguard
- gochecknoinits
- forbidigo
- revive
- gosec

View file

@ -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:

View file

@ -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
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)

View file

@ -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)

View file

@ -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)

View file

@ -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()
@ -71,6 +72,7 @@ var initCmd = &cobra.Command{
}
configuration.Save()
fmt.Println("✅ Done")
return nil
},
}

View file

@ -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)

View file

@ -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)
}

View file

@ -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)

View file

@ -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
}

View file

@ -21,13 +21,15 @@ 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 {
Args: func(_ *cobra.Command, args []string) error {
expectedArgs := 2
if len(args) != expectedArgs {
return errors.NewInvalidCommandError("expected 2 args. <key> <value>")
}
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 <key>",
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. <key>")
}
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)
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -17,4 +17,4 @@ envoid get <key> [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

View file

@ -21,4 +21,4 @@ envoid import <file> [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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -17,4 +17,4 @@ envoid rm <key> [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

View file

@ -19,4 +19,4 @@ envoid set [flags] <key> <value>
* [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

View file

@ -22,4 +22,4 @@ envoid set encrypt <key> [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

8
go.mod
View file

@ -7,9 +7,9 @@ require (
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
golang.org/x/crypto v0.31.0
golang.org/x/sync v0.10.0
golang.org/x/term v0.27.0
)
require (
@ -21,7 +21,7 @@ require (
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
golang.org/x/sys v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect

16
go.sum
View file

@ -24,15 +24,15 @@ 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/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.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/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
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.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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -6,5 +6,6 @@ func StrToSnakeCase(s string) string {
snake := strings.ToLower(s)
snake = strings.ReplaceAll(snake, " ", "_")
snake = strings.ReplaceAll(snake, "\t", "_")
return snake
}

View file

@ -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)

View file

@ -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
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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?"

View file

@ -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 {

View file

@ -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 {

View file

@ -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{}
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -1,6 +1,6 @@
package variables
var (
Commit string = "HEAD"
Version string = "development"
Commit = "HEAD"
Version = "development"
)