mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #5903 from smarterclayton/support_resources_by_type_and_name
Allow resource.Builder commands to take arguments by type/name
This commit is contained in:
		@@ -8,13 +8,13 @@ Display one or many resources
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
kubectl get [(-o|--output=)json|yaml|template|...] RESOURCE [ID]
 | 
			
		||||
kubectl get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Examples
 | 
			
		||||
@@ -23,17 +23,20 @@ kubectl get [(-o|--output=)json|yaml|template|...] RESOURCE [ID]
 | 
			
		||||
// 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
 | 
			
		||||
 | 
			
		||||
// List one or more resources by their type and names
 | 
			
		||||
$ kubectl get rc/web service/frontend pods/web-pod-13je7
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Options
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ Display one or many resources.
 | 
			
		||||
 | 
			
		||||
.PP
 | 
			
		||||
Possible resources include pods (po), replication controllers (rc), services
 | 
			
		||||
(se), minions (mi), or events (ev).
 | 
			
		||||
(svc), minions (mi), or events (ev).
 | 
			
		||||
 | 
			
		||||
.PP
 | 
			
		||||
By specifying the output as 'template' and providing a Go template as the value
 | 
			
		||||
@@ -169,18 +169,21 @@ of the \-\-template flag, you can filter the attributes of the fetched resource(
 | 
			
		||||
// 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
 | 
			
		||||
 | 
			
		||||
// List one or more resources by their type and names
 | 
			
		||||
$ kubectl get rc/web service/frontend pods/web\-pod\-13je7
 | 
			
		||||
 | 
			
		||||
.fi
 | 
			
		||||
.RE
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -155,6 +155,8 @@ for version in "${kube_api_versions[@]}"; do
 | 
			
		||||
  # Post-condition: valid-pod POD is running
 | 
			
		||||
  kube::test::get_object_assert pods "{{range.items}}{{.$id_field}}:{{end}}" 'valid-pod:'
 | 
			
		||||
  kube::test::get_object_assert 'pod valid-pod' "{{.$id_field}}" 'valid-pod'
 | 
			
		||||
  kube::test::get_object_assert 'pod/valid-pod' "{{.$id_field}}" 'valid-pod'
 | 
			
		||||
  kube::test::get_object_assert 'pods/valid-pod' "{{.$id_field}}" 'valid-pod'
 | 
			
		||||
  # Describe command should print detailed information
 | 
			
		||||
  kube::test::describe_object_assert pods 'valid-pod' "Name:" "Image(s):" "Host:" "Labels:" "Status:" "Replication Controllers"
 | 
			
		||||
 | 
			
		||||
@@ -524,6 +526,13 @@ __EOF__
 | 
			
		||||
    kube::test::describe_object_assert minions "127.0.0.1" "Name:" "Conditions:" "Addresses:" "Capacity:" "Pods:"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  #####################
 | 
			
		||||
  # Retrieve multiple #
 | 
			
		||||
  #####################
 | 
			
		||||
 | 
			
		||||
  kube::log::status "Testing kubectl(${version}:multiget)"
 | 
			
		||||
  kube::test::get_object_assert 'nodes/127.0.0.1 service/kubernetes' "{{range.items}}{{.$id_field}}:{{end}}" '127.0.0.1:kubernetes:'
 | 
			
		||||
 | 
			
		||||
  kube::test::clear_all
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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{
 | 
			
		||||
		{
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,8 @@ type Builder struct {
 | 
			
		||||
	namespace string
 | 
			
		||||
	names     []string
 | 
			
		||||
 | 
			
		||||
	resourceTuples []resourceTuple
 | 
			
		||||
 | 
			
		||||
	defaultNamespace bool
 | 
			
		||||
	requireNamespace bool
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +62,11 @@ type Builder struct {
 | 
			
		||||
	continueOnError    bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type resourceTuple struct {
 | 
			
		||||
	Resource string
 | 
			
		||||
	Name     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewBuilder creates a builder that operates on generic objects.
 | 
			
		||||
func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper) *Builder {
 | 
			
		||||
	return &Builder{
 | 
			
		||||
@@ -223,6 +230,26 @@ func (b *Builder) SelectAllParam(selectAll bool) *Builder {
 | 
			
		||||
// When two or more arguments are received, they must be a single type and resource name(s).
 | 
			
		||||
// The allowEmptySelector permits to select all the resources (via Everything func).
 | 
			
		||||
func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string) *Builder {
 | 
			
		||||
	if ok, err := hasCombinedTypeArgs(args); ok {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.errs = append(b.errs, err)
 | 
			
		||||
			return b
 | 
			
		||||
		}
 | 
			
		||||
		for _, s := range args {
 | 
			
		||||
			seg := strings.Split(s, "/")
 | 
			
		||||
			if len(seg) != 2 {
 | 
			
		||||
				b.errs = append(b.errs, fmt.Errorf("arguments in resource/name form may not have more than one slash"))
 | 
			
		||||
				return b
 | 
			
		||||
			}
 | 
			
		||||
			resource, name := seg[0], seg[1]
 | 
			
		||||
			if len(resource) == 0 || len(name) == 0 || len(SplitResourceArgument(resource)) != 1 {
 | 
			
		||||
				b.errs = append(b.errs, fmt.Errorf("arguments in resource/name form must have a single resource and name"))
 | 
			
		||||
				return b
 | 
			
		||||
			}
 | 
			
		||||
			b.resourceTuples = append(b.resourceTuples, resourceTuple{Resource: resource, Name: name})
 | 
			
		||||
		}
 | 
			
		||||
		return b
 | 
			
		||||
	}
 | 
			
		||||
	switch {
 | 
			
		||||
	case len(args) > 2:
 | 
			
		||||
		b.names = append(b.names, args[1:]...)
 | 
			
		||||
@@ -242,6 +269,23 @@ func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hasCombinedTypeArgs(args []string) (bool, error) {
 | 
			
		||||
	hasSlash := 0
 | 
			
		||||
	for _, s := range args {
 | 
			
		||||
		if strings.Contains(s, "/") {
 | 
			
		||||
			hasSlash++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	switch {
 | 
			
		||||
	case hasSlash > 0 && hasSlash == len(args):
 | 
			
		||||
		return true, nil
 | 
			
		||||
	case hasSlash > 0 && hasSlash != len(args):
 | 
			
		||||
		return true, fmt.Errorf("when passing arguments in resource/name form, all arguments must include the resource")
 | 
			
		||||
	default:
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResourceTypeAndNameArgs expects two arguments, a resource type, and a resource name. The resource
 | 
			
		||||
// matching that type and and name will be retrieved from the server.
 | 
			
		||||
func (b *Builder) ResourceTypeAndNameArgs(args ...string) *Builder {
 | 
			
		||||
@@ -304,6 +348,31 @@ func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) {
 | 
			
		||||
	return mappings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) {
 | 
			
		||||
	mappings := make(map[string]*meta.RESTMapping)
 | 
			
		||||
	canonical := make(map[string]struct{})
 | 
			
		||||
	for _, r := range b.resourceTuples {
 | 
			
		||||
		if _, ok := mappings[r.Resource]; ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		version, kind, err := b.mapper.VersionAndKindForResource(r.Resource)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		mapping, err := b.mapper.RESTMapping(kind, version)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		mappings[mapping.Resource] = mapping
 | 
			
		||||
		mappings[r.Resource] = mapping
 | 
			
		||||
		canonical[mapping.Resource] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	if len(canonical) > 1 && b.singleResourceType {
 | 
			
		||||
		return nil, fmt.Errorf("you may only specify a single resource type")
 | 
			
		||||
	}
 | 
			
		||||
	return mappings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Builder) visitorResult() *Result {
 | 
			
		||||
	if len(b.errs) > 0 {
 | 
			
		||||
		return &Result{err: errors.NewAggregate(b.errs)}
 | 
			
		||||
@@ -318,6 +387,9 @@ func (b *Builder) visitorResult() *Result {
 | 
			
		||||
		if len(b.names) != 0 {
 | 
			
		||||
			return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")}
 | 
			
		||||
		}
 | 
			
		||||
		if len(b.resourceTuples) != 0 {
 | 
			
		||||
			return &Result{err: fmt.Errorf("selectors and the all flag cannot be used when passing resource/name arguments")}
 | 
			
		||||
		}
 | 
			
		||||
		if len(b.resources) == 0 {
 | 
			
		||||
			return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
 | 
			
		||||
		}
 | 
			
		||||
@@ -352,6 +424,69 @@ func (b *Builder) visitorResult() *Result {
 | 
			
		||||
		return &Result{visitor: VisitorList(visitors), sources: visitors}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// visit items specified by resource and name
 | 
			
		||||
	if len(b.resourceTuples) != 0 {
 | 
			
		||||
		isSingular := len(b.resourceTuples) == 1
 | 
			
		||||
 | 
			
		||||
		if len(b.paths) != 0 {
 | 
			
		||||
			return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
 | 
			
		||||
		}
 | 
			
		||||
		if len(b.resources) != 0 {
 | 
			
		||||
			return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// retrieve one client for each resource
 | 
			
		||||
		mappings, err := b.resourceTupleMappings()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return &Result{singular: isSingular, err: err}
 | 
			
		||||
		}
 | 
			
		||||
		clients := make(map[string]RESTClient)
 | 
			
		||||
		for _, mapping := range mappings {
 | 
			
		||||
			s := fmt.Sprintf("%s/%s", mapping.APIVersion, mapping.Resource)
 | 
			
		||||
			if _, ok := clients[s]; ok {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			client, err := b.mapper.ClientForMapping(mapping)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return &Result{err: err}
 | 
			
		||||
			}
 | 
			
		||||
			clients[s] = client
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		items := []Visitor{}
 | 
			
		||||
		for _, tuple := range b.resourceTuples {
 | 
			
		||||
			mapping, ok := mappings[tuple.Resource]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return &Result{singular: isSingular, err: fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings)}
 | 
			
		||||
			}
 | 
			
		||||
			s := fmt.Sprintf("%s/%s", mapping.APIVersion, mapping.Resource)
 | 
			
		||||
			client, ok := clients[s]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return &Result{singular: isSingular, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			selectorNamespace := b.namespace
 | 
			
		||||
			if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
 | 
			
		||||
				selectorNamespace = ""
 | 
			
		||||
			} else {
 | 
			
		||||
				if len(b.namespace) == 0 {
 | 
			
		||||
					return &Result{singular: isSingular, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			info := NewInfo(client, mapping, selectorNamespace, tuple.Name)
 | 
			
		||||
			items = append(items, info)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var visitors Visitor
 | 
			
		||||
		if b.continueOnError {
 | 
			
		||||
			visitors = EagerVisitorList(items)
 | 
			
		||||
		} else {
 | 
			
		||||
			visitors = VisitorList(items)
 | 
			
		||||
		}
 | 
			
		||||
		return &Result{singular: isSingular, visitor: visitors, sources: items}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// visit items specified by name
 | 
			
		||||
	if len(b.names) != 0 {
 | 
			
		||||
		isSingular := len(b.names) == 1
 | 
			
		||||
@@ -444,7 +579,10 @@ func (b *Builder) Do() *Result {
 | 
			
		||||
	if b.requireNamespace {
 | 
			
		||||
		helpers = append(helpers, RequireNamespace(b.namespace))
 | 
			
		||||
	}
 | 
			
		||||
	helpers = append(helpers, FilterNamespace())
 | 
			
		||||
	helpers = append(helpers, FilterNamespace)
 | 
			
		||||
	if b.latest {
 | 
			
		||||
		helpers = append(helpers, RetrieveLazy)
 | 
			
		||||
	}
 | 
			
		||||
	r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -416,6 +416,88 @@ func TestSingleResourceType(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestResourceTuple(t *testing.T) {
 | 
			
		||||
	expectNoErr := func(err error) bool { return err == nil }
 | 
			
		||||
	expectErr := func(err error) bool { return err != nil }
 | 
			
		||||
	testCases := map[string]struct {
 | 
			
		||||
		args  []string
 | 
			
		||||
		errFn func(error) bool
 | 
			
		||||
	}{
 | 
			
		||||
		"valid": {
 | 
			
		||||
			args:  []string{"pods/foo"},
 | 
			
		||||
			errFn: expectNoErr,
 | 
			
		||||
		},
 | 
			
		||||
		"valid multiple with name indirection": {
 | 
			
		||||
			args:  []string{"pods/foo", "pod/bar"},
 | 
			
		||||
			errFn: expectNoErr,
 | 
			
		||||
		},
 | 
			
		||||
		"valid multiple with namespaced and non-namespaced types": {
 | 
			
		||||
			args:  []string{"minions/foo", "pod/bar"},
 | 
			
		||||
			errFn: expectNoErr,
 | 
			
		||||
		},
 | 
			
		||||
		"mixed arg types": {
 | 
			
		||||
			args:  []string{"pods/foo", "bar"},
 | 
			
		||||
			errFn: expectErr,
 | 
			
		||||
		},
 | 
			
		||||
		/*"missing resource": {
 | 
			
		||||
			args:  []string{"pods/foo2"},
 | 
			
		||||
			errFn: expectNoErr, // not an error because resources are lazily visited
 | 
			
		||||
		},*/
 | 
			
		||||
		"comma in resource": {
 | 
			
		||||
			args:  []string{",pods/foo"},
 | 
			
		||||
			errFn: expectErr,
 | 
			
		||||
		},
 | 
			
		||||
		"multiple types in resource": {
 | 
			
		||||
			args:  []string{"pods,services/foo"},
 | 
			
		||||
			errFn: expectErr,
 | 
			
		||||
		},
 | 
			
		||||
		"unknown resource type": {
 | 
			
		||||
			args:  []string{"unknown/foo"},
 | 
			
		||||
			errFn: expectErr,
 | 
			
		||||
		},
 | 
			
		||||
		"leading slash": {
 | 
			
		||||
			args:  []string{"/bar"},
 | 
			
		||||
			errFn: expectErr,
 | 
			
		||||
		},
 | 
			
		||||
		"trailing slash": {
 | 
			
		||||
			args:  []string{"bar/"},
 | 
			
		||||
			errFn: expectErr,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for k, testCase := range testCases {
 | 
			
		||||
		pods, _ := testData()
 | 
			
		||||
		b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
			
		||||
			"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
 | 
			
		||||
			"/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
 | 
			
		||||
			"/nodes/foo":                runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
 | 
			
		||||
		})).
 | 
			
		||||
			NamespaceParam("test").DefaultNamespace().
 | 
			
		||||
			ResourceTypeOrNameArgs(true, testCase.args...)
 | 
			
		||||
 | 
			
		||||
		r := b.Do()
 | 
			
		||||
 | 
			
		||||
		if !testCase.errFn(r.Err()) {
 | 
			
		||||
			t.Errorf("%s: unexpected error: %v", k, r.Err())
 | 
			
		||||
		}
 | 
			
		||||
		if r.Err() != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		switch {
 | 
			
		||||
		case (r.singular && len(testCase.args) != 1),
 | 
			
		||||
			(!r.singular && len(testCase.args) == 1):
 | 
			
		||||
			t.Errorf("%s: result had unexpected singular value", k)
 | 
			
		||||
		}
 | 
			
		||||
		info, err := r.Infos()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// test error
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if len(info) != len(testCase.args) {
 | 
			
		||||
			t.Errorf("%s: unexpected number of infos returned: %#v", info)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStream(t *testing.T) {
 | 
			
		||||
	r, pods, rc := streamTestData()
 | 
			
		||||
	b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
			
		||||
@@ -619,7 +701,7 @@ func TestLatest(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
			
		||||
	if err != nil || singular || len(test.Infos) != 3 {
 | 
			
		||||
		t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
			
		||||
		t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos)
 | 
			
		||||
	}
 | 
			
		||||
	if !api.Semantic.DeepDerivative([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) {
 | 
			
		||||
		t.Errorf("unexpected visited objects: %#v", test.Objects())
 | 
			
		||||
 
 | 
			
		||||
@@ -130,6 +130,11 @@ func (i *Info) Refresh(obj runtime.Object, ignoreError bool) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Namespaced returns true if the object belongs to a namespace
 | 
			
		||||
func (i *Info) Namespaced() bool {
 | 
			
		||||
	return i.Mapping != nil && i.Mapping.Scope.Name() == meta.RESTScopeNameNamespace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Watch returns server changes to this object after it was retrieved.
 | 
			
		||||
func (i *Info) Watch(resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	return NewHelper(i.Client, i.Mapping).WatchSingle(i.Namespace, i.Name, resourceVersion)
 | 
			
		||||
@@ -418,14 +423,12 @@ func UpdateObjectNamespace(info *Info) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterNamespace omits the namespace if the object is not namespace scoped
 | 
			
		||||
func FilterNamespace() VisitorFunc {
 | 
			
		||||
	return func(info *Info) error {
 | 
			
		||||
		if info.Mapping.Scope.Name() != meta.RESTScopeNameNamespace {
 | 
			
		||||
			info.Namespace = ""
 | 
			
		||||
			UpdateObjectNamespace(info)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
func FilterNamespace(info *Info) error {
 | 
			
		||||
	if !info.Namespaced() {
 | 
			
		||||
		info.Namespace = ""
 | 
			
		||||
		UpdateObjectNamespace(info)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetNamespace ensures that every Info object visited will have a namespace
 | 
			
		||||
@@ -446,6 +449,9 @@ func SetNamespace(namespace string) VisitorFunc {
 | 
			
		||||
// accidentally operating on resources outside their namespace.
 | 
			
		||||
func RequireNamespace(namespace string) VisitorFunc {
 | 
			
		||||
	return func(info *Info) error {
 | 
			
		||||
		if !info.Namespaced() {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if len(info.Namespace) == 0 {
 | 
			
		||||
			info.Namespace = namespace
 | 
			
		||||
			UpdateObjectNamespace(info)
 | 
			
		||||
@@ -461,9 +467,12 @@ func RequireNamespace(namespace string) VisitorFunc {
 | 
			
		||||
// RetrieveLatest updates the Object on each Info by invoking a standard client
 | 
			
		||||
// Get.
 | 
			
		||||
func RetrieveLatest(info *Info) error {
 | 
			
		||||
	if len(info.Name) == 0 || len(info.Namespace) == 0 {
 | 
			
		||||
	if len(info.Name) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if info.Namespaced() && len(info.Namespace) == 0 {
 | 
			
		||||
		return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
 | 
			
		||||
	}
 | 
			
		||||
	obj, err := NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -472,3 +481,11 @@ func RetrieveLatest(info *Info) error {
 | 
			
		||||
	info.ResourceVersion, _ = info.Mapping.MetadataAccessor.ResourceVersion(obj)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RetrieveLazy updates the object if it has not been loaded yet.
 | 
			
		||||
func RetrieveLazy(info *Info) error {
 | 
			
		||||
	if info.Object == nil {
 | 
			
		||||
		return info.Get()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user