Skip to content

Commit

Permalink
implement 'unset' subcommand (#162)
Browse files Browse the repository at this point in the history
* implement 'unset' subcommand

* code review fix: 'unset' option ignored when reading from envstore file

* code review fix: use explicitly declared default value
  • Loading branch information
lszucs authored Feb 8, 2019
1 parent a9fc969 commit 5f433da
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 0 deletions.
149 changes: 149 additions & 0 deletions _tests/integration/unset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package integration

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/bitrise-io/go-utils/command"
"github.com/bitrise-io/go-utils/pathutil"
"github.com/stretchr/testify/require"
)

func unsetCommand(key, envstore string) *command.Model {
return command.New(binPath(), "-p", envstore, "unset", "--key", key)
}

func runCommand(cmd, envstore string) *command.Model {
return command.New(binPath(), "-p", envstore, "run", cmd)
}

func TestUnset(t *testing.T) {
t.Log("only unset on an empty envstore")
{
// create a fully empty envstore
tmpDir, err := pathutil.NormalizedOSTempDirPath("__envman__")
require.NoError(t, err)

envstore := filepath.Join(tmpDir, ".envstore")
f, err := os.Create(envstore)
require.NoError(t, err)
require.NoError(t, f.Close())

randomEnvKEY := "DONOTEXPORT"

// unset DONOTEXPORT env
out, err := unsetCommand(randomEnvKEY, envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// run env command through envman and see the exported env's list
out, err = runCommand("env", envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// check if the env is surely not exported
if strings.Contains(out, randomEnvKEY) {
t.Errorf("env is exported however it should be unset, complete list of exported envs:\n%s\n", out)
}
}

t.Log("add env then unset it in an empty envstore")
{
// create a fully empty envstore
tmpDir, err := pathutil.NormalizedOSTempDirPath("__envman__")
require.NoError(t, err)

envstore := filepath.Join(tmpDir, ".envstore")
f, err := os.Create(envstore)
require.NoError(t, err)
require.NoError(t, f.Close())

randomEnvKEY := "DONOTEXPORT"

// add DONOTEXPORT env
out, err := addCommand(randomEnvKEY, "sample value", envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// unset DONOTEXPORT env
out, err = unsetCommand(randomEnvKEY, envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// run env command through envman and see the exported env's list
out, err = runCommand("env", envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// check if the env is surely not exported
if strings.Contains(out, randomEnvKEY) {
t.Errorf("env is exported however it should be unset, complete list of exported envs:\n%s\n", out)
}
}

t.Log("set env externally then only unset on an empty envstore")
{
// create a fully empty envstore
tmpDir, err := pathutil.NormalizedOSTempDirPath("__envman__")
require.NoError(t, err)

envstore := filepath.Join(tmpDir, ".envstore")
f, err := os.Create(envstore)
require.NoError(t, err)
require.NoError(t, f.Close())

randomEnvKEY := "DONOTEXPORT"

require.NoError(t, os.Setenv(randomEnvKEY, "value"))

// unset DONOTEXPORT env
out, err := unsetCommand(randomEnvKEY, envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// run env command through envman and see the exported env's list
out, err = runCommand("env", envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// check if the env is surely not exported
if strings.Contains(out, randomEnvKEY) {
t.Errorf("env is exported however it should be unset, complete list of exported envs:\n%s\n", out)
}
}

t.Log("set env externally then add env then unset it in an empty envstore")
{
// create a fully empty envstore
tmpDir, err := pathutil.NormalizedOSTempDirPath("__envman__")
require.NoError(t, err)

envstore := filepath.Join(tmpDir, ".envstore")
f, err := os.Create(envstore)
require.NoError(t, err)
require.NoError(t, f.Close())

controlEnvKey := "EXPORT_THIS"
randomEnvKEY := "DONOTEXPORT"

require.NoError(t, os.Setenv(controlEnvKey, "value"))
require.NoError(t, os.Setenv(randomEnvKEY, "value"))

// add DONOTEXPORT env
out, err := addCommand(randomEnvKEY, "sample value", envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// unset DONOTEXPORT env
out, err = unsetCommand(randomEnvKEY, envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// run env command through envman and see the exported env's list
out, err = runCommand("env", envstore).RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

// check if the env is surely not exported
if !strings.Contains(out, controlEnvKey) {
t.Errorf("env %s is not exported, complete list of exported envs:\n%s\n", controlEnvKey, out)
}

// check if the env is surely not exported
if strings.Contains(out, randomEnvKEY) {
t.Errorf("env is exported however it should be unset, complete list of exported envs:\n%s\n", out)
}
}
}
9 changes: 9 additions & 0 deletions cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,14 @@ var (
SkipFlagParsing: true,
Action: run,
},
{
Name: "unset",
Aliases: []string{"rm"},
Usage: "Enlist an environment variable to be unset (for example to clear OS inherited vars for the process).",
Action: unset,
Flags: []cli.Flag{
flKey,
},
},
}
)
8 changes: 8 additions & 0 deletions cli/run.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"fmt"
"os"

log "github.com/Sirupsen/logrus"
Expand Down Expand Up @@ -33,6 +34,13 @@ func commandEnvs(envs []models.EnvironmentItemModel) ([]string, error) {
return []string{}, err
}

if opts.Unset != nil && *opts.Unset {
if err := os.Unsetenv(key); err != nil {
return []string{}, fmt.Errorf("unset env (%s): %s", key, err)
}
continue
}

if *opts.SkipIfEmpty && value == "" {
continue
}
Expand Down
31 changes: 31 additions & 0 deletions cli/run_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"fmt"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -234,4 +235,34 @@ func TestCommandEnvs(t *testing.T) {
}
require.Equal(t, true, env3Found)
}

t.Log("unset OS envs test")
{
// given
key := "TEST_ENV"
val := "test"
if err := os.Setenv(key, val); err != nil {
require.Equal(t, nil, err, "test setup: error seting env (%s=%s)", key, val)
}
env := models.EnvironmentItemModel{
key: val,
models.OptionsKey: models.EnvironmentItemOptionsModel{
Unset: pointers.NewBoolPtr(true),
},
}
require.Equal(t, nil, env.FillMissingDefaults())
testEnvs := []models.EnvironmentItemModel{
env,
}

// when
envs, err := commandEnvs(testEnvs)
envFmt := "%s=%s" // note: if this format mismatches elements of `envs`, test can be a false positive!
unset := fmt.Sprintf(envFmt, key, val)

// then
require.Equal(t, nil, err)
require.NotContains(t, envs, unset, "failed to unset env (%s)", key)

}
}
36 changes: 36 additions & 0 deletions cli/unset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cli

import (
"github.com/bitrise-io/envman/envman"
"github.com/bitrise-io/envman/models"
"github.com/bitrise-io/go-utils/pointers"
"github.com/urfave/cli"
)

func unset(c *cli.Context) error {
key := c.String(KeyKey)
// Load envs, or create if not exist
environments, err := envman.ReadEnvsOrCreateEmptyList()
if err != nil {
return err
}

// Add or update envlist
newEnv := models.EnvironmentItemModel{
key: "",
models.OptionsKey: models.EnvironmentItemOptionsModel{
Unset: pointers.NewBoolPtr(true),
},
}

if err := newEnv.NormalizeValidateFillDefaults(); err != nil {
return err
}

newEnvSlice, err := envman.UpdateOrAddToEnvlist(environments, newEnv, true)
if err != nil {
return err
}

return envman.WriteEnvMapToFile(envman.CurrentEnvStoreFilePath, newEnvSlice)
}
6 changes: 6 additions & 0 deletions envman/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ func removeDefaults(env *models.EnvironmentItemModel) error {
if opts.SkipIfEmpty != nil && *opts.SkipIfEmpty == models.DefaultSkipIfEmpty {
opts.SkipIfEmpty = nil
}
if opts.Unset != nil && *opts.Unset == models.DefaultUnset {
opts.Unset = nil
}

(*env)[models.OptionsKey] = opts
return nil
Expand Down Expand Up @@ -168,6 +171,9 @@ func generateFormattedYMLForEnvModels(envs []models.EnvironmentItemModel) (model
if opts.SkipIfEmpty != nil {
hasOptions = true
}
if opts.Unset != nil {
hasOptions = true
}

if !hasOptions {
delete(env, models.OptionsKey)
Expand Down
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type EnvironmentItemOptionsModel struct {
IsDontChangeValue *bool `json:"is_dont_change_value,omitempty" yaml:"is_dont_change_value,omitempty"`
IsTemplate *bool `json:"is_template,omitempty" yaml:"is_template,omitempty"`
IsSensitive *bool `json:"is_sensitive,omitempty" yaml:"is_sensitive,omitempty"`
Unset *bool `json:"unset,omitempty" yaml:"unset,omitempty"`
//
Meta map[string]interface{} `json:"meta,omitempty" yaml:"meta,omitempty"`
}
Expand Down
12 changes: 12 additions & 0 deletions models/models_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const (
DefaultIsDontChangeValue = false
// DefaultIsTemplate ...
DefaultIsTemplate = false
// DefaultUnset ...
DefaultUnset = false
)

// NewEnvJSONList ...
Expand Down Expand Up @@ -169,6 +171,12 @@ func (envSerModel *EnvironmentItemOptionsModel) ParseFromInterfaceMap(input map[
return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr)
}
envSerModel.SkipIfEmpty = castedBoolPtr
case "unset":
castedBoolPtr, ok := parseutil.CastToBoolPtr(value)
if !ok {
return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr)
}
envSerModel.Unset = castedBoolPtr
case "meta":
castedMapStringInterface, ok := parseutil.CastToMapStringInterface(value)
if !ok {
Expand Down Expand Up @@ -294,6 +302,10 @@ func (env *EnvironmentItemModel) FillMissingDefaults() error {
if options.Meta == nil {
options.Meta = map[string]interface{}{}
}
if options.Unset == nil {
options.Unset = pointers.NewBoolPtr(DefaultUnset)
}

(*env)[OptionsKey] = options
return nil
}
Expand Down

0 comments on commit 5f433da

Please sign in to comment.