Unverified Commit 49a682d5 authored by bryanl's avatar bryanl
Browse files

Allow showing only env set parameters



When listing parameters for an environment, add an option that only
shows parameters set in the environment itself. This will allow ks users
to show only the differences. The new option is `--without-modules`.

example:

```
 $ ks param list --env default
COMPONENT     PARAM   VALUE
=========     =====   =====
nested.redis3 envset  1
redis         envset  1
redis2        envset  1
redis2        name    'redis2'
redis2        values  {}
redis2        version '3.7.3'
$ ks param list --env default --without-modules
COMPONENT     PARAM  VALUE
=========     =====  =====
nested.redis3 envset 1
redis         envset 1
redis2        envset 1
```

Fixes #747
Signed-off-by: default avatarbryanl <bryanliles@gmail.com>
parent 5b4917a2
......@@ -42,10 +42,11 @@ ks param list guestbook --env=dev
### Options
```
--env string Specify environment to list parameters for
-h, --help help for list
--module string Specify module to list parameters for
-o, --output string Output format. Valid options: table|json
--env string Specify environment to list parameters for
-h, --help help for list
--module string Specify module to list parameters for
-o, --output string Output format. Valid options: table|json
--without-modules Exclude module defaults
```
### Options inherited from parent commands
......
......@@ -224,6 +224,27 @@ var _ = Describe("ks param", func() {
Expect(got).To(Equal(expected))
})
})
Describe("at the environment level without modules", func() {
BeforeEach(func() {
a.generateDeployedService()
a.paramSet("guestbook-ui", "replicas", "3", "--env", "default")
listParams = []string{"param", "list", "-o", "json", "--env", "default", "--without-modules"}
})
It("should exit with 0", func() {
assertExitStatus(listOutput, 0)
})
It("lists the params", func() {
tr := loadTableResponse(listOutput.stdout)
got := tr.paramList()
expected := setGuestBookRow([]paramListRow{}, "replicas", "3")
Expect(got).To(Equal(expected))
})
})
})
Describe("set", func() {
......
......@@ -114,6 +114,8 @@ const (
OptionUnset = "unset"
// OptionURI is uri option. Used for setting registry URI.
OptionURI = "URI"
// OptionWithoutModules is without modules option.
OptionWithoutModules = "without-modules"
// OptionValue is value option.
OptionValue = "value"
// OptionVersion is version option.
......
......@@ -49,17 +49,18 @@ type paramsLister interface {
// ParamList lists parameters for a component.
type ParamList struct {
app app.App
moduleName string
componentName string
envName string
outputType string
app app.App
moduleName string
componentName string
envName string
outputType string
withoutModules bool
out io.Writer
findModuleFn findModuleFn
modulesFn func() ([]component.Module, error)
envParametersFn func(string) (string, error)
envParametersFn func(moduleName string, inherited bool) (string, error)
lister paramsLister
}
......@@ -68,11 +69,12 @@ func NewParamList(m map[string]interface{}) (*ParamList, error) {
ol := newOptionLoader(m)
pl := &ParamList{
app: ol.LoadApp(),
moduleName: ol.LoadOptionalString(OptionModule),
componentName: ol.LoadOptionalString(OptionComponentName),
envName: ol.LoadOptionalString(OptionEnvName),
outputType: ol.LoadOptionalString(OptionOutput),
app: ol.LoadApp(),
moduleName: ol.LoadOptionalString(OptionModule),
componentName: ol.LoadOptionalString(OptionComponentName),
envName: ol.LoadOptionalString(OptionEnvName),
outputType: ol.LoadOptionalString(OptionOutput),
withoutModules: ol.LoadOptionalBool(OptionWithoutModules),
out: os.Stdout,
findModuleFn: component.GetModule,
......@@ -142,7 +144,7 @@ func (pl *ParamList) handleEnvParams() error {
var entries []params.Entry
for _, m := range modules {
source, err := pl.envParametersFn(m.Name())
source, err := pl.envParametersFn(m.Name(), !pl.withoutModules)
if err != nil {
return err
}
......
......@@ -53,18 +53,23 @@ func TestParamList(t *testing.T) {
},
}
fakeEnvParametersFn := func(string, bool) (string, error) {
return "{}", nil
}
withApp(t, func(appMock *amocks.App) {
ec := &app.EnvironmentConfig{}
appMock.On("Environment", "envName").Return(ec, nil)
cases := []struct {
name string
in map[string]interface{}
findModuleFn func(t *testing.T) findModuleFn
modulesFn func() ([]component.Module, error)
lister paramsLister
outputFile string
isErr bool
name string
in map[string]interface{}
findModuleFn func(t *testing.T) findModuleFn
envParametersFn func(string, bool) (string, error)
modulesFn func() ([]component.Module, error)
lister paramsLister
outputFile string
isErr bool
}{
{
name: "component name",
......@@ -126,6 +131,24 @@ func TestParamList(t *testing.T) {
lister: fakeLister,
outputFile: filepath.Join("param", "list", "env.txt"),
},
{
name: "env without modules",
in: map[string]interface{}{
OptionApp: appMock,
OptionEnvName: "envName",
OptionWithoutModules: true,
},
modulesFn: func() ([]component.Module, error) {
module.On("Name").Return("/")
return []component.Module{module}, nil
},
lister: fakeLister,
outputFile: filepath.Join("param", "list", "env.txt"),
envParametersFn: func(envName string, inherited bool) (string, error) {
assert.False(t, inherited, "should not request inherited parameters")
return "{}", nil
},
},
{
name: "invalid output type",
in: map[string]interface{}{
......@@ -160,10 +183,13 @@ func TestParamList(t *testing.T) {
a.modulesFn = tc.modulesFn
}
a.envParametersFn = func(string) (string, error) {
return "{}", nil
envParametersFn := tc.envParametersFn
if envParametersFn == nil {
envParametersFn = fakeEnvParametersFn
}
a.envParametersFn = envParametersFn
var buf bytes.Buffer
a.out = &buf
......
......@@ -53,6 +53,7 @@ const (
flagUnset = "unset"
flagVerbose = "verbose"
flagVersion = "version"
flagWithoutModules = "without-modules"
shortComponent = "c"
shortFilename = "f"
......
......@@ -25,7 +25,8 @@ import (
)
const (
vParamListOutput = "param-list-output"
vParamListOutput = "param-list-output"
vParamListWithoutModules = "param-without-modules"
)
var (
......@@ -84,11 +85,12 @@ func newParamListCmd(a app.App) *cobra.Command {
}
m := map[string]interface{}{
actions.OptionApp: a,
actions.OptionComponentName: component,
actions.OptionEnvName: env,
actions.OptionModule: module,
actions.OptionOutput: viper.GetString(vParamListOutput),
actions.OptionApp: a,
actions.OptionComponentName: component,
actions.OptionEnvName: env,
actions.OptionModule: module,
actions.OptionOutput: viper.GetString(vParamListOutput),
actions.OptionWithoutModules: viper.GetBool(vParamListWithoutModules),
}
return runAction(actionParamList, m)
......@@ -99,6 +101,9 @@ func newParamListCmd(a app.App) *cobra.Command {
paramListCmd.PersistentFlags().String(flagEnv, "", "Specify environment to list parameters for")
paramListCmd.Flags().String(flagModule, "", "Specify module to list parameters for")
paramListCmd.Flags().Bool(flagWithoutModules, false, "Exclude module defaults")
viper.BindPFlag(vParamListWithoutModules, paramListCmd.Flags().Lookup(flagWithoutModules))
return paramListCmd
}
......@@ -28,11 +28,12 @@ func Test_paramListCmd(t *testing.T) {
args: []string{"param", "list"},
action: actionParamList,
expected: map[string]interface{}{
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionWithoutModules: false,
},
},
{
......@@ -40,11 +41,12 @@ func Test_paramListCmd(t *testing.T) {
args: []string{"param", "list", "-o", "json"},
action: actionParamList,
expected: map[string]interface{}{
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "json",
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "json",
actions.OptionWithoutModules: false,
},
},
{
......@@ -52,11 +54,12 @@ func Test_paramListCmd(t *testing.T) {
args: []string{"param", "list", "component"},
action: actionParamList,
expected: map[string]interface{}{
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "",
actions.OptionComponentName: "component",
actions.OptionOutput: "",
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "",
actions.OptionComponentName: "component",
actions.OptionOutput: "",
actions.OptionWithoutModules: false,
},
},
{
......@@ -64,11 +67,12 @@ func Test_paramListCmd(t *testing.T) {
args: []string{"param", "list", "--module", "module"},
action: actionParamList,
expected: map[string]interface{}{
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "module",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionApp: nil,
actions.OptionEnvName: "",
actions.OptionModule: "module",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionWithoutModules: false,
},
},
{
......@@ -76,11 +80,25 @@ func Test_paramListCmd(t *testing.T) {
args: []string{"param", "list", "--env", "env"},
action: actionParamList,
expected: map[string]interface{}{
actions.OptionApp: nil,
actions.OptionEnvName: "env",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionApp: nil,
actions.OptionEnvName: "env",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionWithoutModules: false,
},
},
{
name: "env without modules",
args: []string{"param", "list", "--env", "env", "--without-modules"},
action: actionParamList,
expected: map[string]interface{}{
actions.OptionApp: nil,
actions.OptionEnvName: "env",
actions.OptionModule: "",
actions.OptionComponentName: "",
actions.OptionOutput: "",
actions.OptionWithoutModules: true,
},
},
}
......
......@@ -18,6 +18,7 @@ package pipeline
import (
"bytes"
"encoding/json"
"fmt"
"io"
"path/filepath"
"regexp"
......@@ -58,6 +59,7 @@ type Pipeline struct {
buildObjectsFn func(*Pipeline, []string) ([]*unstructured.Unstructured, error)
evaluateEnvFn func(a app.App, envName, components, paramsStr string, opts ...jsonnet.VMOpt) (string, error)
evaluateEnvParamsFn func(a app.App, sourcePath, paramsStr, envName, moduleName string) (string, error)
stubModuleFn func(m component.Module) (string, error)
}
// New creates an instance of Pipeline.
......@@ -70,6 +72,7 @@ func New(ksApp app.App, envName string, opts ...Opt) *Pipeline {
buildObjectsFn: buildObjects,
evaluateEnvFn: env.Evaluate,
evaluateEnvParamsFn: params.EvaluateEnv,
stubModuleFn: stubModule,
}
for _, opt := range opts {
......@@ -85,15 +88,15 @@ func (p *Pipeline) Modules() ([]component.Module, error) {
}
// EnvParameters creates parameters for a namespace given an environment.
func (p *Pipeline) EnvParameters(moduleName string) (string, error) {
func (p *Pipeline) EnvParameters(moduleName string, inherited bool) (string, error) {
module, err := p.cm.Module(p.app, moduleName)
if err != nil {
return "", errors.Wrapf(err, "load module %s", moduleName)
}
paramsStr, err := module.ResolvedParams(p.envName)
paramsStr, err := p.moduleParams(module, inherited)
if err != nil {
return "", errors.Wrapf(err, "resolve params for %s", moduleName)
return "", err
}
data, err := p.app.EnvironmentParams(p.envName)
......@@ -119,6 +122,45 @@ func (p *Pipeline) EnvParameters(moduleName string) (string, error) {
return vm.EvaluateSnippet("snippet", string(envParams))
}
func (p *Pipeline) moduleParams(module component.Module, inherited bool) (string, error) {
if !inherited {
return stubModule(module)
}
paramsStr, err := module.ResolvedParams(p.envName)
if err != nil {
return "", errors.Wrapf(err, "resolve params for %s", module.Name())
}
fmt.Println(paramsStr)
return paramsStr, nil
}
func stubModule(module component.Module) (string, error) {
componentsObject := map[string]interface{}{}
components, err := module.Components()
if err != nil {
return "", errors.Wrap(err, "loading module components")
}
for _, c := range components {
componentsObject[c.Name(true)] = make(map[string]interface{})
}
m := map[string]interface{}{
"components": componentsObject,
}
data, err := json.Marshal(&m)
if err != nil {
return "", err
}
return string(data), nil
}
// Components returns the components that belong to this pipeline.
func (p *Pipeline) Components(filter []string) ([]component.Component, error) {
modules, err := p.Modules()
......
......@@ -27,6 +27,8 @@ import (
cmocks "github.com/ksonnet/ksonnet/pkg/component/mocks"
"github.com/ksonnet/ksonnet/pkg/metadata"
"github.com/ksonnet/ksonnet/pkg/util/jsonnet"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
......@@ -56,7 +58,31 @@ func TestPipeline_EnvParameters(t *testing.T) {
env := &app.EnvironmentConfig{Path: "default"}
a.On("Environment", "default").Return(env, nil)
got, err := p.EnvParameters("/")
got, err := p.EnvParameters("/", true)
require.NoError(t, err)
require.Equal(t, "{ }\n", got)
})
}
func TestPipeline_EnvParameters_inherited(t *testing.T) {
withPipeline(t, func(p *Pipeline, m *cmocks.Manager, a *appmocks.App) {
module := &cmocks.Module{}
module.On("ResolvedParams", "default").Return("{}", nil)
c := &cmocks.Component{}
c.On("Name", true).Return("app")
module.On("Components").Return([]component.Component{c}, nil)
namespaces := []component.Module{module}
m.On("Modules", p.app, "default").Return(namespaces, nil)
m.On("Module", p.app, "/").Return(module, nil)
a.On("EnvironmentParams", "default").Return("{}", nil)
env := &app.EnvironmentConfig{Path: "default"}
a.On("Environment", "default").Return(env, nil)
got, err := p.EnvParameters("/", false)
require.NoError(t, err)
require.Equal(t, "{ }\n", got)
......@@ -217,6 +243,56 @@ func Test_upgradeParams(t *testing.T) {
require.Equal(t, expected, got)
}
func Test_stubModule(t *testing.T) {
cases := []struct {
name string
module func() *cmocks.Module
expected string
isErr bool
}{
{
name: "valid",
module: func() *cmocks.Module {
module := &cmocks.Module{}
c := &cmocks.Component{}
c.On("Name", true).Return("app")
module.On("Components").Return([]component.Component{c}, nil)
return module
},
expected: `{"components":{"app":{}}}`,
},
{
name: "invalid components",
module: func() *cmocks.Module {
module := &cmocks.Module{}
module.On("Components").Return(nil, errors.New("failed"))
return module
},
isErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require.NotNil(t, tc.module)
module := tc.module()
got, err := stubModule(module)
if tc.isErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.expected, got)
})
}
}
func withPipeline(t *testing.T, fn func(p *Pipeline, m *cmocks.Manager, a *appmocks.App)) {
a := &appmocks.App{}
a.On("Root").Return("/")
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment