Commit b0a1f418 authored by Oren Shomron's avatar Oren Shomron
Browse files

Show environment scope in pkg list



Closes #626
Signed-off-by: default avatarOren Shomron <shomron@gmail.com>
parent 11938d61
......@@ -111,7 +111,12 @@ func (pl *PkgList) Run() error {
if err != nil {
return err
}
rows = append(rows, pl.addRow(p.RegistryName(), p.Name(), p.Version(), isInstalled))
envs, err := envListForPackage(pl.pm, p)
if err != nil {
return err
}
rows = append(rows, pl.addRow(p.RegistryName(), p.Name(), p.Version(), isInstalled, envs))
}
sort.Slice(rows, func(i, j int) bool {
......@@ -129,19 +134,44 @@ func (pl *PkgList) Run() error {
}
t.SetFormat(f)
t.SetHeader([]string{"registry", "name", "version", "installed"})
t.SetHeader([]string{"registry", "name", "version", "installed", "environments"})
t.AppendBulk(rows)
return t.Render()
}
func (pl *PkgList) addRow(regName, libName, version string, isInstalled bool) []string {
row := []string{regName, libName, version}
func (pl *PkgList) addRow(regName, libName, version string, isInstalled bool, envs string) []string {
installedText := ""
if isInstalled {
installedText = pkgInstalled
}
row = append(row, installedText)
row := []string{regName, libName, version, installedText, envs}
return row
}
type pkgEnvResolver interface {
PackageEnvironments(pkg.Package) ([]*app.EnvironmentConfig, error)
}
// envListForPackage returns a comma-separated list of environments the given package is installed in
func envListForPackage(pm pkgEnvResolver, pkg pkg.Package) (string, error) {
if pm == nil {
return "", errors.New("nil package manager")
}
if pkg == nil {
return "", errors.New("nil package")
}
envs, err := pm.PackageEnvironments(pkg)
if err != nil {
return "", err
}
names := make([]string, 0, len(envs))
for _, e := range envs {
names = append(names, e.Name)
}
sort.Sort(sort.StringSlice(names))
return strings.Join(names, ", "), nil
}
......@@ -20,11 +20,15 @@ import (
"fmt"
"testing"
"github.com/ksonnet/ksonnet/pkg/app"
amocks "github.com/ksonnet/ksonnet/pkg/app/mocks"
"github.com/ksonnet/ksonnet/pkg/pkg"
pmocks "github.com/ksonnet/ksonnet/pkg/pkg/mocks"
"github.com/ksonnet/ksonnet/pkg/prototype"
rmocks "github.com/ksonnet/ksonnet/pkg/registry/mocks"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
......@@ -93,6 +97,29 @@ func TestPkgList(t *testing.T) {
},
nil,
)
pmMock.On("PackageEnvironments", mock.Anything).Return(
func(p pkg.Package) []*app.EnvironmentConfig {
switch {
case p.Name() == "lib1" && p.Version() == "0.0.1":
return []*app.EnvironmentConfig{
&app.EnvironmentConfig{
Name: "production",
},
&app.EnvironmentConfig{
Name: "default",
},
}
case p.Name() == "lib1" && p.Version() == "0.0.2":
return []*app.EnvironmentConfig{
&app.EnvironmentConfig{
Name: "stage",
},
}
default:
return nil
}
}, nil,
)
cases := []struct {
name string
......@@ -156,3 +183,73 @@ func TestPkgList_requires_app(t *testing.T) {
_, err := NewPkgList(in)
require.Error(t, err)
}
func makePkg(name string) pkg.Package {
var pkg pmocks.Package
pkg.On("Name").Return(name)
return &pkg
}
func TestPkgList_envListForPackage(t *testing.T) {
var pkgEnvs = map[string][]string{
"lib1": []string{"z", "y", "x"},
"lib2": []string{},
}
pm := rmocks.PackageManager{}
pm.On("PackageEnvironments", mock.Anything).Return(
func(p pkg.Package) []*app.EnvironmentConfig {
names, ok := pkgEnvs[p.Name()]
if !ok {
return nil
}
result := make([]*app.EnvironmentConfig, 0, len(names))
for _, name := range names {
result = append(result,
&app.EnvironmentConfig{
Name: name,
},
)
}
return result
},
func(p pkg.Package) error {
if _, ok := pkgEnvs[p.Name()]; !ok {
return errors.New("no such package")
}
return nil
},
)
tests := []struct {
name string
pkg pkg.Package
expected string
expectErr bool
}{
{
name: "existing package",
pkg: makePkg("lib1"),
expected: "x, y, z",
},
{
name: "package has not environments",
pkg: makePkg("lib2"),
expected: "",
},
{
name: "nonexistent package",
pkg: makePkg("no such package"),
expectErr: true,
},
}
for _, tc := range tests {
actual, err := envListForPackage(&pm, tc.pkg)
if tc.expectErr {
require.Error(t, err, tc.name)
continue
}
require.NoError(t, err, tc.name)
assert.Equal(t, tc.expected, actual, tc.name)
}
}
REGISTRY NAME VERSION INSTALLED
======== ==== ======= =========
incubator lib1 0.0.1 *
incubator lib1 0.0.2 *
REGISTRY NAME VERSION INSTALLED ENVIRONMENTS
======== ==== ======= ========= ============
incubator lib1 0.0.1 * default, production
incubator lib1 0.0.2 * stage
......@@ -2,18 +2,21 @@
"kind": "pkgList",
"data": [
{
"environments": "default, production",
"installed": "*",
"name": "lib1",
"registry": "incubator",
"version": "0.0.1"
},
{
"environments": "stage",
"installed": "*",
"name": "lib1",
"registry": "incubator",
"version": "0.0.2"
},
{
"environments": "",
"installed": "",
"name": "lib2",
"registry": "incubator",
......
REGISTRY NAME VERSION INSTALLED
======== ==== ======= =========
incubator lib1 0.0.1 *
incubator lib1 0.0.2 *
REGISTRY NAME VERSION INSTALLED ENVIRONMENTS
======== ==== ======= ========= ============
incubator lib1 0.0.1 * default, production
incubator lib1 0.0.2 * stage
incubator lib2 master
......@@ -70,6 +70,29 @@ func (_m *PackageManager) IsInstalled(d pkg.Descriptor) (bool, error) {
return r0, r1
}
// PackageEnvironments provides a mock function with given fields: _a0
func (_m *PackageManager) PackageEnvironments(_a0 pkg.Package) ([]*app.EnvironmentConfig, error) {
ret := _m.Called(_a0)
var r0 []*app.EnvironmentConfig
if rf, ok := ret.Get(0).(func(pkg.Package) []*app.EnvironmentConfig); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*app.EnvironmentConfig)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(pkg.Package) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Packages provides a mock function with given fields:
func (_m *PackageManager) Packages() ([]pkg.Package, error) {
ret := _m.Called()
......
......@@ -50,6 +50,9 @@ type PackageManager interface {
// Prototypes lists prototypes.
Prototypes() (prototype.Prototypes, error)
// PackageEnvironments returns a list of environments a package is installed in.
PackageEnvironments(pkg pkg.Package) ([]*app.EnvironmentConfig, error)
InstalledChecker
}
......@@ -65,6 +68,7 @@ type packageManager struct {
packagesFn func() ([]pkg.Package, error)
registriesFn func() (map[string]SpecFetcher, error)
resolverFn func(name string) (LibrarySpecResolver, error)
environmentsFn func() (app.EnvironmentConfigs, error)
}
var _ PackageManager = (*packageManager)(nil)
......@@ -91,6 +95,13 @@ func NewPackageManager(a app.App) PackageManager {
}
return LibrarySpecResolver(r), nil
}
if a != nil {
pm.environmentsFn = a.Environments
} else {
pm.environmentsFn = func() (app.EnvironmentConfigs, error) {
return nil, errors.New("not implemented")
}
}
return &pm
}
......@@ -575,3 +586,28 @@ func (m *packageManager) IsInstalled(d pkg.Descriptor) (bool, error) {
byVer := index[d]
return len(byVer) > 0, nil
}
// PackageEnvironments returns a list of environments a package is installed in.
func (m *packageManager) PackageEnvironments(pkg pkg.Package) ([]*app.EnvironmentConfig, error) {
if pkg == nil {
return nil, errors.New("nil package")
}
envs, err := m.environmentsFn()
if err != nil {
return nil, nil
}
results := make([]*app.EnvironmentConfig, 0)
for _, e := range envs {
for _, l := range e.Libraries {
if l.Registry == pkg.RegistryName() &&
l.Name == pkg.Name() &&
l.Version == pkg.Version() {
results = append(results, e)
}
}
}
return results, nil
}
......@@ -16,12 +16,14 @@
package registry
import (
"sort"
"testing"
"github.com/ksonnet/ksonnet/pkg/app"
amocks "github.com/ksonnet/ksonnet/pkg/app/mocks"
"github.com/ksonnet/ksonnet/pkg/parts"
"github.com/ksonnet/ksonnet/pkg/pkg"
pmocks "github.com/ksonnet/ksonnet/pkg/pkg/mocks"
"github.com/ksonnet/ksonnet/pkg/prototype"
"github.com/ksonnet/ksonnet/pkg/util/test"
"github.com/pkg/errors"
......@@ -587,3 +589,116 @@ func Test_packageManager_RemotePackages(t *testing.T) {
assert.Subset(t, packages, expected)
})
}
func makePkg(registry string, name string, version string) pkg.Package {
var pkg pmocks.Package
pkg.On("Name").Return(name)
pkg.On("Version").Return(version)
pkg.On("RegistryName").Return(registry)
return &pkg
}
func Test_packageManger_PackageEnvironments(t *testing.T) {
var mockApp amocks.App
mockApp.On("Environments").Return(
app.EnvironmentConfigs{
"default": &app.EnvironmentConfig{Name: "default"},
"stage": &app.EnvironmentConfig{
Name: "stage",
Libraries: app.LibraryConfigs{
"lib1": &app.LibraryConfig{
Name: "lib1",
Version: "1.2.3",
Registry: "incubator",
},
"lib2": &app.LibraryConfig{
Name: "lib2",
Version: "0.0.1",
Registry: "incubator",
},
},
},
"dev": &app.EnvironmentConfig{
Name: "dev",
Libraries: app.LibraryConfigs{
"not-incubator/lib1": &app.LibraryConfig{
Name: "lib1",
Version: "1.2.3",
Registry: "not-incubator",
},
"lib1": &app.LibraryConfig{
Name: "lib1",
Version: "2.0.0",
Registry: "incubator",
},
"lib2": &app.LibraryConfig{
Name: "lib2",
Version: "0.0.1",
Registry: "incubator",
},
},
},
}, nil,
)
var pm = packageManager{
environmentsFn: mockApp.Environments,
}
tests := []struct {
caseName string
name string
version string
registry string
expected []string
expectErr bool
}{
{
caseName: "in single environment",
name: "lib1",
version: "1.2.3",
registry: "incubator",
expected: []string{"stage"},
},
{
caseName: "same name, different version",
name: "lib1",
version: "2.0.0",
registry: "incubator",
expected: []string{"dev"},
},
{
caseName: "in multiple environments",
name: "lib2",
version: "0.0.1",
registry: "incubator",
expected: []string{"dev", "stage"},
},
{
caseName: "in no environments",
name: "lib1",
version: "uninstalled-version",
registry: "incubator",
expected: []string{},
},
}
for _, tc := range tests {
pkg := makePkg(tc.registry, tc.name, tc.version)
envs, err := pm.PackageEnvironments(pkg)
if tc.expectErr {
require.Error(t, err, tc.name)
continue
}
require.NoError(t, err, tc.name)
actual := make([]string, 0, len(envs))
for _, e := range envs {
actual = append(actual, e.Name)
}
sort.Sort(sort.StringSlice(actual))
assert.Equal(t, tc.expected, actual)
}
}
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