Unverified Commit d0b9028b authored by bryanl's avatar bryanl
Browse files

Add `ks env current` command



This command allow you to set the current environment. It also allows
you to retrieve the current environment. Feature added to support vscode
extension.
Signed-off-by: default avatarbryanl <bryanliles@gmail.com>
parent e13aee65
......@@ -86,6 +86,8 @@ const (
OptionSkipGc = "skip-gc"
// OptionSpecFlag is specFlag option. Used for setting k8s spec.
OptionSpecFlag = "spec-flag"
// OptionUnset is unset option.
OptionUnset = "unset"
// OptionURI is uri option. Used for setting registry URI.
OptionURI = "URI"
// OptionValue is value option.
......@@ -97,8 +99,8 @@ const (
const (
// OutputWide is wide output
OutputWide = "wide"
// EnvListOutputJSON is JSON output
EnvListOutputJSON = "json"
// OutputJSON is JSON output
OutputJSON = "json"
)
var (
......
......@@ -59,7 +59,6 @@ func newApply(m map[string]interface{}, opts ...applyOpt) (*Apply, error) {
componentNames: ol.loadStringSlice(OptionComponentNames),
create: ol.loadBool(OptionCreate),
dryRun: ol.loadBool(OptionDryRun),
envName: ol.loadString(OptionEnvName),
gcTag: ol.loadString(OptionGcTag),
skipGc: ol.loadBool(OptionSkipGc),
......@@ -74,6 +73,10 @@ func newApply(m map[string]interface{}, opts ...applyOpt) (*Apply, error) {
opt(a)
}
if err := setCurrentEnv(a.app, a, ol); err != nil {
return nil, err
}
return a, nil
}
......@@ -91,3 +94,7 @@ func (a *Apply) run() error {
return a.runApplyFn(config)
}
func (a *Apply) setCurrentEnv(name string) {
a.envName = name
}
......@@ -26,14 +26,38 @@ import (
)
func TestApply(t *testing.T) {
cases := []struct {
name string
isSetupErr bool
currentName string
envName string
}{
{
name: "with a supplied env",
envName: "default",
},
{
name: "with a current env",
currentName: "default",
},
{
name: "without supplied or current env",
isSetupErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
appMock.On("CurrentEnvironment").Return(tc.currentName)
in := map[string]interface{}{
OptionApp: appMock,
OptionClientConfig: &client.Config{},
OptionComponentNames: []string{},
OptionCreate: true,
OptionDryRun: true,
OptionEnvName: "default",
OptionEnvName: tc.envName,
OptionGcTag: "gc-tag",
OptionSkipGc: true,
}
......@@ -57,11 +81,17 @@ func TestApply(t *testing.T) {
}
a, err := newApply(in, runApplyOpt)
if tc.isSetupErr {
require.Error(t, err)
return
}
require.NoError(t, err)
err = a.run()
require.NoError(t, err)
})
})
}
}
func TestApply_invalid_input(t *testing.T) {
......
// Copyright 2018 The ksonnet authors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import "github.com/pkg/errors"
type environmentMetadata interface {
CurrentEnvironment() string
}
type currentEnver interface {
setCurrentEnv(name string)
}
func setCurrentEnv(em environmentMetadata, ce currentEnver, ol *optionLoader) error {
envName := ol.loadOptionalString(OptionEnvName)
if envName == "" {
envName = em.CurrentEnvironment()
}
if envName == "" {
return errors.Errorf("environment is not set; use `env list` to see available environments")
}
ce.setCurrentEnv(envName)
return nil
}
......@@ -54,7 +54,6 @@ func newDelete(m map[string]interface{}, opts ...deleteOpt) (*Delete, error) {
app: ol.loadApp(),
clientConfig: ol.loadClientConfig(),
componentNames: ol.loadStringSlice(OptionComponentNames),
envName: ol.loadString(OptionEnvName),
gracePeriod: ol.loadInt64(OptionGracePeriod),
runDeleteFn: cluster.RunDelete,
......@@ -68,6 +67,10 @@ func newDelete(m map[string]interface{}, opts ...deleteOpt) (*Delete, error) {
opt(d)
}
if err := setCurrentEnv(d.app, d, ol); err != nil {
return nil, err
}
return d, nil
}
......@@ -82,3 +85,7 @@ func (d *Delete) run() error {
return d.runDeleteFn(config)
}
func (d *Delete) setCurrentEnv(name string) {
d.envName = name
}
......@@ -26,12 +26,36 @@ import (
)
func TestDelete(t *testing.T) {
cases := []struct {
name string
isSetupErr bool
currentName string
envName string
}{
{
name: "with a supplied env",
envName: "default",
},
{
name: "with a current env",
currentName: "default",
},
{
name: "without supplied or current env",
isSetupErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
appMock.On("CurrentEnvironment").Return(tc.currentName)
in := map[string]interface{}{
OptionApp: appMock,
OptionClientConfig: &client.Config{},
OptionComponentNames: []string{},
OptionEnvName: "default",
OptionEnvName: tc.envName,
OptionGracePeriod: int64(3),
}
......@@ -51,11 +75,17 @@ func TestDelete(t *testing.T) {
}
a, err := newDelete(in, runDeleteOpt)
if tc.isSetupErr {
require.Error(t, err)
return
}
require.NoError(t, err)
err = a.run()
require.NoError(t, err)
})
})
}
}
func TestDelete_invalid_input(t *testing.T) {
......
// Copyright 2018 The ksonnet authors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"fmt"
"io"
"os"
"github.com/ksonnet/ksonnet/metadata/app"
"github.com/pkg/errors"
)
// RunEnvCurrent runs `env current`.
func RunEnvCurrent(m map[string]interface{}) error {
a, err := newEnvCurrent(m)
if err != nil {
return err
}
return a.run()
}
// EnvCurrent sets/unsets the current environment
type EnvCurrent struct {
app app.App
envName string
unset bool
out io.Writer
}
// RunEnvCurrent runs `env current`
func newEnvCurrent(m map[string]interface{}) (*EnvCurrent, error) {
ol := newOptionLoader(m)
d := &EnvCurrent{
app: ol.loadApp(),
envName: ol.loadOptionalString(OptionEnvName),
unset: ol.loadBool(OptionUnset),
out: os.Stdout,
}
if ol.err != nil {
return nil, ol.err
}
return d, nil
}
func (e *EnvCurrent) run() error {
if e.envName != "" && e.unset == true {
return errors.New("set and unset are exclusive")
}
if e.unset {
return e.app.SetCurrentEnvironment("")
} else if e.envName != "" {
return e.app.SetCurrentEnvironment(e.envName)
} else {
fmt.Fprintln(e.out, e.app.CurrentEnvironment())
return nil
}
}
// Copyright 2018 The ksonnet authors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"bytes"
"testing"
amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
"github.com/stretchr/testify/require"
)
func TestEnvCurrent(t *testing.T) {
cases := []struct {
name string
envName string
currentName string
unset bool
output string
isErr bool
}{
{
name: "show current environment with no current environment",
},
{
name: "show current environment with current environment set",
currentName: "default",
output: "default",
},
{
name: "set current",
envName: "default",
},
{
name: "unset current",
unset: true,
},
{
name: "error if set and unset together",
unset: true,
envName: "default",
isErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
appMock.On("CurrentEnvironment").Return(tc.currentName)
appMock.On("SetCurrentEnvironment", tc.envName).Return(nil)
in := map[string]interface{}{
OptionApp: appMock,
OptionEnvName: tc.envName,
OptionUnset: tc.unset,
}
a, err := newEnvCurrent(in)
require.NoError(t, err)
var buf bytes.Buffer
a.out = &buf
err = a.run()
if tc.isErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
})
}
}
func TestEnvCurrent_invalid_input(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
in := map[string]interface{}{
OptionApp: "invalid",
}
_, err := newEnvCurrent(in)
require.Error(t, err)
})
}
......@@ -72,7 +72,7 @@ func (el *EnvList) Run() error {
return errors.Errorf("unknown output format %q", el.outputType)
case OutputWide:
return el.outputWide()
case EnvListOutputJSON:
case OutputJSON:
return el.outputJSON()
}
}
......
......@@ -59,7 +59,7 @@ func TestEnvList(t *testing.T) {
},
{
name: "json output",
outputType: EnvListOutputJSON,
outputType: OutputJSON,
expectedFile: filepath.Join("env", "list", "output.json"),
},
{
......
......@@ -57,7 +57,6 @@ func newShow(m map[string]interface{}, opts ...showOpt) (*Show, error) {
s := &Show{
app: ol.loadApp(),
componentNames: ol.loadStringSlice(OptionComponentNames),
envName: ol.loadString(OptionEnvName),
format: ol.loadString(OptionFormat),
out: os.Stdout,
......@@ -72,6 +71,10 @@ func newShow(m map[string]interface{}, opts ...showOpt) (*Show, error) {
opt(s)
}
if err := setCurrentEnv(s.app, s, ol); err != nil {
return nil, err
}
return s, nil
}
......@@ -86,3 +89,7 @@ func (s *Show) run() error {
return s.runShowFn(config)
}
func (s *Show) setCurrentEnv(name string) {
s.envName = name
}
......@@ -26,11 +26,35 @@ import (
)
func TestShow(t *testing.T) {
cases := []struct {
name string
isSetupErr bool
currentName string
envName string
}{
{
name: "with a supplied env",
envName: "default",
},
{
name: "with a current env",
currentName: "default",
},
{
name: "without supplied or current env",
isSetupErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
appMock.On("CurrentEnvironment").Return(tc.currentName)
in := map[string]interface{}{
OptionApp: appMock,
OptionComponentNames: []string{},
OptionEnvName: "default",
OptionEnvName: tc.envName,
OptionFormat: "yaml",
}
......@@ -50,11 +74,17 @@ func TestShow(t *testing.T) {
}
a, err := newShow(in, runShowOpt)
if tc.isSetupErr {
require.Error(t, err)
return
}
require.NoError(t, err)
err = a.run()
require.NoError(t, err)
})
})
}
}
func TestShow_invalid_input(t *testing.T) {
......
......@@ -68,7 +68,6 @@ func NewValidate(m map[string]interface{}) (*Validate, error) {
v := &Validate{
app: ol.loadApp(),
envName: ol.loadString(OptionEnvName),
module: ol.loadString(OptionModule),
componentNames: ol.loadStringSlice(OptionComponentNames),
clientConfig: ol.loadClientConfig(),
......@@ -83,6 +82,10 @@ func NewValidate(m map[string]interface{}) (*Validate, error) {
return nil, ol.err
}
if err := setCurrentEnv(v.app, v, ol); err != nil {
return nil, err
}
return v, nil
}
......@@ -136,3 +139,7 @@ func findObjects(a app.App, envName string, componentNames []string) ([]*unstruc
p := pipeline.New(a, envName)
return p.Objects(componentNames)
}
func (v *Validate) setCurrentEnv(name string) {
v.envName = name
}
......@@ -35,28 +35,55 @@ import (
)
func TestValidate(t *testing.T) {
cases := []struct {
name string
isSetupErr bool
currentName string
envName string
}{
{
name: "with a supplied env",
envName: "default",
},
{
name: "with a current env",
currentName: "default",
},
{
name: "without supplied or current env",
isSetupErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
aEnvName := "default"
appMock.On("CurrentEnvironment").Return(tc.currentName)
aComponentNames := make([]string, 0)
aModuleName := "module"
aClientConfig := &client.Config{}
env := &app.EnvironmentSpec{}
appMock.On("Environment", aEnvName).Return(env, nil)
appMock.On("Environment", "default").Return(env, nil)
in := map[string]interface{}{
OptionApp: appMock,
OptionEnvName: aEnvName,
OptionEnvName: tc.envName,
OptionModule: aModuleName,
OptionComponentNames: aComponentNames,
OptionClientConfig: aClientConfig,
}
a, err := NewValidate(in)
if tc.isSetupErr {
require.Error(t, err)
return
}
require.NoError(t, err)
a.discoveryFn = func(a app.App, clientConfig *client.Config, envName string) (discovery.DiscoveryInterface, error) {
assert.Equal(t, aEnvName, envName)
assert.Equal(t, "default", envName)
return &stubDiscovery{}, nil
}
......@@ -77,6 +104,8 @@ func TestValidate(t *testing.T) {
err = a.Run()
require.NoError(t, err)
})
})
}
}
type stubDiscovery struct{}
......
......@@ -29,6 +29,7 @@ const (
actionDelete
actionDiff
actionEnvAdd
actionEnvCurrent
actionEnvDescribe
actionEnvList
actionEnvRm
......@@ -69,6 +70,7 @@ var (
actionDelete: actions.RunDelete,
// actionDiff
actionEnvAdd: actions.RunEnvAdd,
actionEnvCurrent: actions.RunEnvCurrent,
actionEnvDescribe: actions.RunEnvDescribe,
actionEnvList: actions.RunEnvList,
actionEnvRm: actions.RunEnvRm,
......
......@@ -16,8 +16,6 @@
package cmd
import (
"fmt"