Browse code

Resolve api groups in resolveresource

Jordan Liggitt authored on 2016/03/30 03:47:47
Showing 7 changed files
... ...
@@ -119,12 +119,12 @@ func (o *ReconcileClusterRoleBindingsOptions) Complete(cmd *cobra.Command, f *cl
119 119
 
120 120
 	mapper, _ := f.Object()
121 121
 	for _, resourceString := range args {
122
-		resource, name, err := ocmdutil.ResolveResource("clusterroles", resourceString, mapper)
122
+		resource, name, err := ocmdutil.ResolveResource(authorizationapi.Resource("clusterroles"), resourceString, mapper)
123 123
 		if err != nil {
124 124
 			return err
125 125
 		}
126
-		if resource != "clusterroles" {
127
-			return fmt.Errorf("%s is not a valid resource type for this command", resource)
126
+		if resource != authorizationapi.Resource("clusterroles") {
127
+			return fmt.Errorf("%v is not a valid resource type for this command", resource)
128 128
 		}
129 129
 		if len(name) == 0 {
130 130
 			return fmt.Errorf("%s did not contain a name", resourceString)
... ...
@@ -102,12 +102,12 @@ func (o *ReconcileClusterRolesOptions) Complete(cmd *cobra.Command, f *clientcmd
102 102
 
103 103
 	mapper, _ := f.Object()
104 104
 	for _, resourceString := range args {
105
-		resource, name, err := osutil.ResolveResource("clusterroles", resourceString, mapper)
105
+		resource, name, err := osutil.ResolveResource(authorizationapi.Resource("clusterroles"), resourceString, mapper)
106 106
 		if err != nil {
107 107
 			return err
108 108
 		}
109
-		if resource != "clusterroles" {
110
-			return fmt.Errorf("%s is not a valid resource type for this command", resource)
109
+		if resource != authorizationapi.Resource("clusterroles") {
110
+			return fmt.Errorf("%v is not a valid resource type for this command", resource)
111 111
 		}
112 112
 		if len(name) == 0 {
113 113
 			return fmt.Errorf("%s did not contain a name", resourceString)
... ...
@@ -416,12 +416,12 @@ func resolveServiceName(f *clientcmd.Factory, resource string) (string, error) {
416 416
 		return "", fmt.Errorf("you need to provide a service name via --service")
417 417
 	}
418 418
 	mapper, _ := f.Object()
419
-	rType, name, err := cmdutil.ResolveResource("services", resource, mapper)
419
+	rType, name, err := cmdutil.ResolveResource(kapi.Resource("services"), resource, mapper)
420 420
 	if err != nil {
421 421
 		return "", err
422 422
 	}
423
-	if rType != "services" {
424
-		return "", fmt.Errorf("cannot expose %s as routes", rType)
423
+	if rType != kapi.Resource("services") {
424
+		return "", fmt.Errorf("cannot expose %v as routes", rType)
425 425
 	}
426 426
 	return name, nil
427 427
 }
... ...
@@ -142,22 +142,22 @@ func RunStartBuild(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra
142 142
 
143 143
 	var (
144 144
 		name     = buildName
145
-		resource = "builds"
145
+		resource = buildapi.Resource("builds")
146 146
 	)
147 147
 
148 148
 	if len(name) == 0 && len(args) > 0 && len(args[0]) > 0 {
149 149
 		mapper, _ := f.Object()
150
-		resource, name, err = cmdutil.ResolveResource("buildconfigs", args[0], mapper)
150
+		resource, name, err = cmdutil.ResolveResource(buildapi.Resource("buildconfigs"), args[0], mapper)
151 151
 		if err != nil {
152 152
 			return err
153 153
 		}
154 154
 		switch resource {
155
-		case "buildconfigs":
155
+		case buildapi.Resource("buildconfigs"):
156 156
 			// no special handling required
157
-		case "builds":
157
+		case buildapi.Resource("builds"):
158 158
 			return fmt.Errorf("use --from-build to rerun your builds")
159 159
 		default:
160
-			return fmt.Errorf("invalid resource provided: %s", resource)
160
+			return fmt.Errorf("invalid resource provided: %v", resource)
161 161
 		}
162 162
 	}
163 163
 	if len(name) == 0 {
... ...
@@ -214,16 +214,16 @@ func RunStartBuild(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra
214 214
 		if newBuild, err = streamPathToBuild(git, in, cmd.Out(), client.BuildConfigs(namespace), fromDir, fromFile, fromRepo, request); err != nil {
215 215
 			return err
216 216
 		}
217
-	case resource == "builds":
217
+	case resource == buildapi.Resource("builds"):
218 218
 		if newBuild, err = client.Builds(namespace).Clone(request); err != nil {
219 219
 			return err
220 220
 		}
221
-	case resource == "buildconfigs":
221
+	case resource == buildapi.Resource("buildconfigs"):
222 222
 		if newBuild, err = client.BuildConfigs(namespace).Instantiate(request); err != nil {
223 223
 			return err
224 224
 		}
225 225
 	default:
226
-		return fmt.Errorf("invalid resource provided: %s", resource)
226
+		return fmt.Errorf("invalid resource provided: %v", resource)
227 227
 	}
228 228
 
229 229
 	fmt.Fprintln(out, newBuild.Name)
... ...
@@ -291,7 +291,7 @@ func RunStartBuild(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra
291 291
 }
292 292
 
293 293
 // RunListBuildWebHooks prints the webhooks for the provided build config.
294
-func RunListBuildWebHooks(f *clientcmd.Factory, out, errOut io.Writer, name, resource, webhookFilter string) error {
294
+func RunListBuildWebHooks(f *clientcmd.Factory, out, errOut io.Writer, name string, resource unversioned.GroupResource, webhookFilter string) error {
295 295
 	generic, github := false, false
296 296
 	prefix := false
297 297
 	switch webhookFilter {
... ...
@@ -315,9 +315,9 @@ func RunListBuildWebHooks(f *clientcmd.Factory, out, errOut io.Writer, name, res
315 315
 	}
316 316
 
317 317
 	switch resource {
318
-	case "buildconfigs":
318
+	case buildapi.Resource("buildconfigs"):
319 319
 		// no special handling required
320
-	case "builds":
320
+	case buildapi.Resource("builds"):
321 321
 		build, err := client.Builds(namespace).Get(name)
322 322
 		if err != nil {
323 323
 			return err
... ...
@@ -331,7 +331,7 @@ func RunListBuildWebHooks(f *clientcmd.Factory, out, errOut io.Writer, name, res
331 331
 		}
332 332
 		name = ref.Name
333 333
 	default:
334
-		return fmt.Errorf("invalid resource provided: %s", resource)
334
+		return fmt.Errorf("invalid resource provided: %v", resource)
335 335
 	}
336 336
 
337 337
 	config, err := client.BuildConfigs(namespace).Get(name)
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/golang/glog"
9 9
 	"github.com/spf13/cobra"
10 10
 	kapi "k8s.io/kubernetes/pkg/api"
11
+	"k8s.io/kubernetes/pkg/api/unversioned"
11 12
 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
12 13
 	"k8s.io/kubernetes/pkg/util/sets"
13 14
 
... ...
@@ -93,19 +94,19 @@ func (o *BuildChainOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, a
93 93
 	}
94 94
 	o.c, o.t = oc, oc
95 95
 
96
-	resource := ""
96
+	resource := unversioned.GroupResource{}
97 97
 	mapper, _ := f.Object()
98
-	resource, o.name, err = osutil.ResolveResource("imagestreamtags", args[0], mapper)
98
+	resource, o.name, err = osutil.ResolveResource(imageapi.Resource("imagestreamtags"), args[0], mapper)
99 99
 	if err != nil {
100 100
 		return err
101 101
 	}
102 102
 
103 103
 	switch resource {
104
-	case "imagestreamtags":
104
+	case imageapi.Resource("imagestreamtags"):
105 105
 		o.name = imageapi.NormalizeImageStreamTag(o.name)
106 106
 		glog.V(4).Infof("Using %q as the image stream tag to look dependencies for", o.name)
107 107
 	default:
108
-		return fmt.Errorf("invalid resource provided: %s", resource)
108
+		return fmt.Errorf("invalid resource provided: %v", resource)
109 109
 	}
110 110
 
111 111
 	// Setup namespace
... ...
@@ -46,9 +46,9 @@ func GetDisplayFilename(filename string) string {
46 46
 
47 47
 // ResolveResource returns the resource type and name of the resourceString.
48 48
 // If the resource string has no specified type, defaultResource will be returned.
49
-func ResolveResource(defaultResource, resourceString string, mapper meta.RESTMapper) (string, string, error) {
49
+func ResolveResource(defaultResource unversioned.GroupResource, resourceString string, mapper meta.RESTMapper) (unversioned.GroupResource, string, error) {
50 50
 	if mapper == nil {
51
-		return "", "", errors.New("mapper cannot be nil")
51
+		return unversioned.GroupResource{}, "", errors.New("mapper cannot be nil")
52 52
 	}
53 53
 
54 54
 	var name string
... ...
@@ -57,25 +57,20 @@ func ResolveResource(defaultResource, resourceString string, mapper meta.RESTMap
57 57
 	case 1:
58 58
 		name = parts[0]
59 59
 	case 2:
60
-		partialResource := unversioned.GroupVersionResource{Resource: strings.ToLower(parts[0])}
61
-		gvrs, err := mapper.ResourcesFor(partialResource)
62
-		if err != nil {
63
-			return "", "", err
64
-		}
65
-		if len(gvrs) == 0 {
66
-			return gvrs[0].Resource, parts[1], nil
67
-		}
60
+		name = parts[1]
68 61
 
69
-		groupResource := gvrs[0].GroupResource()
70
-		for _, gvr := range gvrs[1:] {
71
-			if groupResource != gvr.GroupResource() {
72
-				return "", "", &meta.AmbiguousResourceError{PartialResource: partialResource, MatchingResources: gvrs}
73
-			}
74
-		}
62
+		// Allow specifying the group the same way kubectl does, as "resource.group.name"
63
+		groupResource := unversioned.ParseGroupResource(parts[0])
64
+		// normalize resource case
65
+		groupResource.Resource = strings.ToLower(groupResource.Resource)
75 66
 
76
-		return gvrs[0].Resource, parts[1], nil
67
+		gvr, err := mapper.ResourceFor(groupResource.WithVersion(""))
68
+		if err != nil {
69
+			return unversioned.GroupResource{}, "", err
70
+		}
71
+		return gvr.GroupResource(), name, nil
77 72
 	default:
78
-		return "", "", fmt.Errorf("invalid resource format: %s", resourceString)
73
+		return unversioned.GroupResource{}, "", fmt.Errorf("invalid resource format: %s", resourceString)
79 74
 	}
80 75
 
81 76
 	return defaultResource, name, nil
... ...
@@ -1,8 +1,10 @@
1 1
 package util_test
2 2
 
3 3
 import (
4
+	"reflect"
4 5
 	"testing"
5 6
 
7
+	"k8s.io/kubernetes/pkg/api/unversioned"
6 8
 	"k8s.io/kubernetes/pkg/apimachinery/registered"
7 9
 	"k8s.io/kubernetes/pkg/kubectl"
8 10
 
... ...
@@ -16,148 +18,197 @@ func TestResolveResource(t *testing.T) {
16 16
 
17 17
 	tests := []struct {
18 18
 		name             string
19
-		defaultResource  string
19
+		defaultResource  unversioned.GroupResource
20 20
 		resourceString   string
21
-		expectedResource string
21
+		expectedResource unversioned.GroupResource
22 22
 		expectedName     string
23 23
 		expectedErr      bool
24 24
 	}{
25 25
 		{
26 26
 			name:             "invalid case #1",
27
-			defaultResource:  "",
27
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
28 28
 			resourceString:   "a/b/c",
29
-			expectedResource: "",
29
+			expectedResource: unversioned.GroupResource{},
30 30
 			expectedName:     "",
31 31
 			expectedErr:      true,
32 32
 		},
33 33
 		{
34 34
 			name:             "invalid case #2",
35
-			defaultResource:  "",
35
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
36 36
 			resourceString:   "foo/bar",
37
-			expectedResource: "",
37
+			expectedResource: unversioned.GroupResource{},
38 38
 			expectedName:     "",
39 39
 			expectedErr:      true,
40 40
 		},
41 41
 		{
42 42
 			name:             "empty resource string case #1",
43
-			defaultResource:  "",
43
+			defaultResource:  unversioned.GroupResource{Resource: ""},
44 44
 			resourceString:   "",
45
-			expectedResource: "",
45
+			expectedResource: unversioned.GroupResource{Resource: ""},
46 46
 			expectedName:     "",
47 47
 			expectedErr:      false,
48 48
 		},
49 49
 		{
50 50
 			name:             "empty resource string case #2",
51
-			defaultResource:  "",
51
+			defaultResource:  unversioned.GroupResource{Resource: ""},
52 52
 			resourceString:   "bar",
53
-			expectedResource: "",
53
+			expectedResource: unversioned.GroupResource{Resource: ""},
54 54
 			expectedName:     "bar",
55 55
 			expectedErr:      false,
56 56
 		},
57 57
 		{
58 58
 			name:             "empty resource string case #3",
59
-			defaultResource:  "foo",
59
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
60 60
 			resourceString:   "bar",
61
-			expectedResource: "foo",
61
+			expectedResource: unversioned.GroupResource{Resource: "foo"},
62 62
 			expectedName:     "bar",
63 63
 			expectedErr:      false,
64 64
 		},
65 65
 		{
66 66
 			name:             "(KUBE) short name",
67
-			defaultResource:  "foo",
67
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
68 68
 			resourceString:   "rc/bar",
69
-			expectedResource: "replicationcontrollers",
69
+			expectedResource: unversioned.GroupResource{Resource: "replicationcontrollers"},
70 70
 			expectedName:     "bar",
71 71
 			expectedErr:      false,
72 72
 		},
73 73
 		{
74 74
 			name:             "(KUBE) long name, case insensitive #1",
75
-			defaultResource:  "foo",
75
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
76 76
 			resourceString:   "replicationcontroller/bar",
77
-			expectedResource: "replicationcontrollers",
77
+			expectedResource: unversioned.GroupResource{Resource: "replicationcontrollers"},
78 78
 			expectedName:     "bar",
79 79
 			expectedErr:      false,
80 80
 		},
81 81
 		{
82 82
 			name:             "(KUBE) long name, case insensitive #2",
83
-			defaultResource:  "foo",
83
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
84 84
 			resourceString:   "replicationcontrollers/bar",
85
-			expectedResource: "replicationcontrollers",
85
+			expectedResource: unversioned.GroupResource{Resource: "replicationcontrollers"},
86 86
 			expectedName:     "bar",
87 87
 			expectedErr:      false,
88 88
 		},
89 89
 		{
90 90
 			name:             "(KUBE) long name, case insensitive #3",
91
-			defaultResource:  "foo",
91
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
92 92
 			resourceString:   "ReplicationControllers/bar",
93
-			expectedResource: "replicationcontrollers",
93
+			expectedResource: unversioned.GroupResource{Resource: "replicationcontrollers"},
94 94
 			expectedName:     "bar",
95 95
 			expectedErr:      false,
96 96
 		},
97 97
 		{
98 98
 			name:             "(KUBE) long name, case insensitive #4",
99
-			defaultResource:  "foo",
99
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
100 100
 			resourceString:   "ReplicationControllers/bar",
101
-			expectedResource: "replicationcontrollers",
101
+			expectedResource: unversioned.GroupResource{Resource: "replicationcontrollers"},
102 102
 			expectedName:     "bar",
103 103
 			expectedErr:      false,
104 104
 		},
105 105
 		{
106 106
 			name:             "(KUBE) long name, case insensitive #5",
107
-			defaultResource:  "foo",
107
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
108 108
 			resourceString:   "ReplicationControllers/Bar",
109
-			expectedResource: "replicationcontrollers",
109
+			expectedResource: unversioned.GroupResource{Resource: "replicationcontrollers"},
110 110
 			expectedName:     "Bar",
111 111
 			expectedErr:      false,
112 112
 		},
113 113
 		{
114 114
 			name:             "(ORIGIN) short name",
115
-			defaultResource:  "foo",
115
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
116 116
 			resourceString:   "bc/bar",
117
-			expectedResource: "buildconfigs",
117
+			expectedResource: unversioned.GroupResource{Resource: "buildconfigs"},
118 118
 			expectedName:     "bar",
119 119
 			expectedErr:      false,
120 120
 		},
121 121
 		{
122 122
 			name:             "(ORIGIN) long name, case insensitive #1",
123
-			defaultResource:  "foo",
123
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
124 124
 			resourceString:   "buildconfig/bar",
125
-			expectedResource: "buildconfigs",
125
+			expectedResource: unversioned.GroupResource{Resource: "buildconfigs"},
126 126
 			expectedName:     "bar",
127 127
 			expectedErr:      false,
128 128
 		},
129 129
 		{
130 130
 			name:             "(ORIGIN) long name, case insensitive #2",
131
-			defaultResource:  "foo",
131
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
132 132
 			resourceString:   "buildconfigs/bar",
133
-			expectedResource: "buildconfigs",
133
+			expectedResource: unversioned.GroupResource{Resource: "buildconfigs"},
134 134
 			expectedName:     "bar",
135 135
 			expectedErr:      false,
136 136
 		},
137 137
 		{
138 138
 			name:             "(ORIGIN) long name, case insensitive #3",
139
-			defaultResource:  "foo",
139
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
140 140
 			resourceString:   "BuildConfigs/bar",
141
-			expectedResource: "buildconfigs",
141
+			expectedResource: unversioned.GroupResource{Resource: "buildconfigs"},
142 142
 			expectedName:     "bar",
143 143
 			expectedErr:      false,
144 144
 		},
145 145
 		{
146 146
 			name:             "(ORIGIN) long name, case insensitive #4",
147
-			defaultResource:  "foo",
147
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
148 148
 			resourceString:   "BuildConfigs/bar",
149
-			expectedResource: "buildconfigs",
149
+			expectedResource: unversioned.GroupResource{Resource: "buildconfigs"},
150 150
 			expectedName:     "bar",
151 151
 			expectedErr:      false,
152 152
 		},
153 153
 		{
154 154
 			name:             "(ORIGIN) long name, case insensitive #5",
155
-			defaultResource:  "foo",
155
+			defaultResource:  unversioned.GroupResource{Resource: "foo"},
156 156
 			resourceString:   "BuildConfigs/Bar",
157
-			expectedResource: "buildconfigs",
157
+			expectedResource: unversioned.GroupResource{Resource: "buildconfigs"},
158 158
 			expectedName:     "Bar",
159 159
 			expectedErr:      false,
160 160
 		},
161
+
162
+		{
163
+			name:             "singular, implicit api group",
164
+			defaultResource:  unversioned.GroupResource{},
165
+			resourceString:   "job/myjob",
166
+			expectedResource: unversioned.GroupResource{Group: "extensions", Resource: "jobs"},
167
+			expectedName:     "myjob",
168
+			expectedErr:      false,
169
+		},
170
+		{
171
+			name:             "singular, explicit extensions api group",
172
+			defaultResource:  unversioned.GroupResource{},
173
+			resourceString:   "job.extensions/myjob",
174
+			expectedResource: unversioned.GroupResource{Group: "extensions", Resource: "jobs"},
175
+			expectedName:     "myjob",
176
+			expectedErr:      false,
177
+		},
178
+		{
179
+			name:             "singular, explicit batch api group",
180
+			defaultResource:  unversioned.GroupResource{},
181
+			resourceString:   "job.batch/myjob",
182
+			expectedResource: unversioned.GroupResource{Group: "batch", Resource: "jobs"},
183
+			expectedName:     "myjob",
184
+			expectedErr:      false,
185
+		},
186
+		{
187
+			name:             "shortname, implicit api group",
188
+			defaultResource:  unversioned.GroupResource{},
189
+			resourceString:   "hpa/myhpa",
190
+			expectedResource: unversioned.GroupResource{Group: "extensions", Resource: "horizontalpodautoscalers"},
191
+			expectedName:     "myhpa",
192
+			expectedErr:      false,
193
+		},
194
+		{
195
+			name:             "shortname, explicit extensions api group",
196
+			defaultResource:  unversioned.GroupResource{},
197
+			resourceString:   "hpa.extensions/myhpa",
198
+			expectedResource: unversioned.GroupResource{Group: "extensions", Resource: "horizontalpodautoscalers"},
199
+			expectedName:     "myhpa",
200
+			expectedErr:      false,
201
+		},
202
+		{
203
+			name:             "shortname, explicit autoscaling api group",
204
+			defaultResource:  unversioned.GroupResource{},
205
+			resourceString:   "hpa.autoscaling/myhpa",
206
+			expectedResource: unversioned.GroupResource{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
207
+			expectedName:     "myhpa",
208
+			expectedErr:      false,
209
+		},
161 210
 	}
162 211
 
163 212
 	for _, test := range tests {
... ...
@@ -170,8 +221,8 @@ func TestResolveResource(t *testing.T) {
170 170
 			t.Errorf("%s: expected error but got none", test.name)
171 171
 			continue
172 172
 		}
173
-		if gotResource != test.expectedResource {
174
-			t.Errorf("%s: expected resource type %s, got %s", test.name, test.expectedResource, gotResource)
173
+		if !reflect.DeepEqual(gotResource, test.expectedResource) {
174
+			t.Errorf("%s: expected resource type %#v, got %#v", test.name, test.expectedResource, gotResource)
175 175
 			continue
176 176
 		}
177 177
 		if gotName != test.expectedName {