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

Support package versioning in pkg describe command



Closes #630
Signed-off-by: default avatarOren Shomron <shomron@gmail.com>
parent 4b1939ed
......@@ -64,6 +64,7 @@ type packageManager struct {
InstallChecker pkg.InstallChecker
packagesFn func() ([]pkg.Package, error)
registriesFn func() (map[string]SpecFetcher, error)
resolverFn func(name string) (LibrarySpecResolver, error)
}
var _ PackageManager = (*packageManager)(nil)
......@@ -76,14 +77,26 @@ func NewPackageManager(a app.App) PackageManager {
}
pm.packagesFn = pm.Packages
pm.registriesFn = func() (map[string]SpecFetcher, error) {
return resolveRegistries(a)
r, err := resolveRegistries(a)
if err != nil {
return nil, err
}
return registriesToSpecFetchers(r), nil
}
pm.resolverFn = func(name string) (LibrarySpecResolver, error) {
r, err := resolveRegistry(a, name)
if err != nil {
return nil, err
}
return LibrarySpecResolver(r), nil
}
return &pm
}
// resolveRegistries returns a list of registries from the provided app.
// (SpecFetcher is a subset of the Registry interface)
func resolveRegistries(a app.App) (map[string]SpecFetcher, error) {
func resolveRegistries(a app.App) (map[string]Registry, error) {
if a == nil {
return nil, errors.New("nil app")
}
......@@ -93,7 +106,7 @@ func resolveRegistries(a app.App) (map[string]SpecFetcher, error) {
return nil, err
}
result := make(map[string]SpecFetcher)
result := make(map[string]Registry)
for _, cfg := range cfgs {
r, err := Locate(a, cfg)
if err != nil {
......@@ -105,54 +118,96 @@ func resolveRegistries(a app.App) (map[string]SpecFetcher, error) {
return result, nil
}
// Find finds a package by name. Package names have the format `<registry>/<library>@<version>`.
// Remote registries may be consulted if the package is not installed locally.
func (m *packageManager) Find(name string) (pkg.Package, error) {
d, err := pkg.Parse(name)
// resolveRegistry returns the named registry from the provided app.
func resolveRegistry(a app.App, name string) (Registry, error) {
if a == nil {
return nil, errors.New("nil app")
}
all, err := resolveRegistries(a)
if err != nil {
return nil, errors.Wrap(err, "parsing package name")
return nil, err
}
if d.Registry == "" {
packages, err := m.Packages()
if err != nil {
return nil, errors.Wrap(err, "loading packages")
}
for _, p := range packages {
if p.Name() == name {
return p, nil
}
}
r, ok := all[name]
if !ok {
return nil, errors.Errorf("registry not found: %s", name)
}
return r, nil
}
return nil, errors.Errorf("package %q was not found", name)
// Maps map[string]Registry -> map[string]SpecFectcher
func registriesToSpecFetchers(r map[string]Registry) map[string]SpecFetcher {
result := make(map[string]SpecFetcher)
for k, v := range r {
result[k] = SpecFetcher(v)
}
return result
}
registryConfigs, err := m.app.Registries()
// Maps map[string]Registry -> map[string]Resolver
func registriesToResolvers(r map[string]Registry) map[string]LibrarySpecResolver {
result := make(map[string]LibrarySpecResolver)
for k, v := range r {
result[k] = LibrarySpecResolver(v)
}
return result
}
// registryPrototcol returns the protocol for the named registry.
func registryProtocol(a registryConfigLister, name string) (proto Protocol, found bool) {
regs, err := a.Registries()
if err != nil {
return nil, errors.Wrap(err, "loading registry configurations")
return ProtocolInvalid, false
}
registryConfig, ok := registryConfigs[d.Registry]
r, ok := regs[name]
if !ok {
return nil, errors.Errorf("registry %q not found", d.Registry)
return ProtocolInvalid, false
}
return Protocol(r.Protocol), true
}
// Find finds a package by name. Package names have the format `<registry>/<library>@<version>`.
// Remote registries may be consulted if the package is not installed locally.
func (m *packageManager) Find(name string) (pkg.Package, error) {
if m.app == nil {
return nil, errors.New("nil app")
}
registry, err := Locate(m.app, registryConfig)
d, err := pkg.Parse(name)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "parsing package name")
}
libraryConfigs, err := m.app.Libraries()
index, err := allLibraries(m.app)
if err != nil {
return nil, errors.Wrap(err, "reading libraries defined in the configuration")
return nil, errors.Wrap(err, "resolving libraries")
}
libraryConfig, ok := libraryConfigs[d.Name]
if ok {
return m.loadPackage(registry.MakeRegistryConfig(), d.Name, d.Registry, libraryConfig.Version, m.InstallChecker)
if byVer, ok := index[d]; ok {
// NOTE: We will use the first match for ambiguous finds
for _, libCfg := range byVer {
protocol, ok := registryProtocol(m.app, libCfg.Registry)
if !ok {
return nil, errors.Errorf("library %s references invalid registry: %s", libCfg.Name, libCfg.Registry)
}
return m.loadPackage(protocol, libCfg.Name, libCfg.Registry, libCfg.Version, m.InstallChecker)
}
}
// TODO - Check libraries configured under environments
// If we are here, the package is not installed locally - construct a remote reference
if d.Registry == "" {
return nil, errors.New("cannot find package - please specify a registry")
}
registry, err := m.resolverFn(d.Registry)
if err != nil {
return nil, err
}
partConfig, err := registry.ResolveLibrarySpec(d.Name, d.Version)
if err != nil {
......@@ -227,21 +282,16 @@ func (m *packageManager) Packages() ([]pkg.Package, error) {
libraryConfigs := uniqueLibsByVersion(libIndex)
registryConfigs, err := m.app.Registries()
if err != nil {
return nil, errors.Wrap(err, "reading registries defined in the configuration")
}
packages := make([]pkg.Package, 0)
for _, libraryConfig := range libraryConfigs {
registryConfig, ok := registryConfigs[libraryConfig.Registry]
protocol, ok := registryProtocol(m.app, libraryConfig.Registry)
if !ok {
return nil, errors.Errorf("registry %q required by library %q is not defined in the configuration",
libraryConfig.Registry, libraryConfig.Name)
}
p, err := m.loadPackage(registryConfig, libraryConfig.Name, libraryConfig.Registry, libraryConfig.Version, pkg.TrueInstallChecker{})
p, err := m.loadPackage(protocol, libraryConfig.Name, libraryConfig.Registry, libraryConfig.Version, pkg.TrueInstallChecker{})
if err != nil {
return nil, err
}
......@@ -267,11 +317,6 @@ func (m *packageManager) PackagesForEnv(e *app.EnvironmentConfig) ([]pkg.Package
return nil, errors.Wrap(err, "reading libraries defined in the configuration")
}
registryConfigs, err := m.app.Registries()
if err != nil {
return nil, errors.Wrap(err, "reading registries defined in the configuration")
}
packages := make([]pkg.Package, 0)
combined := make(map[string]*app.LibraryConfig)
......@@ -287,13 +332,13 @@ func (m *packageManager) PackagesForEnv(e *app.EnvironmentConfig) ([]pkg.Package
}
for k, libraryConfig := range combined {
registryConfig, ok := registryConfigs[libraryConfig.Registry]
protocol, ok := registryProtocol(m.app, libraryConfig.Registry)
if !ok {
return nil, errors.Errorf("registry %q required by library %q is not defined in the configuration",
libraryConfig.Registry, k)
}
p, err := m.loadPackage(registryConfig, k, libraryConfig.Registry, libraryConfig.Version, pkg.TrueInstallChecker{})
p, err := m.loadPackage(protocol, k, libraryConfig.Registry, libraryConfig.Version, pkg.TrueInstallChecker{})
if err != nil {
return nil, err
}
......@@ -334,8 +379,8 @@ func (m *packageManager) RemotePackages() ([]pkg.Package, error) {
return pkgs, nil
}
func (m *packageManager) loadPackage(registryConfig *app.RegistryConfig, pkgName, registryName, version string, installChecker pkg.InstallChecker) (pkg.Package, error) {
switch protocol := registryConfig.Protocol; Protocol(protocol) {
func (m *packageManager) loadPackage(protocol Protocol, pkgName, registryName, version string, installChecker pkg.InstallChecker) (pkg.Package, error) {
switch protocol {
case ProtocolHelm:
h, err := pkg.NewHelm(m.app, pkgName, registryName, version, installChecker)
if err != nil {
......
......@@ -24,40 +24,197 @@ import (
"github.com/ksonnet/ksonnet/pkg/pkg"
"github.com/ksonnet/ksonnet/pkg/prototype"
"github.com/ksonnet/ksonnet/pkg/util/test"
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type miniRegistry interface {
SpecFetcher
LibrarySpecResolver
}
// fakeRegistry is a mini-registry that implements LibrarySpecResolver and SpecFetcher
type fakeRegistry struct {
pkgs map[app.LibraryConfig]*parts.Spec
registryName string
registry Spec
}
func (r *fakeRegistry) FetchRegistrySpec() (*Spec, error) {
return &r.registry, nil
}
func (r *fakeRegistry) ResolveLibrarySpec(name, version string) (*parts.Spec, error) {
key := app.LibraryConfig{
Name: name,
Version: version,
Registry: r.registryName,
}
result, ok := r.pkgs[key]
if !ok {
return nil, errors.Errorf("package not found: %s/%s@%s", r.registryName, name, version)
}
return result, nil
}
func Test_packageManager_Find(t *testing.T) {
makeRegistry := func(name string, cfgs app.LibraryConfigs) *fakeRegistry {
pkgs := make(map[app.LibraryConfig]*parts.Spec)
for _, l := range cfgs {
pkgs[*l] = &parts.Spec{
Name: l.Name,
Version: l.Version,
Description: l.Name,
}
}
r := &fakeRegistry{
pkgs: pkgs,
registryName: name,
registry: Spec{},
}
return r
}
test.WithApp(t, "/app", func(a *amocks.App, fs afero.Fs) {
test.StageDir(t, fs, "incubator/apache", "/work/apache")
test.StageDir(t, fs, "incubator/apache", "/app/vendor/incubator/apache")
test.StageDir(t, fs, "incubator/apache", "/app/vendor/incubator/apache@1.2.3")
test.StageDir(t, fs, "incubator/nginx", "/app/vendor/incubator/nginx@2.0.0")
a.On("VendorPath").Return("/app/vendor")
registries := app.RegistryConfigs{
"incubator": &app.RegistryConfig{
Protocol: "fs",
URI: "/work",
libraries := app.LibraryConfigs{
"apache": &app.LibraryConfig{
Name: "apache",
Version: "1.2.3",
Registry: "incubator",
},
"nginx": &app.LibraryConfig{
Name: "nginx",
Version: "2.0.0",
Registry: "incubator",
},
}
a.On("Registries").Return(registries, nil)
libraries := app.LibraryConfigs{
"apache": &app.LibraryConfig{},
remoteLibs := app.LibraryConfigs{
"mysql": &app.LibraryConfig{
Name: "mysql",
Version: "5.6.1",
Registry: "remote",
},
}
a.On("Libraries").Return(libraries, nil)
pm := NewPackageManager(a)
incubator := makeRegistry("incubator", libraries)
remote := makeRegistry("remote", remoteLibs)
registries := map[string]*fakeRegistry{
"incubator": incubator,
"remote": remote,
}
p, err := pm.Find("incubator/apache")
require.NoError(t, err)
a.On("Environments").Return(
app.EnvironmentConfigs{
"default": &app.EnvironmentConfig{
Name: "default",
},
}, nil,
)
a.On("Registries").Return(
app.RegistryConfigs{
"incubator": &app.RegistryConfig{
Name: "incubator",
Protocol: string(ProtocolFilesystem),
},
"remote": &app.RegistryConfig{
Name: "remote",
Protocol: string(ProtocolGitHub),
},
}, nil,
)
pm := packageManager{
app: a,
InstallChecker: &pkg.DefaultInstallChecker{App: a},
registriesFn: func() (map[string]SpecFetcher, error) {
result := make(map[string]SpecFetcher)
for k, v := range registries {
result[k] = v
}
return result, nil
},
resolverFn: func(name string) (LibrarySpecResolver, error) {
r, ok := registries[name]
if !ok {
return nil, errors.Errorf("invalid registry: %s", name)
}
return r, nil
},
}
tests := []struct {
name string
expectErr bool
expectName string
expectVersion string
}{
{
name: "incubator/apache",
expectName: "apache",
expectVersion: "1.2.3",
},
{
name: "apache",
expectName: "apache",
expectVersion: "1.2.3",
},
{
name: "apache@1.2.3",
expectName: "apache",
expectVersion: "1.2.3",
},
{
name: "incubator/apache@1.2.3",
expectName: "apache",
expectVersion: "1.2.3",
},
{
name: "incubator/apache@4.5.6",
expectErr: true,
},
{
name: "incubator/nginx@2.0.0",
expectName: "nginx",
expectVersion: "2.0.0",
},
{
name: "remote/mysql@5.6.1",
expectName: "mysql",
expectVersion: "5.6.1",
},
{
name: "mysql@5.6.1",
expectErr: true,
},
}
for _, tc := range tests {
p, err := pm.Find(tc.name)
if tc.expectErr {
require.Error(t, err, tc.name)
continue
}
require.NoError(t, err, tc.name)
require.Equal(t, tc.expectName, p.Name())
require.Equal(t, tc.expectVersion, p.Version())
}
require.Equal(t, "apache", p.Name())
})
}
......@@ -398,8 +555,6 @@ func Test_packageManager_RemotePackages(t *testing.T) {
"incubator": incubator,
}
a.On("Registries").Return(registries, nil)
// Expect global libraries + envLibraries
expected := []pkg.Package{
remotePackage{
......
......@@ -34,6 +34,8 @@ const (
ProtocolGitHub Protocol = "github"
// ProtocolHelm is the protocol for Helm based registries.
ProtocolHelm Protocol = "helm"
// ProtocolInvalid is an invalid protocol.
ProtocolInvalid Protocol = "invalid"
registryYAMLFile = "registry.yaml"
partsYAMLFile = "parts.yaml"
......@@ -49,7 +51,8 @@ type ResolveDirectory func(relPath string) error
type Registry interface {
RegistrySpecDir() string
RegistrySpecFilePath() string
Resolver
LibrarySpecResolver
LibraryResolver
Name() string
Protocol() Protocol
URI() string
......@@ -66,9 +69,13 @@ type SpecFetcher interface {
FetchRegistrySpec() (*Spec, error)
}
// Resolver fetches metadata and libraries (packages) from a registry
type Resolver interface {
// LibrarySpecResolver fetches metadata for a library.
type LibrarySpecResolver interface {
ResolveLibrarySpec(libID, libRefSpec string) (*parts.Spec, error)
}
// LibraryResolver fetches library (package) contents from a registry
type LibraryResolver interface {
ResolveLibrary(libID, libAlias, version string, onFile ResolveFile, onDir ResolveDirectory) (*parts.Spec, *app.LibraryConfig, error)
}
......
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