Allow resource.Builder commands to take arguments by type/name

Will allow xarg behavior to fetch resources across multiple types.
Changes 'create', 'get', 'update', 'stop', and 'delete' to output
<resourceType>/<name>.
This commit is contained in:
Clayton Coleman
2015-03-25 01:01:07 -04:00
parent a34f39aee4
commit 581d7cd789
16 changed files with 343 additions and 46 deletions

View File

@@ -99,7 +99,7 @@ func RunCreate(f *Factory, out io.Writer, cmd *cobra.Command, filenames util.Str
}
count++
info.Refresh(obj, true)
fmt.Fprintf(out, "%s\n", info.Name)
fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name)
return nil
})
if err != nil {

View File

@@ -61,7 +61,7 @@ func TestCreateObject(t *testing.T) {
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "redis-master-controller\n" {
if buf.String() != "replicationControllers/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -94,7 +94,7 @@ func TestCreateMultipleObject(t *testing.T) {
cmd.Run(cmd, []string{})
// Names should come from the REST response, NOT the files
if buf.String() != "rc1\nbaz\n" {
if buf.String() != "replicationControllers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -126,7 +126,7 @@ func TestCreateDirectory(t *testing.T) {
cmd.Flags().Set("filename", "../../../examples/guestbook")
cmd.Run(cmd, []string{})
if buf.String() != "name\nbaz\nname\nbaz\nname\nbaz\n" {
if buf.String() != "replicationControllers/name\nservices/baz\nreplicationControllers/name\nservices/baz\nreplicationControllers/name\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

View File

@@ -99,7 +99,7 @@ func RunDelete(f *Factory, out io.Writer, cmd *cobra.Command, args []string, fil
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
return err
}
fmt.Fprintf(out, "%s\n", r.Name)
fmt.Fprintf(out, "%s/%s\n", r.Mapping.Resource, r.Name)
return nil
})
if err != nil {

View File

@@ -50,7 +50,7 @@ func TestDeleteObject(t *testing.T) {
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "redis-master-controller\n" {
if buf.String() != "replicationControllers/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -109,7 +109,7 @@ func TestDeleteMultipleObject(t *testing.T) {
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json")
cmd.Run(cmd, []string{})
if buf.String() != "redis-master-controller\nfrontend\n" {
if buf.String() != "replicationControllers/redis-master-controller\nservices/frontend\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -141,7 +141,7 @@ func TestDeleteMultipleObjectIgnoreMissing(t *testing.T) {
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json")
cmd.Run(cmd, []string{})
if buf.String() != "frontend\n" {
if buf.String() != "services/frontend\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -172,7 +172,7 @@ func TestDeleteDirectory(t *testing.T) {
cmd.Flags().Set("filename", "../../../examples/guestbook")
cmd.Run(cmd, []string{})
if buf.String() != "frontend-controller\nfrontend\nredis-master-controller\nredis-master\nredis-slave-controller\nredis-slave\n" {
if buf.String() != "replicationControllers/frontend-controller\nservices/frontend\nreplicationControllers/redis-master-controller\nservices/redis-master\nreplicationControllers/redis-slave-controller\nservices/redis-slave\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -213,7 +213,7 @@ func TestDeleteMultipleSelector(t *testing.T) {
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
if buf.String() != "foo\nbar\nbaz\n" {
if buf.String() != "pods/foo\npods/bar\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

View File

@@ -34,31 +34,34 @@ const (
get_long = `Display one or many resources.
Possible resources include pods (po), replication controllers (rc), services
(se), minions (mi), or events (ev).
(svc), minions (mi), or events (ev).
By specifying the output as 'template' and providing a Go template as the value
of the --template flag, you can filter the attributes of the fetched resource(s).`
get_example = `// List all pods in ps output format.
$ kubectl get pods
// List a single replication controller with specified ID in ps output format.
$ kubectl get replicationController 1234-56-7890-234234-456456
// List a single replication controller with specified NAME in ps output format.
$ kubectl get replicationController web
// List a single pod in JSON output format.
$ kubectl get -o json pod 1234-56-7890-234234-456456
$ kubectl get -o json pod web-pod-13je7
// Return only the status value of the specified pod.
$ kubectl get -o template pod 1234-56-7890-234234-456456 --template={{.currentState.status}}
$ kubectl get -o template web-pod-13je7 --template={{.currentState.status}}
// List all replication controllers and services together in ps output format.
$ kubectl get rc,services`
$ kubectl get rc,services
// List one or more resources by their type and names
$ kubectl get rc/web service/frontend pods/web-pod-13je7`
)
// NewCmdGet creates a command object for the generic "get" action, which
// retrieves one or more resources from a server.
func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "get [(-o|--output=)json|yaml|template|...] RESOURCE [ID]",
Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
Short: "Display one or many resources",
Long: get_long,
Example: get_example,
@@ -141,6 +144,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error
NamespaceParam(cmdNamespace).DefaultNamespace().
SelectorParam(selector).
ResourceTypeOrNameArgs(true, args...).
ContinueOnError().
Latest()
printer, generic, err := util.PrinterForCommand(cmd)
if err != nil {

View File

@@ -30,6 +30,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
)
@@ -313,6 +314,47 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
}
}
func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
_, svc, _ := testData()
node := &api.Node{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
}
f, tf, codec := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &client.FakeRESTClient{
Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/nodes/foo":
return &http.Response{StatusCode: 200, Body: objBody(codec, node)}, nil
case "/namespaces/test/services/bar":
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"services/bar", "node/foo"})
expected := []runtime.Object{&svc.Items[0], node}
actual := tf.Printer.(*testPrinter).Objects
if !api.Semantic.DeepEqual(expected, actual) {
t.Errorf("unexpected object: %s", util.ObjectDiff(expected, actual))
}
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func watchTestData() ([]api.Pod, []watch.Event) {
pods := []api.Pod{
{

View File

@@ -71,11 +71,10 @@ func (f *Factory) NewCmdStop(out io.Writer) *cobra.Command {
r.Visit(func(info *resource.Info) error {
reaper, err := f.Reaper(info.Mapping)
cmdutil.CheckErr(err)
s, err := reaper.Stop(info.Namespace, info.Name)
if err != nil {
if _, err := reaper.Stop(info.Namespace, info.Name); err != nil {
return err
}
fmt.Fprintf(out, "%s\n", s)
fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name)
return nil
})
},

View File

@@ -111,7 +111,7 @@ func RunUpdate(f *Factory, out io.Writer, cmd *cobra.Command, args []string, fil
return err
}
info.Refresh(obj, true)
fmt.Fprintf(out, "%s\n", info.Name)
fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name)
return nil
})

View File

@@ -52,7 +52,7 @@ func TestUpdateObject(t *testing.T) {
cmd.Run(cmd, []string{})
// uses the name from the file, not the response
if buf.String() != "rc1\n" {
if buf.String() != "replicationControllers/rc1\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -88,7 +88,7 @@ func TestUpdateMultipleObject(t *testing.T) {
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json")
cmd.Run(cmd, []string{})
if buf.String() != "rc1\nbaz\n" {
if buf.String() != "replicationControllers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
@@ -120,7 +120,7 @@ func TestUpdateDirectory(t *testing.T) {
cmd.Flags().Set("namespace", "test")
cmd.Run(cmd, []string{})
if buf.String() != "rc1\nbaz\nrc1\nbaz\nrc1\nbaz\n" {
if buf.String() != "replicationControllers/rc1\nservices/baz\nreplicationControllers/rc1\nservices/baz\nreplicationControllers/rc1\nservices/baz\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

View File

@@ -49,7 +49,7 @@ func CheckErr(err error) {
if client.IsUnexpectedStatusError(err) {
glog.FatalDepth(1, fmt.Sprintf("Unexpected status received from server: %s", err.Error()))
}
glog.FatalDepth(1, fmt.Sprintf("Client error processing command: %s", err.Error()))
glog.FatalDepth(1, fmt.Sprintf("Error: %s", err.Error()))
}
}