Unverified Commit c4ee18ba authored by Bryan Liles's avatar Bryan Liles Committed by GitHub
Browse files

Merge pull request #379 from bryanl/refactor-registry-manager

Create file system based part
parents 7f63e461 f9bb7d62
......@@ -22,6 +22,7 @@ import (
cmocks "github.com/ksonnet/ksonnet/component/mocks"
"github.com/ksonnet/ksonnet/metadata/app/mocks"
"github.com/ksonnet/ksonnet/pkg/registry"
rmocks "github.com/ksonnet/ksonnet/pkg/registry/mocks"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
......@@ -68,7 +69,7 @@ func mockNsWithName(name string) *cmocks.Namespace {
func mockRegistry(name string) *rmocks.Registry {
m := &rmocks.Registry{}
m.On("Name").Return(name)
m.On("Protocol").Return("github")
m.On("Protocol").Return(registry.ProtocolGitHub)
m.On("URI").Return("github.com/ksonnet/parts/tree/master/incubator")
return m
......
// 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/ksonnet/ksonnet/metadata/app"
"github.com/ksonnet/ksonnet/pkg/pkg"
"github.com/ksonnet/ksonnet/pkg/registry"
)
// DepCacher is a function that caches a dependency.j
type DepCacher func(app.App, pkg.Descriptor, string) error
// PkgInstallDepCacher sets the dep cacher for pkg install.
func PkgInstallDepCacher(dc DepCacher) PkgInstallOpt {
return func(pi *PkgInstall) {
pi.depCacher = dc
}
}
// PkgInstallOpt is an option for configuring PkgInstall.
type PkgInstallOpt func(*PkgInstall)
// RunPkgInstall runs `pkg install`
func RunPkgInstall(ksApp app.App, libName, customName string, opts ...PkgInstallOpt) error {
pi, err := NewPkgInstall(ksApp, libName, customName, opts...)
if err != nil {
return err
}
return pi.Run()
}
// PkgInstall lists namespaces.
type PkgInstall struct {
app app.App
libName string
customName string
depCacher DepCacher
}
// NewPkgInstall creates an instance of PkgInstall.
func NewPkgInstall(ksApp app.App, libName, name string, opts ...PkgInstallOpt) (*PkgInstall, error) {
nl := &PkgInstall{
app: ksApp,
libName: libName,
customName: name,
depCacher: registry.CacheDependency,
}
for _, opt := range opts {
opt(nl)
}
return nl, nil
}
// Run lists namespaces.
func (pi *PkgInstall) Run() error {
d, customName, err := pi.parseDepSpec()
if err != nil {
return err
}
return pi.depCacher(pi.app, d, customName)
}
func (pi *PkgInstall) parseDepSpec() (pkg.Descriptor, string, error) {
d, err := pkg.ParseName(pi.libName)
if err != nil {
return pkg.Descriptor{}, "", err
}
customName := pi.customName
if customName == "" {
customName = d.Part
}
return d, customName, 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 (
"testing"
"github.com/ksonnet/ksonnet/metadata/app"
amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
"github.com/ksonnet/ksonnet/pkg/pkg"
"github.com/ksonnet/ksonnet/pkg/registry"
"github.com/stretchr/testify/require"
)
func TestPkgInstall(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
libName := "incubator/apache"
customName := "customName"
dc := func(a app.App, d pkg.Descriptor, cn string) error {
expectedD := pkg.Descriptor{
Registry: "incubator",
Part: "apache",
}
require.Equal(t, expectedD, d)
require.Equal(t, "customName", cn)
return nil
}
dcOpt := PkgInstallDepCacher(dc)
a, err := NewPkgInstall(appMock, libName, customName, dcOpt)
require.NoError(t, err)
libaries := app.LibraryRefSpecs{}
appMock.On("Libraries").Return(libaries, nil)
registries := app.RegistryRefSpecs{
"incubator": &app.RegistryRefSpec{
Protocol: registry.ProtocolFilesystem,
URI: "file:///tmp",
},
}
appMock.On("Registries").Return(registries, nil)
err = a.Run()
require.NoError(t, err)
})
}
// 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 (
"io"
"os"
"sort"
"strings"
"github.com/ksonnet/ksonnet/metadata/app"
"github.com/ksonnet/ksonnet/pkg/registry"
"github.com/ksonnet/ksonnet/pkg/util/table"
)
const (
// pkgInstalled denotes a package is installed
pkgInstalled = "*"
)
// RunPkgList runs `pkg list`
func RunPkgList(ksApp app.App) error {
rl, err := NewPkgList(ksApp)
if err != nil {
return err
}
return rl.Run()
}
// PkgList lists available registries
type PkgList struct {
app app.App
rm registry.Manager
out io.Writer
}
// NewPkgList creates an instance of PkgList
func NewPkgList(ksApp app.App) (*PkgList, error) {
rl := &PkgList{
app: ksApp,
rm: registry.DefaultManager,
out: os.Stdout,
}
return rl, nil
}
// Run runs the env list action.
func (pl *PkgList) Run() error {
registries, err := pl.rm.List(pl.app)
if err != nil {
return err
}
var rows [][]string
appLibraries, err := pl.app.Libraries()
if err != nil {
return err
}
for _, r := range registries {
spec, err := r.FetchRegistrySpec()
if err != nil {
return err
}
for libName := range spec.Libraries {
row := []string{r.Name(), libName}
_, isInstalled := appLibraries[libName]
if isInstalled {
row = append(row, pkgInstalled)
}
rows = append(rows, row)
}
}
sort.Slice(rows, func(i, j int) bool {
nameI := strings.Join([]string{rows[i][0], rows[i][1]}, "-")
nameJ := strings.Join([]string{rows[j][0], rows[j][1]}, "-")
return nameI < nameJ
})
t := table.New(pl.out)
t.SetHeader([]string{"registry", "name", "installed"})
t.AppendBulk(rows)
return t.Render()
}
// 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"
"github.com/ksonnet/ksonnet/metadata/app"
amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
"github.com/ksonnet/ksonnet/pkg/registry"
rmocks "github.com/ksonnet/ksonnet/pkg/registry/mocks"
"github.com/stretchr/testify/require"
)
func TestPkgList(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
libaries := app.LibraryRefSpecs{
"lib1": &app.LibraryRefSpec{},
}
appMock.On("Libraries").Return(libaries, nil)
a, err := NewPkgList(appMock)
require.NoError(t, err)
var buf bytes.Buffer
a.out = &buf
rm := &rmocks.Manager{}
a.rm = rm
spec := &registry.Spec{
Libraries: registry.LibraryRefSpecs{
"lib1": &registry.LibraryRef{},
"lib2": &registry.LibraryRef{},
},
}
incubator := mockRegistry("incubator")
incubator.On("FetchRegistrySpec").Return(spec, nil)
registries := []registry.Registry{incubator}
rm.On("List", appMock).Return(registries, nil)
err = a.Run()
require.NoError(t, err)
assertOutput(t, "pkg/list/output.txt", buf.String())
})
}
// 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 (
"net/url"
"strings"
"github.com/ksonnet/ksonnet/metadata/app"
"github.com/ksonnet/ksonnet/pkg/registry"
)
// RunRegistryAdd runs `registry add`
func RunRegistryAdd(ksApp app.App, name, uri, version string) error {
nl, err := NewRegistryAdd(ksApp, name, uri, version)
if err != nil {
return err
}
return nl.Run()
}
// RegistryAdd lists namespaces.
type RegistryAdd struct {
app app.App
name string
uri string
version string
rm registry.Manager
}
// NewRegistryAdd creates an instance of RegistryAdd.
func NewRegistryAdd(ksApp app.App, name, uri, version string) (*RegistryAdd, error) {
ra := &RegistryAdd{
app: ksApp,
name: name,
uri: uri,
version: version,
rm: registry.DefaultManager,
}
return ra, nil
}
// Run lists namespaces.
func (ra *RegistryAdd) Run() error {
uri, protocol := ra.protocol()
_, err := ra.rm.Add(ra.app, ra.name, protocol, uri, ra.version)
return err
}
func (ra *RegistryAdd) protocol() (string, string) {
if strings.HasPrefix(ra.uri, "file://") {
return ra.uri, registry.ProtocolFilesystem
}
if strings.HasPrefix(ra.uri, "/") {
u := url.URL{
Scheme: "file",
Path: ra.uri,
}
return u.String(), registry.ProtocolFilesystem
}
return ra.uri, registry.ProtocolGitHub
}
// 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 (
"testing"
amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
"github.com/ksonnet/ksonnet/pkg/registry"
"github.com/ksonnet/ksonnet/pkg/registry/mocks"
"github.com/stretchr/testify/require"
)
func TestRegistryAdd(t *testing.T) {
withApp(t, func(appMock *amocks.App) {
name := "new"
cases := []struct {
name string
uri string
version string
expectedURI string
protocol string
}{
{
name: "github",
uri: "github.com/foo/bar",
expectedURI: "github.com/foo/bar",
protocol: registry.ProtocolGitHub,
},
{
name: "fs",
uri: "/path",
expectedURI: "file:///path",
protocol: registry.ProtocolFilesystem,
},
{
name: "fs with URL",
uri: "file:///path",
expectedURI: "file:///path",
protocol: registry.ProtocolFilesystem,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
a, err := NewRegistryAdd(appMock, name, tc.uri, tc.version)
require.NoError(t, err)
rm := &mocks.Manager{}
rm.On("Add", appMock, "new", tc.protocol, tc.expectedURI, tc.version).Return(nil, nil)
a.rm = rm
err = a.Run()
require.NoError(t, err)
})
}
})
}
......@@ -54,7 +54,7 @@ func NewRegistryList(ksApp app.App) (*RegistryList, error) {
// Run runs the env list action.
func (rl *RegistryList) Run() error {
registries, err := rl.rm.Registries(rl.app)
registries, err := rl.rm.List(rl.app)
if err != nil {
return err
}
......
......@@ -39,7 +39,7 @@ func TestRegistryList(t *testing.T) {
registries := []registry.Registry{
mockRegistry("incubator"),
}
rm.On("Registries", appMock).Return(registries, nil)
rm.On("List", appMock).Return(registries, nil)
err = a.Run()
require.NoError(t, err)
......
REGISTRY NAME INSTALLED
======== ==== =========
incubator lib1 *
incubator lib2
......@@ -18,12 +18,10 @@ package cmd
import (
"fmt"
"os"
"sort"
"strings"
"github.com/ksonnet/ksonnet/metadata"
"github.com/ksonnet/ksonnet/metadata/parts"
"github.com/ksonnet/ksonnet/pkg/util/table"
"github.com/ksonnet/ksonnet/pkg/parts"
"github.com/spf13/cobra"
)
......@@ -41,10 +39,7 @@ var errInvalidSpec = fmt.Errorf("Command 'pkg install' requires a single argumen
func init() {
RootCmd.AddCommand(pkgCmd)
pkgCmd.AddCommand(pkgInstallCmd)
pkgCmd.AddCommand(pkgListCmd)
pkgCmd.AddCommand(pkgDescribeCmd)
pkgInstallCmd.PersistentFlags().String(flagName, "", "Name to give the dependency, to use within the ksonnet app")
}
var pkgCmd = &cobra.Command{
......@@ -84,67 +79,6 @@ See the annotated file tree below, as an example:
},
}
var pkgInstallCmd = &cobra.Command{
Use: "install <registry>/<library>@<version>",
Short: pkgShortDesc["install"],
Aliases: []string{"get"},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("Command requires a single argument of the form <registry>/<library>@<version>\n\n%s", cmd.UsageString())
}
registry, libID, name, version, err := parseDepSpec(cmd, args[0])
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
manager, err := metadata.Find(cwd)
if err != nil {
return err
}
_, err = manager.CacheDependency(registry, libID, name, version)
if err != nil {
return err
}
return nil
},
Long: `
The ` + "`install`" + ` command caches a ksonnet library locally, and makes it available
for use in the current ksonnet application. Enough info and metadata is recorded in
` + "`app.yaml` " + `that new users can retrieve the dependency after a fresh clone of this app.
The library itself needs to be located in a registry (e.g. Github repo). By default,
ksonnet knows about two registries: *incubator* and *stable*, which are the release
channels for official ksonnet libraries.
### Related Commands
* ` + "`ks pkg list` " + `— ` + pkgShortDesc["list"] + `
* ` + "`ks prototype list` " + `— ` + protoShortDesc["list"] + `
* ` + "`ks registry describe` " + `— ` + regShortDesc["describe"] + `
### Syntax
`,
Example: `
# Install an nginx dependency, based on the latest branch.
# In a ksonnet source file, this can be referenced as:
# local nginx = import "incubator/nginx/nginx.libsonnet";
ks pkg install incubator/nginx
# Install an nginx dependency, based on the 'master' branch.
# In a ksonnet source file, this can be referenced as:
# local nginx = import "incubator/nginx/nginx.libsonnet";
ks pkg install incubator/nginx@master
`,
}
var pkgDescribeCmd = &cobra.Command{
Use: "describe [<registry-name>/]<package-name>",
Short: pkgShortDesc["describe"],
......@@ -218,90 +152,6 @@ known ` + "`<registry-name>`" + ` like *incubator*). The output includes:
`,
}
var pkgListCmd = &cobra.Command{
Use: "list",