Skip to content
Snippets Groups Projects
Commit fe3962e3 authored by Jessica Yuen's avatar Jessica Yuen
Browse files

Introduce 'component' commands, starting with 'component list'


This adds a high level 'component' command and a 'component list'
command. 'component list' will pretty print all the components in
ksonnet application directory.

To accomplish this, an API is added to the metadata manager that returns
all components. Components are the individual files in /components, with
the path extension trimmed.

Signed-off-by: default avatarJessica Yuen <im.jessicayuen@gmail.com>
parent 94d58228
No related branches found
No related tags found
No related merge requests found
// Copyright 2017 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 cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/ksonnet/ksonnet/pkg/kubecfg"
)
func init() {
RootCmd.AddCommand(componentCmd)
componentCmd.AddCommand(componentListCmd)
}
var componentCmd = &cobra.Command{
Use: "component",
Short: "Manage ksonnet components",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("%s is not a valid subcommand\n\n%s", strings.Join(args, " "), cmd.UsageString())
}
return fmt.Errorf("Command 'component' requires a subcommand\n\n%s", cmd.UsageString())
},
}
var componentListCmd = &cobra.Command{
Use: "list",
Short: "List known components",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("'component list' takes zero arguments")
}
c := kubecfg.NewComponentListCmd()
return c.Run(cmd.OutOrStdout())
},
Long: `
The ` + "`list`" + ` command displays all known components.
### Syntax
`,
Example: `
# List all components
ks component list`,
}
...@@ -20,6 +20,7 @@ application configuration to remote clusters. ...@@ -20,6 +20,7 @@ application configuration to remote clusters.
### SEE ALSO ### SEE ALSO
* [ks apply](ks_apply.md) - Apply local Kubernetes manifests (components) to remote clusters * [ks apply](ks_apply.md) - Apply local Kubernetes manifests (components) to remote clusters
* [ks component](ks_component.md) - Manage ksonnet components
* [ks delete](ks_delete.md) - Remove component-specified Kubernetes resources from remote clusters * [ks delete](ks_delete.md) - Remove component-specified Kubernetes resources from remote clusters
* [ks diff](ks_diff.md) - Compare manifests, based on environment or location (local or remote) * [ks diff](ks_diff.md) - Compare manifests, based on environment or location (local or remote)
* [ks env](ks_env.md) - Manage ksonnet environments * [ks env](ks_env.md) - Manage ksonnet environments
......
## ks component
Manage ksonnet components
### Synopsis
Manage ksonnet components
```
ks component
```
### Options inherited from parent commands
```
-v, --verbose count[=-1] Increase verbosity. May be given multiple times.
```
### SEE ALSO
* [ks](ks.md) - Configure your application to deploy to a Kubernetes cluster
* [ks component list](ks_component_list.md) - List known components
## ks component list
List known components
### Synopsis
The `list` command displays all known components.
### Syntax
```
ks component list
```
### Examples
```
# List all components
ks component list
```
### Options inherited from parent commands
```
-v, --verbose count[=-1] Increase verbosity. May be given multiple times.
```
### SEE ALSO
* [ks component](ks_component.md) - Manage ksonnet components
...@@ -18,6 +18,7 @@ package metadata ...@@ -18,6 +18,7 @@ package metadata
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"strings" "strings"
param "github.com/ksonnet/ksonnet/metadata/params" param "github.com/ksonnet/ksonnet/metadata/params"
...@@ -28,13 +29,14 @@ import ( ...@@ -28,13 +29,14 @@ import (
func (m *manager) ComponentPaths() (AbsPaths, error) { func (m *manager) ComponentPaths() (AbsPaths, error) {
paths := AbsPaths{} paths := AbsPaths{}
err := afero.Walk(m.appFS, string(m.componentsPath), func(path string, info os.FileInfo, err error) error { err := afero.Walk(m.appFS, string(m.componentsPath), func(p string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
if !info.IsDir() { // Only add file paths and exclude the params.libsonnet file
paths = append(paths, path) if !info.IsDir() && path.Base(p) != componentParamsFile {
paths = append(paths, p)
} }
return nil return nil
}) })
...@@ -45,6 +47,21 @@ func (m *manager) ComponentPaths() (AbsPaths, error) { ...@@ -45,6 +47,21 @@ func (m *manager) ComponentPaths() (AbsPaths, error) {
return paths, nil return paths, nil
} }
func (m *manager) GetAllComponents() ([]string, error) {
componentPaths, err := m.ComponentPaths()
if err != nil {
return nil, err
}
var components []string
for _, p := range componentPaths {
component := strings.TrimSuffix(path.Base(p), path.Ext(p))
components = append(components, component)
}
return components, nil
}
func (m *manager) CreateComponent(name string, text string, params param.Params, templateType prototype.TemplateType) error { func (m *manager) CreateComponent(name string, text string, params param.Params, templateType prototype.TemplateType) error {
if !isValidName(name) || strings.Contains(name, "/") { if !isValidName(name) || strings.Contains(name, "/") {
return fmt.Errorf("Component name '%s' is not valid; must not contain punctuation, spaces, or begin or end with a slash", name) return fmt.Errorf("Component name '%s' is not valid; must not contain punctuation, spaces, or begin or end with a slash", name)
......
...@@ -18,16 +18,24 @@ import ( ...@@ -18,16 +18,24 @@ import (
"fmt" "fmt"
"os" "os"
"sort" "sort"
"strings"
"testing" "testing"
) )
func TestComponentPaths(t *testing.T) { const (
componentsPath = "/componentsPath"
componentSubdir = "subdir"
componentFile1 = "component1.jsonnet"
componentFile2 = "component2.jsonnet"
)
func populateComponentPaths(t *testing.T) *manager {
spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS)
if err != nil { if err != nil {
t.Fatalf("Failed to parse cluster spec: %v", err) t.Fatalf("Failed to parse cluster spec: %v", err)
} }
appPath := AbsPath("/componentPaths") appPath := AbsPath(componentsPath)
reg := newMockRegistryManager("incubator") reg := newMockRegistryManager("incubator")
m, err := initManager("componentPaths", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) m, err := initManager("componentPaths", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS)
if err != nil { if err != nil {
...@@ -36,7 +44,7 @@ func TestComponentPaths(t *testing.T) { ...@@ -36,7 +44,7 @@ func TestComponentPaths(t *testing.T) {
// Create empty app file. // Create empty app file.
components := appendToAbsPath(appPath, componentsDir) components := appendToAbsPath(appPath, componentsDir)
appFile1 := appendToAbsPath(components, "component1.jsonnet") appFile1 := appendToAbsPath(components, componentFile1)
f1, err := testFS.OpenFile(string(appFile1), os.O_RDONLY|os.O_CREATE, 0777) f1, err := testFS.OpenFile(string(appFile1), os.O_RDONLY|os.O_CREATE, 0777)
if err != nil { if err != nil {
t.Fatalf("Failed to touch app file '%s'\n%v", appFile1, err) t.Fatalf("Failed to touch app file '%s'\n%v", appFile1, err)
...@@ -44,12 +52,12 @@ func TestComponentPaths(t *testing.T) { ...@@ -44,12 +52,12 @@ func TestComponentPaths(t *testing.T) {
f1.Close() f1.Close()
// Create empty file in a nested directory. // Create empty file in a nested directory.
appSubdir := appendToAbsPath(components, "appSubdir") appSubdir := appendToAbsPath(components, componentSubdir)
err = testFS.MkdirAll(string(appSubdir), os.ModePerm) err = testFS.MkdirAll(string(appSubdir), os.ModePerm)
if err != nil { if err != nil {
t.Fatalf("Failed to create directory '%s'\n%v", appSubdir, err) t.Fatalf("Failed to create directory '%s'\n%v", appSubdir, err)
} }
appFile2 := appendToAbsPath(appSubdir, "component2.jsonnet") appFile2 := appendToAbsPath(appSubdir, componentFile2)
f2, err := testFS.OpenFile(string(appFile2), os.O_RDONLY|os.O_CREATE, 0777) f2, err := testFS.OpenFile(string(appFile2), os.O_RDONLY|os.O_CREATE, 0777)
if err != nil { if err != nil {
t.Fatalf("Failed to touch app file '%s'\n%v", appFile1, err) t.Fatalf("Failed to touch app file '%s'\n%v", appFile1, err)
...@@ -63,6 +71,17 @@ func TestComponentPaths(t *testing.T) { ...@@ -63,6 +71,17 @@ func TestComponentPaths(t *testing.T) {
t.Fatalf("Failed to create directory '%s'\n%v", unlistedDir, err) t.Fatalf("Failed to create directory '%s'\n%v", unlistedDir, err)
} }
return m
}
func cleanComponentPaths(t *testing.T) {
testFS.RemoveAll(componentsPath)
}
func TestComponentPaths(t *testing.T) {
m := populateComponentPaths(t)
defer cleanComponentPaths(t)
paths, err := m.ComponentPaths() paths, err := m.ComponentPaths()
if err != nil { if err != nil {
t.Fatalf("Failed to find component paths: %v", err) t.Fatalf("Failed to find component paths: %v", err)
...@@ -70,7 +89,35 @@ func TestComponentPaths(t *testing.T) { ...@@ -70,7 +89,35 @@ func TestComponentPaths(t *testing.T) {
sort.Slice(paths, func(i, j int) bool { return paths[i] < paths[j] }) sort.Slice(paths, func(i, j int) bool { return paths[i] < paths[j] })
if len(paths) != 3 || paths[0] != string(appFile2) || paths[1] != string(appFile1) { expectedPath1 := fmt.Sprintf("%s/components/%s", componentsPath, componentFile1)
t.Fatalf("m.ComponentPaths failed; expected '%s', got '%s'", []string{string(appFile1), string(appFile2)}, paths) expectedPath2 := fmt.Sprintf("%s/components/%s/%s", componentsPath, componentSubdir, componentFile2)
if len(paths) != 2 || paths[0] != expectedPath1 || paths[1] != expectedPath2 {
t.Fatalf("m.ComponentPaths failed; expected '%s', got '%s'", []string{expectedPath1, expectedPath2}, paths)
}
}
func TestGetAllComponents(t *testing.T) {
m := populateComponentPaths(t)
defer cleanComponentPaths(t)
components, err := m.GetAllComponents()
if err != nil {
t.Fatalf("Failed to get all components, %v", err)
}
expected1 := strings.TrimSuffix(componentFile1, ".jsonnet")
expected2 := strings.TrimSuffix(componentFile2, ".jsonnet")
if len(components) != 2 {
t.Fatalf("Expected exactly 2 components, got %d", len(components))
}
if components[0] != expected1 {
t.Fatalf("Expected component %s, got %s", expected1, components)
}
if components[1] != expected2 {
t.Fatalf("Expected component %s, got %s", expected2, components)
} }
} }
...@@ -49,6 +49,7 @@ type Manager interface { ...@@ -49,6 +49,7 @@ type Manager interface {
// Components API. // Components API.
ComponentPaths() (AbsPaths, error) ComponentPaths() (AbsPaths, error)
GetAllComponents() ([]string, error)
CreateComponent(name string, text string, params param.Params, templateType prototype.TemplateType) error CreateComponent(name string, text string, params param.Params, templateType prototype.TemplateType) error
// Params API. // Params API.
......
// Copyright 2017 The kubecfg 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 kubecfg
import (
"fmt"
"io"
"sort"
"strings"
"github.com/ksonnet/ksonnet/utils"
)
const (
componentNameHeader = "COMPONENT"
)
// ComponentListCmd stores the information necessary to display components.
type ComponentListCmd struct {
}
// NewComponentListCmd acts as a constructor for ComponentListCmd.
func NewComponentListCmd() *ComponentListCmd {
return &ComponentListCmd{}
}
// Run executes the displaying of components.
func (c *ComponentListCmd) Run(out io.Writer) error {
manager, err := manager()
if err != nil {
return err
}
components, err := manager.GetAllComponents()
if err != nil {
return err
}
_, err = printComponents(out, components)
return err
}
func printComponents(out io.Writer, components []string) (string, error) {
rows := [][]string{
[]string{componentNameHeader},
[]string{strings.Repeat("=", len(componentNameHeader))},
}
sort.Strings(components)
for _, component := range components {
rows = append(rows, []string{component})
}
formatted, err := utils.PadRows(rows)
if err != nil {
return "", err
}
_, err = fmt.Fprint(out, formatted)
return formatted, err
}
// Copyright 2017 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 kubecfg
import (
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestPrintComponents(t *testing.T) {
for _, tc := range []struct {
components []string
expected string
}{
{
components: []string{"a", "b"},
expected: `COMPONENT
=========
a
b
`,
},
// Check that components are displayed in alphabetical order
{
components: []string{"b", "a"},
expected: `COMPONENT
=========
a
b
`,
},
// Check empty components scenario
{
components: []string{},
expected: `COMPONENT
=========
`,
},
} {
out, err := printComponents(os.Stdout, tc.components)
if err != nil {
t.Fatalf(err.Error())
}
require.EqualValues(t, tc.expected, out)
}
}
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