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 ( ...@@ -86,6 +86,8 @@ const (
OptionSkipGc = "skip-gc" OptionSkipGc = "skip-gc"
// OptionSpecFlag is specFlag option. Used for setting k8s spec. // OptionSpecFlag is specFlag option. Used for setting k8s spec.
OptionSpecFlag = "spec-flag" OptionSpecFlag = "spec-flag"
// OptionUnset is unset option.
OptionUnset = "unset"
// OptionURI is uri option. Used for setting registry URI. // OptionURI is uri option. Used for setting registry URI.
OptionURI = "URI" OptionURI = "URI"
// OptionValue is value option. // OptionValue is value option.
...@@ -97,8 +99,8 @@ const ( ...@@ -97,8 +99,8 @@ const (
const ( const (
// OutputWide is wide output // OutputWide is wide output
OutputWide = "wide" OutputWide = "wide"
// EnvListOutputJSON is JSON output // OutputJSON is JSON output
EnvListOutputJSON = "json" OutputJSON = "json"
) )
var ( var (
......
...@@ -59,7 +59,6 @@ func newApply(m map[string]interface{}, opts ...applyOpt) (*Apply, error) { ...@@ -59,7 +59,6 @@ func newApply(m map[string]interface{}, opts ...applyOpt) (*Apply, error) {
componentNames: ol.loadStringSlice(OptionComponentNames), componentNames: ol.loadStringSlice(OptionComponentNames),
create: ol.loadBool(OptionCreate), create: ol.loadBool(OptionCreate),
dryRun: ol.loadBool(OptionDryRun), dryRun: ol.loadBool(OptionDryRun),
envName: ol.loadString(OptionEnvName),
gcTag: ol.loadString(OptionGcTag), gcTag: ol.loadString(OptionGcTag),
skipGc: ol.loadBool(OptionSkipGc), skipGc: ol.loadBool(OptionSkipGc),
...@@ -74,6 +73,10 @@ func newApply(m map[string]interface{}, opts ...applyOpt) (*Apply, error) { ...@@ -74,6 +73,10 @@ func newApply(m map[string]interface{}, opts ...applyOpt) (*Apply, error) {
opt(a) opt(a)
} }
if err := setCurrentEnv(a.app, a, ol); err != nil {
return nil, err
}
return a, nil return a, nil
} }
...@@ -91,3 +94,7 @@ func (a *Apply) run() error { ...@@ -91,3 +94,7 @@ func (a *Apply) run() error {
return a.runApplyFn(config) return a.runApplyFn(config)
} }
func (a *Apply) setCurrentEnv(name string) {
a.envName = name
}
...@@ -26,42 +26,72 @@ import ( ...@@ -26,42 +26,72 @@ import (
) )
func TestApply(t *testing.T) { func TestApply(t *testing.T) {
withApp(t, func(appMock *amocks.App) { cases := []struct {
in := map[string]interface{}{ name string
OptionApp: appMock, isSetupErr bool
OptionClientConfig: &client.Config{}, currentName string
OptionComponentNames: []string{}, envName string
OptionCreate: true, }{
OptionDryRun: true, {
OptionEnvName: "default", name: "with a supplied env",
OptionGcTag: "gc-tag", envName: "default",
OptionSkipGc: true, },
} {
name: "with a current env",
currentName: "default",
},
{
name: "without supplied or current env",
isSetupErr: true,
},
}
expected := cluster.ApplyConfig{ for _, tc := range cases {
App: appMock, t.Run(tc.name, func(t *testing.T) {
ClientConfig: &client.Config{}, withApp(t, func(appMock *amocks.App) {
ComponentNames: []string{}, appMock.On("CurrentEnvironment").Return(tc.currentName)
Create: true,
DryRun: true,
EnvName: "default",
GcTag: "gc-tag",
SkipGc: true,
}
runApplyOpt := func(a *Apply) { in := map[string]interface{}{
a.runApplyFn = func(config cluster.ApplyConfig, opts ...cluster.ApplyOpts) error { OptionApp: appMock,
assert.Equal(t, expected, config) OptionClientConfig: &client.Config{},
return nil OptionComponentNames: []string{},
} OptionCreate: true,
} OptionDryRun: true,
OptionEnvName: tc.envName,
OptionGcTag: "gc-tag",
OptionSkipGc: true,
}
a, err := newApply(in, runApplyOpt) expected := cluster.ApplyConfig{
require.NoError(t, err) App: appMock,
ClientConfig: &client.Config{},
ComponentNames: []string{},
Create: true,
DryRun: true,
EnvName: "default",
GcTag: "gc-tag",
SkipGc: true,
}
err = a.run() runApplyOpt := func(a *Apply) {
require.NoError(t, err) a.runApplyFn = func(config cluster.ApplyConfig, opts ...cluster.ApplyOpts) error {
}) assert.Equal(t, expected, config)
return nil
}
}
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) { 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) { ...@@ -54,7 +54,6 @@ func newDelete(m map[string]interface{}, opts ...deleteOpt) (*Delete, error) {
app: ol.loadApp(), app: ol.loadApp(),
clientConfig: ol.loadClientConfig(), clientConfig: ol.loadClientConfig(),
componentNames: ol.loadStringSlice(OptionComponentNames), componentNames: ol.loadStringSlice(OptionComponentNames),
envName: ol.loadString(OptionEnvName),
gracePeriod: ol.loadInt64(OptionGracePeriod), gracePeriod: ol.loadInt64(OptionGracePeriod),
runDeleteFn: cluster.RunDelete, runDeleteFn: cluster.RunDelete,
...@@ -68,6 +67,10 @@ func newDelete(m map[string]interface{}, opts ...deleteOpt) (*Delete, error) { ...@@ -68,6 +67,10 @@ func newDelete(m map[string]interface{}, opts ...deleteOpt) (*Delete, error) {
opt(d) opt(d)
} }
if err := setCurrentEnv(d.app, d, ol); err != nil {
return nil, err
}
return d, nil return d, nil
} }
...@@ -82,3 +85,7 @@ func (d *Delete) run() error { ...@@ -82,3 +85,7 @@ func (d *Delete) run() error {
return d.runDeleteFn(config) return d.runDeleteFn(config)
} }
func (d *Delete) setCurrentEnv(name string) {
d.envName = name
}
...@@ -26,36 +26,66 @@ import ( ...@@ -26,36 +26,66 @@ import (
) )
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
withApp(t, func(appMock *amocks.App) { cases := []struct {
in := map[string]interface{}{ name string
OptionApp: appMock, isSetupErr bool
OptionClientConfig: &client.Config{}, currentName string
OptionComponentNames: []string{}, envName string
OptionEnvName: "default", }{
OptionGracePeriod: int64(3), {
} name: "with a supplied env",
envName: "default",
},
{
name: "with a current env",
currentName: "default",
},
{
name: "without supplied or current env",
isSetupErr: true,
},
}
expected := cluster.DeleteConfig{ for _, tc := range cases {
App: appMock, t.Run(tc.name, func(t *testing.T) {
ClientConfig: &client.Config{}, withApp(t, func(appMock *amocks.App) {
ComponentNames: []string{}, appMock.On("CurrentEnvironment").Return(tc.currentName)
EnvName: "default",
GracePeriod: 3,
}
runDeleteOpt := func(a *Delete) { in := map[string]interface{}{
a.runDeleteFn = func(config cluster.DeleteConfig, opts ...cluster.DeleteOpts) error { OptionApp: appMock,
assert.Equal(t, expected, config) OptionClientConfig: &client.Config{},
return nil OptionComponentNames: []string{},
} OptionEnvName: tc.envName,
} OptionGracePeriod: int64(3),
}
a, err := newDelete(in, runDeleteOpt) expected := cluster.DeleteConfig{
require.NoError(t, err) App: appMock,
ClientConfig: &client.Config{},
ComponentNames: []string{},
EnvName: "default",
GracePeriod: 3,
}
err = a.run() runDeleteOpt := func(a *Delete) {
require.NoError(t, err) a.runDeleteFn = func(config cluster.DeleteConfig, opts ...cluster.DeleteOpts) error {
}) assert.Equal(t, expected, config)
return nil
}
}
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) { 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 { ...@@ -72,7 +72,7 @@ func (el *EnvList) Run() error {
return errors.Errorf("unknown output format %q", el.outputType) return errors.Errorf("unknown output format %q", el.outputType)
case OutputWide: case OutputWide:
return el.outputWide() return el.outputWide()
case EnvListOutputJSON: case OutputJSON:
return el.outputJSON() return el.outputJSON()
} }
} }
......
...@@ -59,7 +59,7 @@ func TestEnvList(t *testing.T) { ...@@ -59,7 +59,7 @@ func TestEnvList(t *testing.T) {
}, },
{ {
name: "json output", name: "json output",
outputType: EnvListOutputJSON, outputType: OutputJSON,
expectedFile: filepath.Join("env", "list", "output.json"), expectedFile: filepath.Join("env", "list", "output.json"),
}, },
{ {
......
...@@ -57,7 +57,6 @@ func newShow(m map[string]interface{}, opts ...showOpt) (*Show, error) { ...@@ -57,7 +57,6 @@ func newShow(m map[string]interface{}, opts ...showOpt) (*Show, error) {
s := &Show{ s := &Show{
app: ol.loadApp(), app: ol.loadApp(),
componentNames: ol.loadStringSlice(OptionComponentNames), componentNames: ol.loadStringSlice(OptionComponentNames),
envName: ol.loadString(OptionEnvName),
format: ol.loadString(OptionFormat), format: ol.loadString(OptionFormat),
out: os.Stdout, out: os.Stdout,
...@@ -72,6 +71,10 @@ func newShow(m map[string]interface{}, opts ...showOpt) (*Show, error) { ...@@ -72,6 +71,10 @@ func newShow(m map[string]interface{}, opts ...showOpt) (*Show, error) {
opt(s) opt(s)
} }
if err := setCurrentEnv(s.app, s, ol); err != nil {
return nil, err
}
return s, nil return s, nil
} }
...@@ -86,3 +89,7 @@ func (s *Show) run() error { ...@@ -86,3 +89,7 @@ func (s *Show) run() error {
return s.runShowFn(config) return s.runShowFn(config)
} }
func (s *Show) setCurrentEnv(name string) {
s.envName = name
}
...@@ -26,35 +26,65 @@ import ( ...@@ -26,35 +26,65 @@ import (
) )
func TestShow(t *testing.T) { func TestShow(t *testing.T) {
withApp(t, func(appMock *amocks.App) { cases := []struct {
in := map[string]interface{}{ name string
OptionApp: appMock, isSetupErr bool
OptionComponentNames: []string{}, currentName string
OptionEnvName: "default", envName string
OptionFormat: "yaml", }{
} {
name: "with a supplied env",
envName: "default",
},
{
name: "with a current env",
currentName: "default",