Merge pull request #36909 from sttts/sttts-discovery-with-verbs

Automatic merge from submit-queue (batch tested with PRs 37370, 37003, 36909)

Add verbs to APIResourceInfo for discovery

Verbs will be used by generic controllers (gc, namespace) to avoid unnecessary API calls, reducing load on the apiserver. E.g. not all objects can be deleted.

Example:
```json
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "batch/v1",
  "resources": [
    {
      "name": "jobs",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "create",
        "delete",
        "deletecollection",
        "get",
        "list",
        "update",
        "watch"
      ]
    },
    {
      "name": "jobs/status",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "create",
        "get"
      ]
    }
  ]
}
```
pull/6/head
Kubernetes Submit Queue 2016-12-05 06:48:41 -08:00 committed by GitHub
commit 45a436ac24
63 changed files with 1402 additions and 406 deletions

View File

@ -32308,7 +32308,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"kind": {
@ -32322,6 +32323,13 @@
"namespaced": {
"description": "namespaced indicates if a resource is namespaced or not.",
"type": "boolean"
},
"verbs": {
"description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)",
"type": "array",
"items": {
"type": "string"
}
}
}
},

View File

@ -2885,7 +2885,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -2899,6 +2900,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -300,7 +300,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -314,6 +315,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -513,7 +513,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -527,6 +528,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -1445,7 +1445,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -1459,6 +1460,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -2865,7 +2865,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -2879,6 +2880,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -68,7 +68,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -82,6 +83,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -1133,7 +1133,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -1147,6 +1148,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -10737,7 +10737,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -10751,6 +10752,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -1455,7 +1455,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -1469,6 +1470,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -3363,7 +3363,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -3377,6 +3378,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -968,7 +968,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -982,6 +983,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -19982,7 +19982,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"name": {
@ -19996,6 +19997,12 @@
"kind": {
"type": "string",
"description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')"
},
"verbs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -24,6 +24,8 @@ go_library(
"//pkg/apimachinery/registered:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/componentconfig:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/client/clientset_generated/release_1_5:go_default_library",
"//pkg/client/clientset_generated/release_1_5/typed/core/v1:go_default_library",
"//pkg/client/leaderelection:go_default_library",

View File

@ -36,6 +36,8 @@ import (
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
v1core "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/core/v1"
"k8s.io/kubernetes/pkg/client/leaderelection"
@ -393,29 +395,26 @@ func StartControllers(s *options.CMServer, rootClientBuilder, clientBuilder cont
namespaceKubeClient := clientBuilder.ClientOrDie("namespace-controller")
namespaceClientPool := dynamic.NewClientPool(rootClientBuilder.ConfigOrDie("namespace-controller"), restMapper, dynamic.LegacyAPIPathResolverFunc)
// TODO: consider using a list-watch + cache here rather than polling
var gvrFn func() ([]schema.GroupVersionResource, error)
rsrcs, err := namespaceKubeClient.Discovery().ServerResources()
resources, err := namespaceKubeClient.Discovery().ServerResources()
if err != nil {
return fmt.Errorf("failed to get group version resources: %v", err)
return fmt.Errorf("failed to get preferred server resources: %v", err)
}
for _, rsrcList := range rsrcs {
for ix := range rsrcList.APIResources {
rsrc := &rsrcList.APIResources[ix]
if rsrc.Kind == "ThirdPartyResource" {
gvrFn = namespaceKubeClient.Discovery().ServerPreferredNamespacedResources
}
}
gvrs, err := discovery.GroupVersionResources(resources)
if err != nil {
return fmt.Errorf("failed to parse preferred server resources: %v", err)
}
if gvrFn == nil {
gvr, err := namespaceKubeClient.Discovery().ServerPreferredNamespacedResources()
discoverResourcesFn := namespaceKubeClient.Discovery().ServerPreferredNamespacedResources
if _, found := gvrs[extensions.SchemeGroupVersion.WithResource("thirdpartyresource")]; found {
// make discovery static
snapshot, err := discoverResourcesFn()
if err != nil {
return fmt.Errorf("failed to get resources: %v", err)
return fmt.Errorf("failed to get server resources: %v", err)
}
gvrFn = func() ([]schema.GroupVersionResource, error) {
return gvr, nil
discoverResourcesFn = func() ([]*metav1.APIResourceList, error) {
return snapshot, nil
}
}
namespaceController := namespacecontroller.NewNamespaceController(namespaceKubeClient, namespaceClientPool, gvrFn, s.NamespaceSyncPeriod.Duration, v1.FinalizerKubernetes)
namespaceController := namespacecontroller.NewNamespaceController(namespaceKubeClient, namespaceClientPool, discoverResourcesFn, s.NamespaceSyncPeriod.Duration, v1.FinalizerKubernetes)
go namespaceController.Run(int(s.ConcurrentNamespaceSyncs), stop)
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))
@ -548,17 +547,22 @@ func StartControllers(s *options.CMServer, rootClientBuilder, clientBuilder cont
if s.EnableGarbageCollector {
gcClientset := clientBuilder.ClientOrDie("generic-garbage-collector")
groupVersionResources, err := gcClientset.Discovery().ServerPreferredResources()
preferredResources, err := gcClientset.Discovery().ServerPreferredResources()
if err != nil {
return fmt.Errorf("failed to get supported resources from server: %v", err)
}
deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, preferredResources)
deletableGroupVersionResources, err := discovery.GroupVersionResources(deletableResources)
if err != nil {
glog.Errorf("Failed to parse resources from server: %v", err)
}
config := rootClientBuilder.ConfigOrDie("generic-garbage-collector")
config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()}
metaOnlyClientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc)
config.ContentConfig = dynamic.ContentConfig()
clientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc)
garbageCollector, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, restMapper, groupVersionResources)
garbageCollector, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, restMapper, deletableGroupVersionResources)
if err != nil {
glog.Errorf("Failed to start the generic garbage collector: %v", err)
} else {

View File

@ -2130,6 +2130,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -4444,7 +4451,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:06:41 UTC
Last updated 2016-12-05 09:52:09 UTC
</div>
</div>
</body>

View File

@ -885,6 +885,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -900,7 +907,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:06:49 UTC
Last updated 2016-12-05 09:52:16 UTC
</div>
</div>
</body>

View File

@ -754,6 +754,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -1161,7 +1168,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:06:55 UTC
Last updated 2016-12-05 09:52:22 UTC
</div>
</div>
</body>

View File

@ -1297,6 +1297,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -1368,7 +1375,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:01 UTC
Last updated 2016-12-05 09:52:30 UTC
</div>
</div>
</body>

View File

@ -2041,6 +2041,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -4376,7 +4383,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:08 UTC
Last updated 2016-12-05 09:52:37 UTC
</div>
</div>
</body>

View File

@ -472,6 +472,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -487,7 +494,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:16 UTC
Last updated 2016-12-05 09:52:43 UTC
</div>
</div>
</body>

View File

@ -1274,6 +1274,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -1345,7 +1352,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:23 UTC
Last updated 2016-12-05 09:52:51 UTC
</div>
</div>
</body>

View File

@ -5731,6 +5731,13 @@ Both these may change in the future. Incoming requests are matched against the h
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -6818,7 +6825,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:30 UTC
Last updated 2016-12-05 09:52:58 UTC
</div>
</div>
</body>

View File

@ -1290,6 +1290,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -1402,7 +1409,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:37 UTC
Last updated 2016-12-05 09:53:07 UTC
</div>
</div>
</body>

View File

@ -1487,6 +1487,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -1730,7 +1737,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:43 UTC
Last updated 2016-12-05 09:53:16 UTC
</div>
</div>
</body>

View File

@ -1135,6 +1135,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -1206,7 +1213,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:07:50 UTC
Last updated 2016-12-05 09:53:24 UTC
</div>
</div>
</body>

View File

@ -6719,6 +6719,13 @@ Examples:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">verbs</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -8613,7 +8620,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-12-03 22:06:34 UTC
Last updated 2016-12-05 09:52:00 UTC
</div>
</div>
</body>

View File

@ -8555,7 +8555,8 @@
"required": [
"name",
"namespaced",
"kind"
"kind",
"verbs"
],
"properties": {
"kind": {
@ -8569,6 +8570,13 @@
"namespaced": {
"description": "namespaced indicates if a resource is namespaced or not.",
"type": "boolean"
},
"verbs": {
"description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)",
"type": "array",
"items": {
"type": "string"
}
}
}
},

View File

@ -38,6 +38,7 @@ go_library(
"//vendor:github.com/gogo/protobuf/proto",
"//vendor:github.com/gogo/protobuf/sortkeys",
"//vendor:github.com/google/gofuzz",
"//vendor:github.com/ugorji/go/codec",
],
)
@ -48,6 +49,7 @@ go_test(
"group_version_test.go",
"helpers_test.go",
"time_test.go",
"types_test.go",
],
library = "go_default_library",
tags = ["automanaged"],

View File

@ -50,6 +50,7 @@ limitations under the License.
Time
Timestamp
TypeMeta
Verbs
*/
package v1
@ -180,6 +181,10 @@ func (m *TypeMeta) Reset() { *m = TypeMeta{} }
func (*TypeMeta) ProtoMessage() {}
func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} }
func (m *Verbs) Reset() { *m = Verbs{} }
func (*Verbs) ProtoMessage() {}
func (*Verbs) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} }
func init() {
proto.RegisterType((*APIGroup)(nil), "k8s.io.kubernetes.pkg.apis.meta.v1.APIGroup")
proto.RegisterType((*APIGroupList)(nil), "k8s.io.kubernetes.pkg.apis.meta.v1.APIGroupList")
@ -206,6 +211,7 @@ func init() {
proto.RegisterType((*Time)(nil), "k8s.io.kubernetes.pkg.apis.meta.v1.Time")
proto.RegisterType((*Timestamp)(nil), "k8s.io.kubernetes.pkg.apis.meta.v1.Timestamp")
proto.RegisterType((*TypeMeta)(nil), "k8s.io.kubernetes.pkg.apis.meta.v1.TypeMeta")
proto.RegisterType((*Verbs)(nil), "k8s.io.kubernetes.pkg.apis.meta.v1.Verbs")
}
func (m *APIGroup) Marshal() (data []byte, err error) {
size := m.Size()
@ -322,6 +328,16 @@ func (m *APIResource) MarshalTo(data []byte) (int, error) {
i++
i = encodeVarintGenerated(data, i, uint64(len(m.Kind)))
i += copy(data[i:], m.Kind)
if m.Verbs != nil {
data[i] = 0x22
i++
i = encodeVarintGenerated(data, i, uint64(m.Verbs.Size()))
n2, err := m.Verbs.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n2
}
return i, nil
}
@ -836,11 +852,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) {
data[i] = 0xa
i++
i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size()))
n2, err := m.ListMeta.MarshalTo(data[i:])
n3, err := m.ListMeta.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n2
i += n3
data[i] = 0x12
i++
i = encodeVarintGenerated(data, i, uint64(len(m.Status)))
@ -857,11 +873,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) {
data[i] = 0x2a
i++
i = encodeVarintGenerated(data, i, uint64(m.Details.Size()))
n3, err := m.Details.MarshalTo(data[i:])
n4, err := m.Details.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n3
i += n4
}
data[i] = 0x30
i++
@ -994,6 +1010,39 @@ func (m *TypeMeta) MarshalTo(data []byte) (int, error) {
return i, nil
}
func (m Verbs) Marshal() (data []byte, err error) {
size := m.Size()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m Verbs) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m) > 0 {
for _, s := range m {
data[i] = 0xa
i++
l = len(s)
for l >= 1<<7 {
data[i] = uint8(uint64(l)&0x7f | 0x80)
l >>= 7
i++
}
data[i] = uint8(l)
i++
i += copy(data[i:], s)
}
}
return i, nil
}
func encodeFixed64Generated(data []byte, offset int, v uint64) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
@ -1063,6 +1112,10 @@ func (m *APIResource) Size() (n int) {
n += 2
l = len(m.Kind)
n += 1 + l + sovGenerated(uint64(l))
if m.Verbs != nil {
l = m.Verbs.Size()
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -1321,6 +1374,18 @@ func (m *TypeMeta) Size() (n int) {
return n
}
func (m Verbs) Size() (n int) {
var l int
_ = l
if len(m) > 0 {
for _, s := range m {
l = len(s)
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
func sovGenerated(x uint64) (n int) {
for {
n++
@ -1365,6 +1430,7 @@ func (this *APIResource) String() string {
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`Namespaced:` + fmt.Sprintf("%v", this.Namespaced) + `,`,
`Kind:` + fmt.Sprintf("%v", this.Kind) + `,`,
`Verbs:` + strings.Replace(fmt.Sprintf("%v", this.Verbs), "Verbs", "Verbs", 1) + `,`,
`}`,
}, "")
return s
@ -1917,6 +1983,39 @@ func (m *APIResource) Unmarshal(data []byte) error {
}
m.Kind = string(data[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Verbs", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Verbs == nil {
m.Verbs = Verbs{}
}
if err := m.Verbs.Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(data[iNdEx:])
@ -4465,6 +4564,85 @@ func (m *TypeMeta) Unmarshal(data []byte) error {
}
return nil
}
func (m *Verbs) Unmarshal(data []byte) error {
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Verbs: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Verbs: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
*m = append(*m, string(data[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipGenerated(data []byte) (n int, err error) {
l := len(data)
iNdEx := 0
@ -4571,94 +4749,97 @@ var (
)
var fileDescriptorGenerated = []byte{
// 1416 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x57, 0x4f, 0x6f, 0xdb, 0xc6,
0x12, 0x17, 0x25, 0x4b, 0x91, 0x46, 0xd6, 0xb3, 0xc3, 0xe7, 0xe0, 0x31, 0x06, 0x9e, 0xa4, 0xc7,
0x3c, 0x14, 0x0e, 0x90, 0x48, 0xb0, 0x51, 0x14, 0x41, 0xda, 0x14, 0x30, 0x6d, 0x27, 0x30, 0x12,
0x27, 0xc6, 0x3a, 0x48, 0x8b, 0x34, 0x87, 0xd2, 0xe4, 0x5a, 0x66, 0x25, 0x91, 0xec, 0xee, 0x4a,
0x88, 0xd0, 0x43, 0x83, 0x02, 0x05, 0x7a, 0x28, 0x8a, 0x1c, 0x8b, 0x1e, 0x8a, 0x18, 0xe8, 0x07,
0xe8, 0x57, 0xe8, 0x2d, 0xb7, 0xe6, 0xd8, 0x43, 0x61, 0x34, 0x2e, 0x7a, 0xea, 0x37, 0xf0, 0xa9,
0xd8, 0xe5, 0x2e, 0x45, 0xca, 0x51, 0x4c, 0x23, 0x3d, 0xf4, 0x24, 0xee, 0xfc, 0xf9, 0xcd, 0x6f,
0x67, 0x66, 0x67, 0x57, 0xb0, 0xd2, 0xbd, 0x46, 0x5b, 0x5e, 0xd0, 0xee, 0x0e, 0x76, 0x31, 0xf1,
0x31, 0xc3, 0xb4, 0x1d, 0x76, 0x3b, 0x6d, 0x3b, 0xf4, 0x68, 0xbb, 0x8f, 0x99, 0xdd, 0x1e, 0x2e,
0xb7, 0x3b, 0xd8, 0xc7, 0xc4, 0x66, 0xd8, 0x6d, 0x85, 0x24, 0x60, 0x81, 0x6e, 0x46, 0x3e, 0xad,
0xb1, 0x4f, 0x2b, 0xec, 0x76, 0x5a, 0xdc, 0xa7, 0xc5, 0x7d, 0x5a, 0xc3, 0xe5, 0xc5, 0xab, 0x1d,
0x8f, 0xed, 0x0f, 0x76, 0x5b, 0x4e, 0xd0, 0x6f, 0x77, 0x82, 0x4e, 0xd0, 0x16, 0xae, 0xbb, 0x83,
0x3d, 0xb1, 0x12, 0x0b, 0xf1, 0x15, 0x41, 0x2e, 0x5e, 0x7d, 0x35, 0x0d, 0x32, 0xf0, 0x99, 0xd7,
0xc7, 0x93, 0x0c, 0x16, 0xdf, 0x7e, 0xbd, 0x39, 0x75, 0xf6, 0x71, 0xdf, 0x3e, 0xe1, 0xb5, 0xfc,
0x6a, 0xaf, 0x01, 0xf3, 0x7a, 0x6d, 0xcf, 0x67, 0x94, 0x91, 0x49, 0x17, 0xf3, 0xa7, 0x02, 0x94,
0x57, 0xb7, 0x37, 0x6f, 0x91, 0x60, 0x10, 0xea, 0x4d, 0x98, 0xf1, 0xed, 0x3e, 0x36, 0xb4, 0xa6,
0xb6, 0x54, 0xb1, 0x66, 0x9f, 0x1f, 0x36, 0x72, 0x47, 0x87, 0x8d, 0x99, 0xbb, 0x76, 0x1f, 0x23,
0xa1, 0xd1, 0x3f, 0x81, 0xf2, 0x10, 0x13, 0xea, 0x05, 0x3e, 0x35, 0xf2, 0xcd, 0xc2, 0x52, 0x75,
0xe5, 0xbd, 0xd6, 0xe9, 0xc9, 0x6a, 0x09, 0xf8, 0x07, 0x91, 0xe3, 0xcd, 0x80, 0xac, 0x7b, 0xd4,
0x09, 0x86, 0x98, 0x8c, 0xac, 0x79, 0x19, 0xa3, 0x2c, 0x95, 0x14, 0xc5, 0xf8, 0xfa, 0x17, 0x1a,
0xcc, 0x87, 0x04, 0xef, 0x61, 0x42, 0xb0, 0x2b, 0xf5, 0x46, 0xa1, 0xa9, 0xbd, 0x71, 0x50, 0x43,
0x06, 0x9d, 0xdf, 0x9e, 0x40, 0x47, 0x27, 0xe2, 0xe9, 0x07, 0x1a, 0x2c, 0x52, 0x4c, 0x86, 0x98,
0xac, 0xba, 0x2e, 0xc1, 0x94, 0x5a, 0xa3, 0xb5, 0x9e, 0x87, 0x7d, 0xb6, 0xb6, 0xb9, 0x8e, 0xa8,
0x31, 0x23, 0x72, 0x70, 0x23, 0x0b, 0x9d, 0x9d, 0x69, 0x28, 0x96, 0x29, 0xf9, 0x2c, 0x4e, 0x35,
0xa1, 0xe8, 0x35, 0x24, 0x4c, 0x17, 0x66, 0x55, 0x09, 0xef, 0x78, 0x94, 0xe9, 0xf7, 0xa1, 0xd4,
0xe1, 0x0b, 0x6a, 0x68, 0x82, 0xde, 0x95, 0x2c, 0xf4, 0x14, 0x82, 0xf5, 0x2f, 0xc9, 0xa6, 0x24,
0x96, 0x14, 0x49, 0x2c, 0xf3, 0x4b, 0x0d, 0xaa, 0xab, 0xdb, 0x9b, 0x08, 0xd3, 0x60, 0x40, 0x1c,
0x9c, 0xa1, 0x59, 0x56, 0x00, 0xf8, 0x2f, 0x0d, 0x6d, 0x07, 0xbb, 0x46, 0xbe, 0xa9, 0x2d, 0x95,
0x2d, 0x5d, 0xda, 0xc1, 0xdd, 0x58, 0x83, 0x12, 0x56, 0x1c, 0xb5, 0xeb, 0xf9, 0xae, 0xa8, 0x73,
0x02, 0xf5, 0xb6, 0xe7, 0xbb, 0x48, 0x68, 0xcc, 0x1f, 0x35, 0x98, 0x4b, 0xf0, 0x10, 0x3b, 0xbe,
0x06, 0xb3, 0x9d, 0x44, 0xb5, 0x25, 0xa7, 0x05, 0xe9, 0x3d, 0x9b, 0xec, 0x04, 0x94, 0xb2, 0xd4,
0x1d, 0xa8, 0x10, 0x89, 0xa4, 0x3a, 0xba, 0x9d, 0x31, 0x5d, 0x8a, 0xc1, 0x38, 0x4e, 0x42, 0x48,
0xd1, 0x18, 0xd7, 0xfc, 0x23, 0x4a, 0x9d, 0xea, 0x71, 0x7d, 0x29, 0x71, 0x8a, 0x78, 0x89, 0x2a,
0xd6, 0xec, 0x94, 0x33, 0x70, 0x4a, 0xfb, 0xe5, 0xff, 0x01, 0xed, 0x77, 0xbd, 0xfc, 0xed, 0xb3,
0x46, 0xee, 0xc9, 0xaf, 0xcd, 0x9c, 0xb9, 0x09, 0xe5, 0xf5, 0x01, 0xb1, 0x19, 0x4f, 0xec, 0x0d,
0x28, 0xbb, 0xf2, 0x5b, 0x94, 0xa3, 0x60, 0xfd, 0x4f, 0x9d, 0x75, 0x65, 0x73, 0x7c, 0xd8, 0xa8,
0xf1, 0x71, 0xd6, 0x52, 0x02, 0x14, 0xbb, 0x98, 0x8f, 0xa0, 0xb6, 0xf1, 0x38, 0x0c, 0x08, 0xbb,
0x17, 0x32, 0x91, 0x89, 0xb7, 0xa0, 0x84, 0x85, 0x40, 0xa0, 0x95, 0xc7, 0x6d, 0x1a, 0x99, 0x21,
0xa9, 0xd5, 0x2f, 0x41, 0x11, 0x3f, 0xb6, 0x1d, 0x26, 0xfb, 0xad, 0x26, 0xcd, 0x8a, 0x1b, 0x5c,
0x88, 0x22, 0x9d, 0x79, 0x0f, 0xe0, 0x16, 0x8e, 0xa1, 0x57, 0x61, 0x4e, 0xd5, 0x2a, 0xdd, 0x40,
0xff, 0x91, 0xce, 0x73, 0x28, 0xad, 0x46, 0x93, 0xf6, 0xe6, 0x23, 0xa8, 0x88, 0x26, 0xe3, 0x7d,
0xca, 0x29, 0x88, 0x1e, 0x93, 0x28, 0x31, 0x05, 0x61, 0x81, 0x22, 0x5d, 0xdc, 0xe8, 0xf9, 0x69,
0x8d, 0x9e, 0xc8, 0x6b, 0x0f, 0x6a, 0x91, 0xaf, 0x3a, 0x7b, 0x99, 0x22, 0x5c, 0x81, 0xb2, 0xa2,
0x29, 0xa3, 0xc4, 0xd3, 0x56, 0x01, 0xa1, 0xd8, 0x22, 0x11, 0x6d, 0x1f, 0x52, 0x07, 0x26, 0x5b,
0xb0, 0xcb, 0x70, 0x4e, 0x36, 0xad, 0x8c, 0x35, 0x27, 0xcd, 0xce, 0xa9, 0x9c, 0x29, 0x7d, 0x22,
0xd2, 0xe7, 0x60, 0x4c, 0x1b, 0xd2, 0x6f, 0x70, 0xa4, 0xb3, 0x53, 0x31, 0xbf, 0xd1, 0x60, 0x3e,
0x89, 0x94, 0xbd, 0x7c, 0xd9, 0x83, 0x9c, 0x3e, 0xd2, 0x12, 0x19, 0xf9, 0x5e, 0x83, 0x85, 0xd4,
0xd6, 0xce, 0x54, 0xf1, 0x33, 0x90, 0x4a, 0x36, 0x47, 0xe1, 0x0c, 0xcd, 0xf1, 0x73, 0x1e, 0x6a,
0x77, 0xec, 0x5d, 0xdc, 0xdb, 0xc1, 0x3d, 0xec, 0xb0, 0x80, 0xe8, 0x23, 0xa8, 0xf6, 0x6d, 0xe6,
0xec, 0x0b, 0xa9, 0xba, 0x72, 0xac, 0x2c, 0x23, 0x29, 0x85, 0xd3, 0xda, 0x1a, 0x83, 0x6c, 0xf8,
0x8c, 0x8c, 0xac, 0x7f, 0x4b, 0x42, 0xd5, 0x84, 0x06, 0x25, 0x63, 0x89, 0x17, 0x82, 0x58, 0x6f,
0x3c, 0x0e, 0xf9, 0x5c, 0x3a, 0xeb, 0xb3, 0x24, 0x45, 0x00, 0xe1, 0x4f, 0x07, 0x1e, 0xc1, 0x7d,
0xec, 0xb3, 0xf1, 0x0b, 0x61, 0x6b, 0x02, 0x1d, 0x9d, 0x88, 0xb7, 0xf8, 0x3e, 0xcc, 0x4f, 0x52,
0xd7, 0xe7, 0xa1, 0xd0, 0xc5, 0xa3, 0xa8, 0x56, 0x88, 0x7f, 0xea, 0x0b, 0x50, 0x1c, 0xda, 0xbd,
0x81, 0x3c, 0x89, 0x28, 0x5a, 0x5c, 0xcf, 0x5f, 0xd3, 0xcc, 0x1f, 0x34, 0x30, 0xa6, 0x11, 0xd1,
0xff, 0x9b, 0x00, 0xb2, 0xaa, 0x92, 0x55, 0xe1, 0x36, 0x1e, 0x45, 0xa8, 0x1b, 0x50, 0x0e, 0x42,
0xfe, 0x9e, 0x0b, 0x88, 0xac, 0xf8, 0x65, 0x55, 0xc5, 0x7b, 0x52, 0x7e, 0x7c, 0xd8, 0xb8, 0x90,
0x82, 0x57, 0x0a, 0x14, 0xbb, 0xea, 0x26, 0x94, 0x04, 0x1f, 0x6a, 0x14, 0xc4, 0x6d, 0x04, 0x7c,
0xae, 0x3e, 0x10, 0x12, 0x24, 0x35, 0xe6, 0x67, 0x50, 0xe6, 0x57, 0xed, 0x16, 0x66, 0x36, 0x6f,
0x1e, 0x8a, 0x7b, 0x7b, 0x77, 0x3c, 0xbf, 0x2b, 0xa9, 0xc5, 0xcd, 0xb3, 0x23, 0xe5, 0x28, 0xb6,
0x78, 0xd5, 0x78, 0xcd, 0x9f, 0x71, 0xbc, 0x5e, 0x81, 0x0a, 0x0a, 0x02, 0xb6, 0x6d, 0xb3, 0x7d,
0xaa, 0x37, 0xa0, 0x18, 0xf2, 0x0f, 0x79, 0x75, 0x56, 0xf8, 0x31, 0x10, 0x1a, 0x14, 0xc9, 0xcd,
0xaf, 0x35, 0xb8, 0x38, 0xf5, 0x2e, 0xe3, 0xaf, 0x12, 0x27, 0x5e, 0x49, 0xfa, 0xf1, 0xab, 0x64,
0x6c, 0x87, 0x12, 0x56, 0xfa, 0xbb, 0x50, 0x4b, 0x5d, 0x80, 0x72, 0x03, 0x17, 0xa4, 0x5b, 0x2d,
0x15, 0x0d, 0xa5, 0x6d, 0xcd, 0x3f, 0xf3, 0x50, 0xda, 0x61, 0x36, 0x1b, 0x50, 0xfd, 0x21, 0x94,
0x79, 0xef, 0xb9, 0x36, 0xb3, 0x45, 0xe4, 0x8c, 0x6f, 0x33, 0x95, 0xf8, 0x71, 0x9a, 0x95, 0x04,
0xc5, 0x78, 0xfc, 0x82, 0xa4, 0x22, 0x8a, 0x24, 0x17, 0x5f, 0x90, 0x51, 0x6c, 0x24, 0xb5, 0x7c,
0x48, 0xf4, 0x31, 0xa5, 0x76, 0x47, 0x1d, 0xfc, 0x78, 0x48, 0x6c, 0x45, 0x62, 0xa4, 0xf4, 0xfa,
0x3b, 0x50, 0x22, 0xd8, 0xa6, 0x81, 0x6f, 0xcc, 0x08, 0xcb, 0xba, 0x82, 0x44, 0x42, 0x7a, 0x7c,
0xd8, 0x98, 0x95, 0xe0, 0x62, 0x8d, 0xa4, 0xb5, 0xfe, 0x21, 0x9c, 0x73, 0x31, 0xb3, 0xbd, 0x1e,
0x35, 0x8a, 0x62, 0x97, 0xcb, 0x99, 0x5e, 0x28, 0x02, 0x6a, 0x3d, 0x72, 0xb4, 0xaa, 0x9c, 0x91,
0x5c, 0x20, 0x05, 0xc7, 0x67, 0xa9, 0x13, 0xb8, 0xd8, 0x28, 0x35, 0xb5, 0xa5, 0xe2, 0x78, 0x96,
0xae, 0x05, 0x2e, 0x46, 0x42, 0x63, 0x3e, 0xd5, 0xa0, 0x1a, 0x21, 0xad, 0xd9, 0x03, 0x8a, 0xf5,
0xe5, 0x78, 0x0f, 0x51, 0xa9, 0x2f, 0x2a, 0x9f, 0xfb, 0xa3, 0x10, 0x1f, 0x1f, 0x36, 0x2a, 0xc2,
0x8c, 0x2f, 0x62, 0xfa, 0x89, 0x0c, 0xe5, 0x4f, 0xc9, 0xd0, 0x25, 0x28, 0xee, 0x79, 0xb8, 0xa7,
0x86, 0x7b, 0x3c, 0x96, 0x6f, 0x72, 0x21, 0x8a, 0x74, 0xe6, 0x77, 0x79, 0xa8, 0xa5, 0x36, 0x97,
0xe1, 0xed, 0x1c, 0xcf, 0xfb, 0x7c, 0x86, 0x37, 0xc4, 0xd4, 0x9b, 0x45, 0xff, 0x00, 0x4a, 0x0e,
0xdf, 0x9f, 0xfa, 0xa7, 0xd2, 0xce, 0x5e, 0x08, 0x91, 0x97, 0x71, 0x17, 0x89, 0x25, 0x45, 0x12,
0x4e, 0xbf, 0x05, 0xe7, 0x09, 0x66, 0x64, 0xb4, 0xba, 0xc7, 0x30, 0xd9, 0xc1, 0x4e, 0xe0, 0xbb,
0x51, 0xb1, 0x8b, 0x71, 0x86, 0xcf, 0xa3, 0x49, 0x03, 0x74, 0xd2, 0xc7, 0xec, 0xc1, 0xcc, 0x7d,
0xaf, 0x8f, 0x79, 0xd2, 0xa9, 0x84, 0x89, 0x9e, 0x8b, 0x71, 0xd2, 0x95, 0xb3, 0xd2, 0xf3, 0xdc,
0xf8, 0xb6, 0x1f, 0x44, 0x8d, 0x5e, 0x1c, 0xe7, 0xe6, 0x2e, 0x17, 0xa2, 0x48, 0x77, 0x7d, 0x81,
0x5f, 0x59, 0x5f, 0x1d, 0x34, 0x72, 0x4f, 0x0f, 0x1a, 0xb9, 0x67, 0x07, 0xf2, 0xfa, 0xfa, 0x08,
0x2a, 0x3c, 0x1a, 0x65, 0x76, 0x3f, 0xfc, 0xbb, 0x43, 0x9a, 0x1f, 0x43, 0x99, 0xf7, 0x91, 0x18,
0x91, 0xaa, 0x34, 0xda, 0xd4, 0xd2, 0xac, 0x00, 0xd8, 0xa1, 0x97, 0x9e, 0x88, 0xf1, 0x1c, 0x1a,
0xff, 0x5b, 0x40, 0x09, 0x2b, 0xeb, 0xff, 0xcf, 0x5f, 0xd6, 0x73, 0x2f, 0x5e, 0xd6, 0x73, 0xbf,
0xbc, 0xac, 0xe7, 0x9e, 0x1c, 0xd5, 0xb5, 0xe7, 0x47, 0x75, 0xed, 0xc5, 0x51, 0x5d, 0xfb, 0xed,
0xa8, 0xae, 0x3d, 0xfd, 0xbd, 0x9e, 0x7b, 0x98, 0x1f, 0x2e, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff,
0xf6, 0xd1, 0x29, 0xc5, 0xf3, 0x10, 0x00, 0x00,
// 1459 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x57, 0x4f, 0x6f, 0x1b, 0x45,
0x14, 0xf7, 0xda, 0xb1, 0x6b, 0x3f, 0xc7, 0x24, 0x5d, 0x52, 0xe1, 0x46, 0xc2, 0x36, 0x5b, 0x84,
0x52, 0xa9, 0xb5, 0x95, 0x08, 0xa1, 0xaa, 0x50, 0xa4, 0x6c, 0x92, 0x56, 0xa1, 0x4d, 0x1b, 0x4d,
0xaa, 0x82, 0x4a, 0x0f, 0x6c, 0xbc, 0x13, 0x67, 0xb1, 0xbd, 0xbb, 0xcc, 0x8c, 0xad, 0x5a, 0x1c,
0xa8, 0x38, 0x71, 0x40, 0xa8, 0x47, 0xc4, 0x01, 0x35, 0x12, 0x1f, 0x80, 0xaf, 0xc0, 0xad, 0x37,
0xca, 0x8d, 0x03, 0xb2, 0x68, 0x10, 0x27, 0xbe, 0x41, 0x4e, 0x68, 0x66, 0x67, 0xf6, 0x8f, 0x53,
0x37, 0x1b, 0x95, 0x03, 0x27, 0xef, 0xbc, 0x3f, 0xbf, 0xf7, 0x9b, 0xf7, 0xde, 0xbc, 0x19, 0xc3,
0x4a, 0xf7, 0x0a, 0x6d, 0x3a, 0x5e, 0xab, 0x3b, 0xd8, 0xc5, 0xc4, 0xc5, 0x0c, 0xd3, 0x96, 0xdf,
0xed, 0xb4, 0x2c, 0xdf, 0xa1, 0xad, 0x3e, 0x66, 0x56, 0x6b, 0xb8, 0xdc, 0xea, 0x60, 0x17, 0x13,
0x8b, 0x61, 0xbb, 0xe9, 0x13, 0x8f, 0x79, 0xba, 0x11, 0xf8, 0x34, 0x23, 0x9f, 0xa6, 0xdf, 0xed,
0x34, 0xb9, 0x4f, 0x93, 0xfb, 0x34, 0x87, 0xcb, 0x8b, 0x97, 0x3b, 0x0e, 0xdb, 0x1f, 0xec, 0x36,
0xdb, 0x5e, 0xbf, 0xd5, 0xf1, 0x3a, 0x5e, 0x4b, 0xb8, 0xee, 0x0e, 0xf6, 0xc4, 0x4a, 0x2c, 0xc4,
0x57, 0x00, 0xb9, 0x78, 0xf9, 0xc5, 0x34, 0xc8, 0xc0, 0x65, 0x4e, 0x1f, 0x4f, 0x32, 0x58, 0x7c,
0xf7, 0xe5, 0xe6, 0xb4, 0xbd, 0x8f, 0xfb, 0xd6, 0x31, 0xaf, 0xe5, 0x17, 0x7b, 0x0d, 0x98, 0xd3,
0x6b, 0x39, 0x2e, 0xa3, 0x8c, 0x4c, 0xba, 0x18, 0xbf, 0xe4, 0xa0, 0xb8, 0xba, 0xbd, 0x79, 0x83,
0x78, 0x03, 0x5f, 0x6f, 0xc0, 0x8c, 0x6b, 0xf5, 0x71, 0x55, 0x6b, 0x68, 0x4b, 0x25, 0x73, 0xf6,
0xe9, 0xb8, 0x9e, 0x39, 0x1c, 0xd7, 0x67, 0x6e, 0x5b, 0x7d, 0x8c, 0x84, 0x46, 0xff, 0x1c, 0x8a,
0x43, 0x4c, 0xa8, 0xe3, 0xb9, 0xb4, 0x9a, 0x6d, 0xe4, 0x96, 0xca, 0x2b, 0x1f, 0x34, 0x4f, 0x4e,
0x56, 0x53, 0xc0, 0xdf, 0x0b, 0x1c, 0xaf, 0x7b, 0x64, 0xdd, 0xa1, 0x6d, 0x6f, 0x88, 0xc9, 0xc8,
0x9c, 0x97, 0x31, 0x8a, 0x52, 0x49, 0x51, 0x88, 0xaf, 0x7f, 0xad, 0xc1, 0xbc, 0x4f, 0xf0, 0x1e,
0x26, 0x04, 0xdb, 0x52, 0x5f, 0xcd, 0x35, 0xb4, 0x57, 0x0e, 0x5a, 0x95, 0x41, 0xe7, 0xb7, 0x27,
0xd0, 0xd1, 0xb1, 0x78, 0xfa, 0x81, 0x06, 0x8b, 0x14, 0x93, 0x21, 0x26, 0xab, 0xb6, 0x4d, 0x30,
0xa5, 0xe6, 0x68, 0xad, 0xe7, 0x60, 0x97, 0xad, 0x6d, 0xae, 0x23, 0x5a, 0x9d, 0x11, 0x39, 0xb8,
0x96, 0x86, 0xce, 0xce, 0x34, 0x14, 0xd3, 0x90, 0x7c, 0x16, 0xa7, 0x9a, 0x50, 0xf4, 0x12, 0x12,
0x86, 0x0d, 0xb3, 0xaa, 0x84, 0xb7, 0x1c, 0xca, 0xf4, 0xbb, 0x50, 0xe8, 0xf0, 0x05, 0xad, 0x6a,
0x82, 0xde, 0xa5, 0x34, 0xf4, 0x14, 0x82, 0xf9, 0x9a, 0x64, 0x53, 0x10, 0x4b, 0x8a, 0x24, 0x96,
0xf1, 0x9b, 0x06, 0xe5, 0xd5, 0xed, 0x4d, 0x84, 0xa9, 0x37, 0x20, 0x6d, 0x9c, 0xa2, 0x59, 0x56,
0x00, 0xf8, 0x2f, 0xf5, 0xad, 0x36, 0xb6, 0xab, 0xd9, 0x86, 0xb6, 0x54, 0x34, 0x75, 0x69, 0x07,
0xb7, 0x43, 0x0d, 0x8a, 0x59, 0x71, 0xd4, 0xae, 0xe3, 0xda, 0xa2, 0xce, 0x31, 0xd4, 0x9b, 0x8e,
0x6b, 0x23, 0xa1, 0xd1, 0x3f, 0x82, 0xfc, 0x10, 0x93, 0x5d, 0x9e, 0x7b, 0xde, 0x0a, 0x17, 0xd3,
0x6c, 0xee, 0x1e, 0x77, 0x30, 0x4b, 0x87, 0xe3, 0x7a, 0x5e, 0x7c, 0xa2, 0x00, 0xc2, 0xf8, 0x59,
0x83, 0xb9, 0xd8, 0x9e, 0x44, 0xf6, 0xae, 0xc0, 0x6c, 0x27, 0xd6, 0x39, 0x72, 0x7f, 0x0b, 0x92,
0xc9, 0x6c, 0xbc, 0xab, 0x50, 0xc2, 0x52, 0x6f, 0x43, 0x89, 0x48, 0x24, 0x75, 0x3a, 0x5a, 0x29,
0x53, 0xaf, 0x18, 0x44, 0x71, 0x62, 0x42, 0x8a, 0x22, 0x5c, 0xe3, 0xef, 0xa0, 0x0c, 0xea, 0xbc,
0xe8, 0x4b, 0xb1, 0x13, 0xc9, 0xcb, 0x5d, 0x32, 0x67, 0xa7, 0x9c, 0xa7, 0x13, 0x5a, 0x39, 0xfb,
0x3f, 0x68, 0xe5, 0xab, 0xc5, 0xef, 0x9f, 0xd4, 0x33, 0x8f, 0xfe, 0x68, 0x64, 0x8c, 0x4d, 0x28,
0xae, 0x0f, 0x88, 0xc5, 0x78, 0x62, 0xaf, 0x41, 0xd1, 0x96, 0xdf, 0xa2, 0x1c, 0x39, 0xf3, 0x2d,
0x35, 0x37, 0x94, 0xcd, 0xd1, 0xb8, 0x5e, 0xe1, 0xa3, 0xb1, 0xa9, 0x04, 0x28, 0x74, 0x31, 0x1e,
0x40, 0x65, 0xe3, 0xa1, 0xef, 0x11, 0x76, 0xc7, 0x67, 0x22, 0x13, 0xef, 0x40, 0x01, 0x0b, 0x81,
0x40, 0x2b, 0x46, 0x2d, 0x1f, 0x98, 0x21, 0xa9, 0xd5, 0x2f, 0x40, 0x1e, 0x3f, 0xb4, 0xda, 0x4c,
0xf6, 0x6e, 0x45, 0x9a, 0xe5, 0x37, 0xb8, 0x10, 0x05, 0x3a, 0xe3, 0x0e, 0xc0, 0x0d, 0x1c, 0x42,
0xaf, 0xc2, 0x9c, 0xaa, 0x55, 0xb2, 0x81, 0xde, 0x90, 0xce, 0x73, 0x28, 0xa9, 0x46, 0x93, 0xf6,
0xc6, 0x03, 0x28, 0x89, 0x26, 0xe3, 0x3d, 0xcf, 0x29, 0x88, 0x1e, 0x93, 0x28, 0x21, 0x05, 0x61,
0x81, 0x02, 0x5d, 0x78, 0x68, 0xb2, 0xd3, 0x0e, 0x4d, 0x2c, 0xaf, 0x3d, 0xa8, 0x04, 0xbe, 0xea,
0x1c, 0xa7, 0x8a, 0x70, 0x09, 0x8a, 0x8a, 0xa6, 0x8c, 0x12, 0x4e, 0x6e, 0x05, 0x84, 0x42, 0x8b,
0x58, 0xb4, 0x7d, 0x48, 0x1c, 0x98, 0x74, 0xc1, 0x2e, 0xc2, 0x19, 0xd9, 0xb4, 0x32, 0xd6, 0x9c,
0x34, 0x3b, 0xa3, 0x72, 0xa6, 0xf4, 0xb1, 0x48, 0x5f, 0x41, 0x75, 0xda, 0xc0, 0x7f, 0x85, 0x23,
0x9d, 0x9e, 0x8a, 0xf1, 0x9d, 0x06, 0xf3, 0x71, 0xa4, 0xf4, 0xe5, 0x4b, 0x1f, 0xe4, 0xe4, 0xf1,
0x18, 0xcb, 0xc8, 0x8f, 0x1a, 0x2c, 0x24, 0xb6, 0x76, 0xaa, 0x8a, 0x9f, 0x82, 0x54, 0xbc, 0x39,
0x72, 0xa7, 0x68, 0x8e, 0x5f, 0xb3, 0x50, 0xb9, 0x65, 0xed, 0xe2, 0xde, 0x0e, 0xee, 0xe1, 0x36,
0xf3, 0x88, 0x3e, 0x82, 0x72, 0xdf, 0x62, 0xed, 0x7d, 0x21, 0x55, 0xd7, 0x97, 0x99, 0x66, 0x24,
0x25, 0x70, 0x9a, 0x5b, 0x11, 0xc8, 0x86, 0xcb, 0xc8, 0xc8, 0x7c, 0x5d, 0x12, 0x2a, 0xc7, 0x34,
0x28, 0x1e, 0x4b, 0xbc, 0x36, 0xc4, 0x7a, 0xe3, 0xa1, 0xcf, 0xe7, 0xd2, 0x69, 0x9f, 0x38, 0x09,
0x02, 0x08, 0x7f, 0x31, 0x70, 0x08, 0xee, 0x63, 0x97, 0x45, 0xaf, 0x8d, 0xad, 0x09, 0x74, 0x74,
0x2c, 0xde, 0xe2, 0x87, 0x30, 0x3f, 0x49, 0x5d, 0x9f, 0x87, 0x5c, 0x17, 0x8f, 0x82, 0x5a, 0x21,
0xfe, 0xa9, 0x2f, 0x40, 0x7e, 0x68, 0xf5, 0x06, 0xf2, 0x24, 0xa2, 0x60, 0x71, 0x35, 0x7b, 0x45,
0x33, 0x7e, 0xd2, 0xa0, 0x3a, 0x8d, 0x88, 0xfe, 0x66, 0x0c, 0xc8, 0x2c, 0x4b, 0x56, 0xb9, 0x9b,
0x78, 0x14, 0xa0, 0x6e, 0x40, 0xd1, 0xf3, 0xf9, 0xdb, 0xd0, 0x23, 0xb2, 0xe2, 0x17, 0x55, 0x15,
0xef, 0x48, 0xf9, 0xd1, 0xb8, 0x7e, 0x2e, 0x01, 0xaf, 0x14, 0x28, 0x74, 0xd5, 0x0d, 0x28, 0x08,
0x3e, 0xb4, 0x9a, 0x13, 0xb7, 0x11, 0xf0, 0xb9, 0x7a, 0x4f, 0x48, 0x90, 0xd4, 0x18, 0x5f, 0x42,
0x91, 0x5f, 0xb5, 0x5b, 0x98, 0x59, 0xbc, 0x79, 0x28, 0xee, 0xed, 0xdd, 0x72, 0xdc, 0xae, 0xa4,
0x16, 0x36, 0xcf, 0x8e, 0x94, 0xa3, 0xd0, 0xe2, 0x45, 0xe3, 0x35, 0x7b, 0xca, 0xf1, 0x7a, 0x09,
0x4a, 0xc8, 0xf3, 0xd8, 0xb6, 0xc5, 0xf6, 0xa9, 0x5e, 0x87, 0xbc, 0xcf, 0x3f, 0xe4, 0xd5, 0x29,
0x5e, 0x08, 0x42, 0x83, 0x02, 0xb9, 0xf1, 0xad, 0x06, 0xe7, 0xa7, 0xde, 0x65, 0xfc, 0x85, 0xd3,
0x0e, 0x57, 0x92, 0x7e, 0xf8, 0xc2, 0x89, 0xec, 0x50, 0xcc, 0x4a, 0x7f, 0x1f, 0x2a, 0x89, 0x0b,
0x50, 0x6e, 0xe0, 0x9c, 0x74, 0xab, 0x24, 0xa2, 0xa1, 0xa4, 0xad, 0xf1, 0x4f, 0x16, 0x0a, 0x3b,
0xcc, 0x62, 0x03, 0xaa, 0xdf, 0x87, 0x22, 0xef, 0x3d, 0xdb, 0x62, 0x96, 0x88, 0x9c, 0xf2, 0x9d,
0xa7, 0x12, 0x1f, 0xa5, 0x59, 0x49, 0x50, 0x88, 0xc7, 0x2f, 0x48, 0x2a, 0xa2, 0x48, 0x72, 0xe1,
0x05, 0x19, 0xc4, 0x46, 0x52, 0xcb, 0x87, 0x44, 0x1f, 0x53, 0x6a, 0x75, 0xd4, 0xc1, 0x0f, 0x87,
0xc4, 0x56, 0x20, 0x46, 0x4a, 0xaf, 0xbf, 0x07, 0x05, 0x82, 0x2d, 0xea, 0xb9, 0xe2, 0xdd, 0x56,
0x32, 0x6b, 0x0a, 0x12, 0x09, 0xe9, 0xd1, 0xb8, 0x3e, 0x2b, 0xc1, 0xc5, 0x1a, 0x49, 0x6b, 0xfd,
0x13, 0x38, 0x63, 0x63, 0x66, 0x39, 0x3d, 0x5a, 0xcd, 0x8b, 0x5d, 0x2e, 0xa7, 0x7a, 0xa1, 0x08,
0xa8, 0xf5, 0xc0, 0xd1, 0x2c, 0x73, 0x46, 0x72, 0x81, 0x14, 0x1c, 0x9f, 0xa5, 0x6d, 0xcf, 0xc6,
0xd5, 0x42, 0x43, 0x5b, 0xca, 0x47, 0xb3, 0x74, 0xcd, 0xb3, 0x31, 0x12, 0x1a, 0xe3, 0xb1, 0x06,
0xe5, 0x00, 0x69, 0xcd, 0x1a, 0x50, 0xac, 0x2f, 0x87, 0x7b, 0x08, 0x4a, 0x7d, 0x5e, 0xf9, 0xdc,
0x1d, 0xf9, 0xf8, 0x68, 0x5c, 0x2f, 0x09, 0x33, 0xbe, 0x08, 0xe9, 0xc7, 0x32, 0x94, 0x3d, 0x21,
0x43, 0x17, 0x20, 0xbf, 0xe7, 0xe0, 0x9e, 0x1a, 0xee, 0xe1, 0x58, 0xbe, 0xce, 0x85, 0x28, 0xd0,
0x19, 0x3f, 0x64, 0xa1, 0x92, 0xd8, 0x5c, 0x8a, 0x77, 0x78, 0x38, 0xef, 0xb3, 0x29, 0xde, 0x10,
0xd3, 0x1f, 0xde, 0x1f, 0x43, 0xa1, 0xcd, 0xf7, 0xa7, 0xfe, 0xf5, 0xb4, 0xd2, 0x17, 0x42, 0xe4,
0x25, 0xea, 0x22, 0xb1, 0xa4, 0x48, 0xc2, 0xe9, 0x37, 0xe0, 0x2c, 0xc1, 0x8c, 0x8c, 0x56, 0xf7,
0x18, 0x26, 0x3b, 0xb8, 0xed, 0xb9, 0x76, 0x50, 0xec, 0x7c, 0x98, 0xe1, 0xb3, 0x68, 0xd2, 0x00,
0x1d, 0xf7, 0x31, 0x7a, 0x30, 0x73, 0xd7, 0xe9, 0x63, 0x9e, 0x74, 0x2a, 0x61, 0x82, 0xe7, 0x62,
0x98, 0x74, 0xe5, 0xac, 0xf4, 0x3c, 0x37, 0xae, 0xe5, 0x7a, 0x41, 0xa3, 0xe7, 0xa3, 0xdc, 0xdc,
0xe6, 0x42, 0x14, 0xe8, 0xae, 0x2e, 0xf0, 0x2b, 0xeb, 0x9b, 0x83, 0x7a, 0xe6, 0xf1, 0x41, 0x3d,
0xf3, 0xe4, 0x40, 0x5e, 0x5f, 0x9f, 0x42, 0x89, 0x47, 0xa3, 0xcc, 0xea, 0xfb, 0xff, 0x75, 0x48,
0xe3, 0x33, 0x28, 0xf2, 0x3e, 0x12, 0x23, 0x52, 0x95, 0x46, 0x9b, 0x5a, 0x9a, 0x15, 0x00, 0xcb,
0x77, 0x92, 0x13, 0x31, 0x9c, 0x43, 0xd1, 0xbf, 0x05, 0x14, 0xb3, 0x32, 0x56, 0x20, 0xf8, 0x2f,
0xc4, 0x67, 0xa0, 0xc3, 0x70, 0x3f, 0x31, 0x03, 0x37, 0xb9, 0x00, 0x05, 0xf2, 0xe8, 0xc6, 0x36,
0xdf, 0x7e, 0xfa, 0xbc, 0x96, 0x79, 0xf6, 0xbc, 0x96, 0xf9, 0xfd, 0x79, 0x2d, 0xf3, 0xe8, 0xb0,
0xa6, 0x3d, 0x3d, 0xac, 0x69, 0xcf, 0x0e, 0x6b, 0xda, 0x9f, 0x87, 0x35, 0xed, 0xf1, 0x5f, 0xb5,
0xcc, 0xfd, 0xec, 0x70, 0xf9, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xa0, 0x5e, 0x60, 0x73,
0x11, 0x00, 0x00,
}

View File

@ -69,6 +69,10 @@ message APIResource {
// kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')
optional string kind = 3;
// verbs is a list of supported kube verbs (this includes get, list, watch, create,
// update, patch, delete, deletecollection, and proxy)
optional Verbs verbs = 4;
}
// APIResourceList is a list of APIResource, it is used to expose the name of the
@ -408,3 +412,13 @@ message TypeMeta {
optional string apiVersion = 2;
}
// Verbs masks the value so protobuf can generate
//
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
message Verbs {
// items, if empty, will result in an empty slice
repeated string items = 1;
}

View File

@ -25,7 +25,12 @@ limitations under the License.
// separate packages.
package v1
import "strings"
import (
"fmt"
"strings"
"github.com/ugorji/go/codec"
)
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
@ -403,6 +408,40 @@ type APIResource struct {
Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
// kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')
Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
// verbs is a list of supported kube verbs (this includes get, list, watch, create,
// update, patch, delete, deletecollection, and proxy)
Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
}
// Verbs masks the value so protobuf can generate
//
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type Verbs []string
func (vs Verbs) String() string {
return fmt.Sprintf("%v", []string(vs))
}
// CodecEncodeSelf is part of the codec.Selfer interface.
func (vs *Verbs) CodecEncodeSelf(encoder *codec.Encoder) {
encoder.Encode(vs)
}
// CodecDecodeSelf is part of the codec.Selfer interface. It is overwritten here to make sure
// that an empty verbs list is not decoded as nil. On the other hand, an undefined verbs list
// will lead to nil because this decoding for Verbs is not invoked.
//
// TODO(sttts): this is due to a ugorji regression: https://github.com/ugorji/go/issues/119. Remove the
// workaround when the regression is fixed.
func (vs *Verbs) CodecDecodeSelf(decoder *codec.Decoder) {
m := []string{}
decoder.Decode(&m)
if len(m) == 0 {
*vs = []string{}
} else {
*vs = m
}
}
// APIResourceList is a list of APIResource, it is used to expose the name of the

View File

@ -0,0 +1,112 @@
/*
Copyright 2016 The Kubernetes 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 v1
import (
"encoding/json"
"reflect"
"testing"
"github.com/ugorji/go/codec"
)
func TestVerbsMarshalJSON(t *testing.T) {
cases := []struct {
input APIResource
result string
}{
{APIResource{}, `{"name":"","namespaced":false,"kind":"","verbs":null}`},
{APIResource{Verbs: Verbs([]string{})}, `{"name":"","namespaced":false,"kind":"","verbs":[]}`},
{APIResource{Verbs: Verbs([]string{"delete"})}, `{"name":"","namespaced":false,"kind":"","verbs":["delete"]}`},
}
for i, c := range cases {
result, err := json.Marshal(&c.input)
if err != nil {
t.Errorf("[%d] Failed to marshal input: '%v': %v", i, c.input, err)
}
if string(result) != c.result {
t.Errorf("[%d] Failed to marshal input: '%v': expected %+v, got %q", i, c.input, c.result, string(result))
}
}
}
func TestVerbsUnmarshalJSON(t *testing.T) {
cases := []struct {
input string
result APIResource
}{
{`{}`, APIResource{}},
{`{"verbs":null}`, APIResource{}},
{`{"verbs":[]}`, APIResource{Verbs: Verbs([]string{})}},
{`{"verbs":["delete"]}`, APIResource{Verbs: Verbs([]string{"delete"})}},
}
for i, c := range cases {
var result APIResource
if err := codec.NewDecoderBytes([]byte(c.input), new(codec.JsonHandle)).Decode(&result); err != nil {
t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err)
}
if !reflect.DeepEqual(result, c.result) {
t.Errorf("[%d] Failed to unmarshal input '%v': expected %+v, got %+v", i, c.input, c.result, result)
}
}
}
func TestVerbsUgorjiUnmarshalJSON(t *testing.T) {
cases := []struct {
input string
result APIResource
}{
{`{}`, APIResource{}},
{`{"verbs":null}`, APIResource{}},
{`{"verbs":[]}`, APIResource{Verbs: Verbs([]string{})}},
{`{"verbs":["delete"]}`, APIResource{Verbs: Verbs([]string{"delete"})}},
}
for i, c := range cases {
var result APIResource
if err := json.Unmarshal([]byte(c.input), &result); err != nil {
t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err)
}
if !reflect.DeepEqual(result, c.result) {
t.Errorf("[%d] Failed to unmarshal input '%v': expected %+v, got %+v", i, c.input, c.result, result)
}
}
}
func TestVerbsProto(t *testing.T) {
cases := []APIResource{
{},
{Verbs: Verbs([]string{})},
{Verbs: Verbs([]string{"delete"})},
}
for _, input := range cases {
data, err := input.Marshal()
if err != nil {
t.Fatalf("Failed to marshal input: '%v': %v", input, err)
}
resource := APIResource{}
if err := resource.Unmarshal(data); err != nil {
t.Fatalf("Failed to unmarshal output: '%v': %v", input, err)
}
if !reflect.DeepEqual(input, resource) {
t.Errorf("Marshal->Unmarshal is not idempotent: '%v' vs '%v'", input, resource)
}
}
}

View File

@ -81,6 +81,13 @@ func DeepCopy_v1_APIResource(in interface{}, out interface{}, c *conversion.Clon
out.Name = in.Name
out.Namespaced = in.Namespaced
out.Kind = in.Kind
if in.Verbs != nil {
in, out := &in.Verbs, &out.Verbs
*out = make(Verbs, len(*in))
copy(*out, *in)
} else {
out.Verbs = nil
}
return nil
}
}
@ -95,7 +102,9 @@ func DeepCopy_v1_APIResourceList(in interface{}, out interface{}, c *conversion.
in, out := &in.APIResources, &out.APIResources
*out = make([]APIResource, len(*in))
for i := range *in {
(*out)[i] = (*in)[i]
if err := DeepCopy_v1_APIResource(&(*in)[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.APIResources = nil

View File

@ -62,6 +62,21 @@ type documentable interface {
SwaggerDoc() map[string]string
}
// toDiscoveryKubeVerb maps an action.Verb to the logical kube verb, used for discovery
var toDiscoveryKubeVerb = map[string]string{
"CONNECT": "", // do not list in discovery.
"DELETE": "delete",
"DELETECOLLECTION": "deletecollection",
"GET": "get",
"LIST": "list",
"PATCH": "patch",
"POST": "create",
"PROXY": "proxy",
"PUT": "update",
"WATCH": "watch",
"WATCHLIST": "watch",
}
// errEmptyName is returned when API requests do not fill the name section of the path.
var errEmptyName = errors.NewBadRequest("name must be provided")
@ -490,6 +505,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
allMediaTypes := append(mediaTypes, streamMediaTypes...)
ws.Produces(allMediaTypes...)
kubeVerbs := map[string]struct{}{}
reqScope := RequestScope{
ContextFunc: ctxFn,
Serializer: a.group.Serializer,
@ -518,6 +534,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
namespaced = ""
}
if kubeVerb, found := toDiscoveryKubeVerb[action.Verb]; found {
if len(kubeVerb) != 0 {
kubeVerbs[kubeVerb] = struct{}{}
}
} else {
return nil, fmt.Errorf("unknown action verb for discovery: %s", action.Verb)
}
switch action.Verb {
case "GET": // Get a resource.
var handler restful.RouteFunction
@ -754,6 +778,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
}
// Note: update GetAuthorizerAttributes() when adding a custom handler.
}
apiResource.Verbs = make([]string, 0, len(kubeVerbs))
for kubeVerb := range kubeVerbs {
apiResource.Verbs = append(apiResource.Verbs, kubeVerb)
}
sort.Strings(apiResource.Verbs)
return &apiResource, nil
}

View File

@ -28,7 +28,7 @@ type Attributes interface {
// GetUser returns the user.Info object to authorize
GetUser() user.Info
// GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, and proxy),
// GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy),
// or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete)
GetVerb() string

View File

@ -45,7 +45,7 @@ type Fake struct {
// for every request in the order they are tried.
ProxyReactionChain []ProxyReactor
Resources map[string]*metav1.APIResourceList
Resources []*metav1.APIResourceList
}
// Reactor is an interface to allow the composition of reaction functions.
@ -225,10 +225,16 @@ func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*me
Resource: schema.GroupVersionResource{Resource: "resource"},
}
c.Invokes(action, nil)
return c.Resources[groupVersion], nil
for _, rl := range c.Resources {
if rl.GroupVersion == groupVersion {
return rl, nil
}
}
return nil, fmt.Errorf("GroupVersion %q not found", groupVersion)
}
func (c *FakeDiscovery) ServerResources() (map[string]*metav1.APIResourceList, error) {
func (c *FakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
action := ActionImpl{
Verb: "get",
Resource: schema.GroupVersionResource{Resource: "resource"},

View File

@ -53,6 +53,7 @@ go_test(
"//pkg/client/restclient:go_default_library",
"//pkg/client/restclient/fake:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/util/sets:go_default_library",
"//pkg/version:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger",
"//vendor:github.com/stretchr/testify/assert",
@ -73,5 +74,6 @@ go_test(
"//pkg/client/typed/discovery:go_default_library",
"//pkg/runtime:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/util/sets:go_default_library",
],
)

View File

@ -36,6 +36,9 @@ import (
"k8s.io/kubernetes/pkg/version"
)
// defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. ThirdPartyResources).
const defaultRetries = 2
// DiscoveryInterface holds the methods that discover server-supported API groups,
// versions and resources.
type DiscoveryInterface interface {
@ -67,13 +70,13 @@ type ServerResourcesInterface interface {
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
// ServerResources returns the supported resources for all groups and versions.
ServerResources() (map[string]*metav1.APIResourceList, error)
ServerResources() ([]*metav1.APIResourceList, error)
// ServerPreferredResources returns the supported resources with the version preferred by the
// server.
ServerPreferredResources() ([]schema.GroupVersionResource, error)
ServerPreferredResources() ([]*metav1.APIResourceList, error)
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
// version preferred by the server.
ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error)
ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
}
// ServerVersionInterface has a method for retrieving the server's version.
@ -154,7 +157,9 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
} else {
url.Path = "/apis/" + groupVersion
}
resources = &metav1.APIResourceList{}
resources = &metav1.APIResourceList{
GroupVersion: groupVersion,
}
err = d.restClient.Get().AbsPath(url.String()).Do().Into(resources)
if err != nil {
// ignore 403 or 404 error to be compatible with an v1.0 server.
@ -166,22 +171,43 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
return resources, nil
}
// ServerResources returns the supported resources for all groups and versions.
func (d *DiscoveryClient) ServerResources() (map[string]*metav1.APIResourceList, error) {
// serverResources returns the supported resources for all groups and versions.
func (d *DiscoveryClient) serverResources(failEarly bool) ([]*metav1.APIResourceList, error) {
apiGroups, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersions := metav1.ExtractGroupVersions(apiGroups)
result := map[string]*metav1.APIResourceList{}
for _, groupVersion := range groupVersions {
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return nil, err
result := []*metav1.APIResourceList{}
failedGroups := make(map[schema.GroupVersion]error)
for _, apiGroup := range apiGroups.Groups {
for _, version := range apiGroup.Versions {
gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
resources, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
if err != nil {
// TODO: maybe restrict this to NotFound errors
failedGroups[gv] = err
if failEarly {
return nil, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
continue
}
result = append(result, resources)
}
result[groupVersion] = resources
}
return result, nil
if len(failedGroups) == 0 {
return result, nil
}
return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
// ServerResources returns the supported resources for all groups and versions.
func (d *DiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
return withRetries(defaultRetries, d.serverResources)
}
// ErrGroupDiscoveryFailed is returned if one or more API groups fail to load.
@ -207,78 +233,86 @@ func IsGroupDiscoveryFailedError(err error) bool {
return err != nil && ok
}
// serverPreferredResources returns the supported resources with the version preferred by the
// server. If namespaced is true, only namespaced resources will be returned.
func (d *DiscoveryClient) serverPreferredResources(namespaced bool) ([]schema.GroupVersionResource, error) {
// retry in case the groups supported by the server change after ServerGroup() returns.
const maxRetries = 2
var failedGroups map[schema.GroupVersion]error
var results []schema.GroupVersionResource
var resources map[schema.GroupResource]string
RetrieveGroups:
for i := 0; i < maxRetries; i++ {
results = []schema.GroupVersionResource{}
resources = map[schema.GroupResource]string{}
failedGroups = make(map[schema.GroupVersion]error)
serverGroupList, err := d.ServerGroups()
if err != nil {
return results, err
}
// serverPreferredResources returns the supported resources with the version preferred by the server.
func (d *DiscoveryClient) serverPreferredResources(failEarly bool) ([]*metav1.APIResourceList, error) {
serverGroupList, err := d.ServerGroups()
if err != nil {
return nil, err
}
for _, apiGroup := range serverGroupList.Groups {
versions := apiGroup.Versions
for _, version := range versions {
groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
if err != nil {
if i < maxRetries-1 {
continue RetrieveGroups
}
failedGroups[groupVersion] = err
result := []*metav1.APIResourceList{}
failedGroups := make(map[schema.GroupVersion]error)
grVersions := map[schema.GroupResource]string{} // selected version of a GroupResource
grApiResources := map[schema.GroupResource]*metav1.APIResource{} // selected APIResource for a GroupResource
gvApiResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // blueprint for a APIResourceList for later grouping
for _, apiGroup := range serverGroupList.Groups {
for _, version := range apiGroup.Versions {
groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
if err != nil {
// TODO: maybe restrict this to NotFound errors
failedGroups[groupVersion] = err
if failEarly {
return nil, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
continue
}
// create empty list which is filled later in another loop
emptyApiResourceList := metav1.APIResourceList{
GroupVersion: version.GroupVersion,
}
gvApiResourceLists[groupVersion] = &emptyApiResourceList
result = append(result, &emptyApiResourceList)
for i := range apiResourceList.APIResources {
apiResource := &apiResourceList.APIResources[i]
if strings.Contains(apiResource.Name, "/") {
continue
}
for _, apiResource := range apiResourceList.APIResources {
// ignore the root scoped resources if "namespaced" is true.
if namespaced && !apiResource.Namespaced {
continue
}
if strings.Contains(apiResource.Name, "/") {
continue
}
gvr := groupVersion.WithResource(apiResource.Name)
if _, ok := resources[gvr.GroupResource()]; ok {
if gvr.Version != apiGroup.PreferredVersion.Version {
continue
}
// remove previous entry, because it will be replaced with a preferred one
for i := range results {
if results[i].GroupResource() == gvr.GroupResource() {
results = append(results[:i], results[i+1:]...)
}
}
}
resources[gvr.GroupResource()] = gvr.Version
results = append(results, gvr)
gv := schema.GroupResource{Group: apiGroup.Name, Resource: apiResource.Name}
if _, ok := grApiResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {
// only override with preferred version
continue
}
grVersions[gv] = version.Version
grApiResources[gv] = apiResource
}
}
if len(failedGroups) == 0 {
return results, nil
}
}
return results, &ErrGroupDiscoveryFailed{Groups: failedGroups}
// group selected APIResources according to GroupVersion into APIResourceLists
for groupResource, apiResource := range grApiResources {
version := grVersions[groupResource]
groupVersion := schema.GroupVersion{Group: groupResource.Group, Version: version}
apiResourceList := gvApiResourceLists[groupVersion]
apiResourceList.APIResources = append(apiResourceList.APIResources, *apiResource)
}
if len(failedGroups) == 0 {
return result, nil
}
return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
// ServerPreferredResources returns the supported resources with the version preferred by the
// server.
func (d *DiscoveryClient) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
return d.serverPreferredResources(false)
func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return withRetries(defaultRetries, func(retryEarly bool) ([]*metav1.APIResourceList, error) {
return d.serverPreferredResources(retryEarly)
})
}
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
// version preferred by the server.
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
return d.serverPreferredResources(true)
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
all, err := d.ServerPreferredResources()
return FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {
return r.Namespaced
}), all), err
}
// ServerVersion retrieves and parses the server's version (git version).
@ -329,6 +363,23 @@ func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.A
return &schema, nil
}
// withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns.
func withRetries(maxRetries int, f func(failEarly bool) ([]*metav1.APIResourceList, error)) ([]*metav1.APIResourceList, error) {
var result []*metav1.APIResourceList
var err error
for i := 0; i < maxRetries; i++ {
failEarly := i < maxRetries-1
result, err = f(failEarly)
if err == nil {
return result, nil
}
if _, ok := err.(*ErrGroupDiscoveryFailed); !ok {
return nil, err
}
}
return result, err
}
func setDiscoveryDefaults(config *restclient.Config) error {
config.APIPath = ""
config.GroupVersion = nil

View File

@ -29,6 +29,7 @@ import (
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/version"
)
@ -141,14 +142,14 @@ func TestGetServerResourcesWithV1Server(t *testing.T) {
defer server.Close()
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
// ServerResources should not return an error even if server returns error at /api/v1.
resourceMap, err := client.ServerResources()
serverResources, err := client.ServerResources()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if _, found := resourceMap["v1"]; !found {
t.Errorf("missing v1 in resource map")
gvs := groupVersions(serverResources)
if !sets.NewString(gvs...).Has("v1") {
t.Errorf("missing v1 in resource list: %v", serverResources)
}
}
func TestGetServerResources(t *testing.T) {
@ -161,7 +162,7 @@ func TestGetServerResources(t *testing.T) {
},
}
beta := metav1.APIResourceList{
GroupVersion: "extensions/v1",
GroupVersion: "extensions/v1beta1",
APIResources: []metav1.APIResource{
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
@ -249,13 +250,14 @@ func TestGetServerResources(t *testing.T) {
}
}
resourceMap, err := client.ServerResources()
serverResources, err := client.ServerResources()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
serverGroupVersions := sets.NewString(groupVersions(serverResources)...)
for _, api := range []string{"v1", "extensions/v1beta1"} {
if _, found := resourceMap[api]; !found {
t.Errorf("missing expected api: %s", api)
if !serverGroupVersions.Has(api) {
t.Errorf("missing expected api %q in %v", api, serverResources)
}
}
}
@ -332,12 +334,12 @@ func TestServerPreferredResources(t *testing.T) {
},
}
tests := []struct {
resourcesList *metav1.APIResourceList
resourcesList []*metav1.APIResourceList
response func(w http.ResponseWriter, req *http.Request)
expectErr func(err error) bool
}{
{
resourcesList: &stable,
resourcesList: []*metav1.APIResourceList{&stable},
expectErr: IsGroupDiscoveryFailedError,
response: func(w http.ResponseWriter, req *http.Request) {
var list interface{}
@ -426,7 +428,7 @@ func TestServerPreferredResources(t *testing.T) {
defer server.Close()
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
got, err := client.ServerPreferredResources()
resources, err := client.ServerPreferredResources()
if test.expectErr != nil {
if err == nil {
t.Error("unexpected non-error")
@ -438,7 +440,13 @@ func TestServerPreferredResources(t *testing.T) {
t.Errorf("unexpected error: %v", err)
continue
}
if !reflect.DeepEqual(got, test.resourcesList) {
got, err := GroupVersionResources(resources)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
expected, _ := GroupVersionResources(test.resourcesList)
if !reflect.DeepEqual(got, expected) {
t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got)
}
server.Close()
@ -533,10 +541,14 @@ func TestServerPreferredResourcesRetries(t *testing.T) {
defer server.Close()
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
got, err := client.ServerPreferredResources()
resources, err := client.ServerPreferredResources()
if !tc.expectedError(err) {
t.Errorf("case %d: unexpected error: %v", i, err)
}
got, err := GroupVersionResources(resources)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
}
if len(got) != tc.expectResources {
t.Errorf("case %d: expect %d resources, got %#v", i, tc.expectResources, got)
}
@ -575,7 +587,7 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
}
tests := []struct {
response func(w http.ResponseWriter, req *http.Request)
expected []schema.GroupVersionResource
expected map[schema.GroupVersionResource]struct{}
}{
{
response: func(w http.ResponseWriter, req *http.Request) {
@ -603,9 +615,9 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Write(output)
},
expected: []schema.GroupVersionResource{
{Group: "", Version: "v1", Resource: "pods"},
{Group: "", Version: "v1", Resource: "services"},
expected: map[schema.GroupVersionResource]struct{}{
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}: {},
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}: {},
},
},
{
@ -646,9 +658,9 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Write(output)
},
expected: []schema.GroupVersionResource{
{Group: "batch", Version: "v1", Resource: "jobs"},
{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"},
expected: map[schema.GroupVersionResource]struct{}{
schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"}: {},
schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
},
},
{
@ -689,27 +701,39 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Write(output)
},
expected: []schema.GroupVersionResource{
{Group: "batch", Version: "v2alpha1", Resource: "jobs"},
{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"},
expected: map[schema.GroupVersionResource]struct{}{
schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "jobs"}: {},
schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
},
},
}
for _, test := range tests {
for i, test := range tests {
server := httptest.NewServer(http.HandlerFunc(test.response))
defer server.Close()
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
got, err := client.ServerPreferredNamespacedResources()
resources, err := client.ServerPreferredNamespacedResources()
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Errorf("[%d] unexpected error: %v", i, err)
continue
}
// we need deterministic order and since during processing in ServerPreferredNamespacedResources
// a map comes into play the result needs sorting
got, err := GroupVersionResources(resources)
if err != nil {
t.Errorf("[%d] unexpected error: %v", i, err)
continue
}
if !reflect.DeepEqual(got, test.expected) {
t.Errorf("expected:\n%v\ngot:\n%v\n", test.expected, got)
t.Errorf("[%d] expected:\n%v\ngot:\n%v\n", i, test.expected, got)
}
server.Close()
}
}
func groupVersions(resources []*metav1.APIResourceList) []string {
result := []string{}
for _, resourceList := range resources {
result = append(result, resourceList.GroupVersion)
}
return result
}

View File

@ -17,7 +17,10 @@ limitations under the License.
package fake
import (
"fmt"
"github.com/emicklei/go-restful/swagger"
"k8s.io/kubernetes/pkg/api/v1"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/client/restclient"
@ -36,10 +39,15 @@ func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*me
Resource: schema.GroupVersionResource{Resource: "resource"},
}
c.Invokes(action, nil)
return c.Resources[groupVersion], nil
for _, resourceList := range c.Resources {
if resourceList.GroupVersion == groupVersion {
return resourceList, nil
}
}
return nil, fmt.Errorf("GroupVersion %q not found", groupVersion)
}
func (c *FakeDiscovery) ServerResources() (map[string]*metav1.APIResourceList, error) {
func (c *FakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
action := core.ActionImpl{
Verb: "get",
Resource: schema.GroupVersionResource{Resource: "resource"},
@ -48,11 +56,11 @@ func (c *FakeDiscovery) ServerResources() (map[string]*metav1.APIResourceList, e
return c.Resources, nil
}
func (c *FakeDiscovery) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
func (c *FakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}

View File

@ -108,3 +108,55 @@ func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion
return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v",
serverVersions, clientVersions)
}
// GroupVersionResources converts APIResourceLists to the GroupVersionResources.
func GroupVersionResources(rls []*metav1.APIResourceList) (map[schema.GroupVersionResource]struct{}, error) {
gvrs := map[schema.GroupVersionResource]struct{}{}
for _, rl := range rls {
gv, err := schema.ParseGroupVersion(rl.GroupVersion)
if err != nil {
return nil, err
}
for i := range rl.APIResources {
gvrs[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{}
}
}
return gvrs, nil
}
// FilteredBy filters by the given predicate. Empty APIResourceLists are dropped.
func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1.APIResourceList {
result := []*metav1.APIResourceList{}
for _, rl := range rls {
filtered := *rl
filtered.APIResources = nil
for i := range rl.APIResources {
if pred.Match(rl.GroupVersion, &rl.APIResources[i]) {
filtered.APIResources = append(filtered.APIResources, rl.APIResources[i])
}
}
if filtered.APIResources != nil {
result = append(result, &filtered)
}
}
return result
}
type ResourcePredicate interface {
Match(groupVersion string, r *metav1.APIResource) bool
}
type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool
func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool {
return fn(groupVersion, r)
}
// SupportsAllVerbs is a predicate matching a resource iff all given verbs are supported.
type SupportsAllVerbs struct {
Verbs []string
}
func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool {
return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...)
}

View File

@ -29,12 +29,14 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apimachinery/registered"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
uapi "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/util/sets"
)
func objBody(object interface{}) io.ReadCloser {
@ -155,3 +157,74 @@ func TestNegotiateVersion(t *testing.T) {
}
}
}
func TestFilteredBy(t *testing.T) {
all := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
return true
})
none := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
return false
})
onlyV2 := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
return strings.HasSuffix(gv, "/v2") || gv == "v2"
})
onlyBar := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
return r.Kind == "Bar"
})
foo := []*metav1.APIResourceList{
{
GroupVersion: "foo/v1",
APIResources: []metav1.APIResource{
{Name: "bar", Kind: "Bar"},
{Name: "test", Kind: "Test"},
},
},
{
GroupVersion: "foo/v2",
APIResources: []metav1.APIResource{
{Name: "bar", Kind: "Bar"},
{Name: "test", Kind: "Test"},
},
},
{
GroupVersion: "foo/v3",
APIResources: []metav1.APIResource{},
},
}
tests := []struct {
input []*metav1.APIResourceList
pred discovery.ResourcePredicate
expectedResources []string
}{
{nil, all, []string{}},
{[]*metav1.APIResourceList{
{GroupVersion: "foo/v1"},
}, all, []string{}},
{foo, all, []string{"foo/v1.bar", "foo/v1.test", "foo/v2.bar", "foo/v2.test"}},
{foo, onlyV2, []string{"foo/v2.bar", "foo/v2.test"}},
{foo, onlyBar, []string{"foo/v1.bar", "foo/v2.bar"}},
{foo, none, []string{}},
}
for i, test := range tests {
filtered := discovery.FilteredBy(test.pred, test.input)
if expected, got := sets.NewString(test.expectedResources...), sets.NewString(stringify(filtered)...); !expected.Equal(got) {
t.Errorf("[%d] unexpected group versions: expected=%v, got=%v", i, test.expectedResources, stringify(filtered))
}
}
}
func stringify(rls []*metav1.APIResourceList) []string {
result := []string{}
for _, rl := range rls {
for _, r := range rl.APIResources {
result = append(result, rl.GroupVersion+"."+r.Name)
}
if len(rl.APIResources) == 0 {
result = append(result, rl.GroupVersion)
}
}
return result
}

View File

@ -290,31 +290,34 @@ func (c *fakeCachedDiscoveryInterface) ServerResourcesForGroupVersion(groupVersi
return nil, errors.NewNotFound(schema.GroupResource{}, "")
}
func (c *fakeCachedDiscoveryInterface) ServerResources() (map[string]*metav1.APIResourceList, error) {
func (c *fakeCachedDiscoveryInterface) ServerResources() ([]*metav1.APIResourceList, error) {
if c.enabledA {
av1, _ := c.ServerResourcesForGroupVersion("a/v1")
return map[string]*metav1.APIResourceList{
"a/v1": av1,
}, nil
return []*metav1.APIResourceList{av1}, nil
}
return map[string]*metav1.APIResourceList{}, nil
return []*metav1.APIResourceList{}, nil
}
func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
if c.enabledA {
return []schema.GroupVersionResource{
return []*metav1.APIResourceList{
{
Group: "a",
Version: "v1",
Resource: "foo",
GroupVersion: "a/v1",
APIResources: []metav1.APIResource{
{
Name: "foo",
Kind: "Foo",
Verbs: []string{},
},
},
},
}, nil
}
return []schema.GroupVersionResource{}, nil
return nil, nil
}
func (c *fakeCachedDiscoveryInterface) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
return []schema.GroupVersionResource{}, nil
func (c *fakeCachedDiscoveryInterface) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) {

View File

@ -537,7 +537,7 @@ var ignoredResources = map[schema.GroupVersionResource]struct{}{
schema.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1beta1", Resource: "localsubjectaccessreviews"}: {},
}
func NewGarbageCollector(metaOnlyClientPool dynamic.ClientPool, clientPool dynamic.ClientPool, mapper meta.RESTMapper, resources []schema.GroupVersionResource) (*GarbageCollector, error) {
func NewGarbageCollector(metaOnlyClientPool dynamic.ClientPool, clientPool dynamic.ClientPool, mapper meta.RESTMapper, deletableResources map[schema.GroupVersionResource]struct{}) (*GarbageCollector, error) {
gc := &GarbageCollector{
metaOnlyClientPool: metaOnlyClientPool,
clientPool: clientPool,
@ -545,8 +545,8 @@ func NewGarbageCollector(metaOnlyClientPool dynamic.ClientPool, clientPool dynam
clock: clock.RealClock{},
dirtyQueue: workqueue.NewTimedWorkQueue(),
orphanQueue: workqueue.NewTimedWorkQueue(),
registeredRateLimiter: NewRegisteredRateLimiter(resources),
registeredRateLimiterForMonitors: NewRegisteredRateLimiter(resources),
registeredRateLimiter: NewRegisteredRateLimiter(deletableResources),
registeredRateLimiterForMonitors: NewRegisteredRateLimiter(deletableResources),
absentOwnerCache: NewUIDCache(500),
}
gc.propagator = &Propagator{
@ -557,7 +557,7 @@ func NewGarbageCollector(metaOnlyClientPool dynamic.ClientPool, clientPool dynam
},
gc: gc,
}
for _, resource := range resources {
for resource := range deletableResources {
if _, ok := ignoredResources[resource]; ok {
glog.V(6).Infof("ignore resource %#v", resource)
continue

View File

@ -49,7 +49,7 @@ func TestNewGarbageCollector(t *testing.T) {
metaOnlyClientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
config.ContentConfig.NegotiatedSerializer = nil
clientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
podResource := []schema.GroupVersionResource{{Version: "v1", Resource: "pods"}}
podResource := map[schema.GroupVersionResource]struct{}{schema.GroupVersionResource{Version: "v1", Resource: "pods"}: {}}
gc, err := NewGarbageCollector(metaOnlyClientPool, clientPool, registered.RESTMapper(), podResource)
if err != nil {
t.Fatal(err)
@ -113,7 +113,7 @@ func setupGC(t *testing.T, config *restclient.Config) *GarbageCollector {
metaOnlyClientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
config.ContentConfig.NegotiatedSerializer = nil
clientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
podResource := []schema.GroupVersionResource{{Version: "v1", Resource: "pods"}}
podResource := map[schema.GroupVersionResource]struct{}{schema.GroupVersionResource{Version: "v1", Resource: "pods"}: {}}
gc, err := NewGarbageCollector(metaOnlyClientPool, clientPool, registered.RESTMapper(), podResource)
if err != nil {
t.Fatal(err)

View File

@ -35,9 +35,9 @@ type RegisteredRateLimiter struct {
// NewRegisteredRateLimiter returns a new RegisteredRateLimiater.
// TODO: NewRegisteredRateLimiter is not dynamic. We need to find a better way
// when GC dynamically change the resources it monitors.
func NewRegisteredRateLimiter(resources []schema.GroupVersionResource) *RegisteredRateLimiter {
func NewRegisteredRateLimiter(resources map[schema.GroupVersionResource]struct{}) *RegisteredRateLimiter {
rateLimiters := make(map[schema.GroupVersion]*sync.Once)
for _, resource := range resources {
for resource := range resources {
gv := resource.GroupVersion()
if _, found := rateLimiters[gv]; !found {
rateLimiters[gv] = &sync.Once{}

View File

@ -24,6 +24,7 @@ go_library(
"//pkg/apis/meta/v1:go_default_library",
"//pkg/client/cache:go_default_library",
"//pkg/client/clientset_generated/release_1_5:go_default_library",
"//pkg/client/typed/discovery:go_default_library",
"//pkg/client/typed/dynamic:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/runtime:go_default_library",
@ -53,6 +54,7 @@ go_test(
"//pkg/client/clientset_generated/release_1_5/fake:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/testing/core:go_default_library",
"//pkg/client/typed/discovery:go_default_library",
"//pkg/client/typed/dynamic:go_default_library",
"//pkg/runtime:go_default_library",
"//pkg/runtime/schema:go_default_library",

View File

@ -20,6 +20,7 @@ import (
"time"
"k8s.io/kubernetes/pkg/api/v1"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/client/cache"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
@ -28,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/util/metrics"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/wait"
"k8s.io/kubernetes/pkg/util/workqueue"
"k8s.io/kubernetes/pkg/watch"
@ -57,8 +59,8 @@ type NamespaceController struct {
controller *cache.Controller
// namespaces that have been queued up for processing by workers
queue workqueue.RateLimitingInterface
// function to list of preferred group versions and their corresponding resource set for namespace deletion
groupVersionResourcesFn func() ([]schema.GroupVersionResource, error)
// function to list of preferred resources for namespace deletion
discoverResourcesFn func() ([]*metav1.APIResourceList, error)
// opCache is a cache to remember if a particular operation is not supported to aid dynamic client.
opCache *operationNotSupportedCache
// finalizerToken is the finalizer token managed by this controller
@ -69,36 +71,55 @@ type NamespaceController struct {
func NewNamespaceController(
kubeClient clientset.Interface,
clientPool dynamic.ClientPool,
groupVersionResourcesFn func() ([]schema.GroupVersionResource, error),
discoverResourcesFn func() ([]*metav1.APIResourceList, error),
resyncPeriod time.Duration,
finalizerToken v1.FinalizerName) *NamespaceController {
// the namespace deletion code looks at the discovery document to enumerate the set of resources on the server.
// it then finds all namespaced resources, and in response to namespace deletion, will call delete on all of them.
// unfortunately, the discovery information does not include the list of supported verbs/methods. if the namespace
// controller calls LIST/DELETECOLLECTION for a resource, it will get a 405 error from the server and cache that that was the case.
// we found in practice though that some auth engines when encountering paths they don't know about may return a 50x.
// until we have verbs, we pre-populate resources that do not support list or delete for well-known apis rather than
// probing the server once in order to be told no.
opCache := &operationNotSupportedCache{
m: make(map[operationKey]bool),
}
ignoredGroupVersionResources := []schema.GroupVersionResource{
{Group: "", Version: "v1", Resource: "bindings"},
// pre-fill opCache with the discovery info
//
// TODO(sttts): get rid of opCache and http 405 logic around it and trust discovery info
resources, err := discoverResourcesFn()
if err != nil {
glog.Fatalf("Failed to get supported resources: %v", err)
}
for _, ignoredGroupVersionResource := range ignoredGroupVersionResources {
opCache.setNotSupported(operationKey{op: operationDeleteCollection, gvr: ignoredGroupVersionResource})
opCache.setNotSupported(operationKey{op: operationList, gvr: ignoredGroupVersionResource})
deletableGroupVersionResources := []schema.GroupVersionResource{}
for _, rl := range resources {
gv, err := schema.ParseGroupVersion(rl.GroupVersion)
if err != nil {
glog.Errorf("Failed to parse GroupVersion %q, skipping: %v", rl.GroupVersion, err)
continue
}
for _, r := range rl.APIResources {
gvr := schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: r.Name}
verbs := sets.NewString([]string(r.Verbs)...)
if !verbs.Has("delete") {
glog.V(6).Infof("Skipping resource %v because it cannot be deleted.", gvr)
}
for _, op := range []operation{operationList, operationDeleteCollection} {
if !verbs.Has(string(op)) {
opCache.setNotSupported(operationKey{op: op, gvr: gvr})
}
}
deletableGroupVersionResources = append(deletableGroupVersionResources, gvr)
}
}
// create the controller so we can inject the enqueue function
namespaceController := &NamespaceController{
kubeClient: kubeClient,
clientPool: clientPool,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespace"),
groupVersionResourcesFn: groupVersionResourcesFn,
opCache: opCache,
finalizerToken: finalizerToken,
kubeClient: kubeClient,
clientPool: clientPool,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespace"),
discoverResourcesFn: discoverResourcesFn,
opCache: opCache,
finalizerToken: finalizerToken,
}
if kubeClient != nil && kubeClient.Core().RESTClient().GetRateLimiter() != nil {
@ -203,7 +224,7 @@ func (nm *NamespaceController) syncNamespaceFromKey(key string) (err error) {
return err
}
namespace := obj.(*v1.Namespace)
return syncNamespace(nm.kubeClient, nm.clientPool, nm.opCache, nm.groupVersionResourcesFn, namespace, nm.finalizerToken)
return syncNamespace(nm.kubeClient, nm.clientPool, nm.opCache, nm.discoverResourcesFn, namespace, nm.finalizerToken)
}
// Run starts observing the system with the specified number of workers.

View File

@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/fake"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema"
@ -113,8 +114,9 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
// when doing a delete all of content, we will do a GET of a collection, and DELETE of a collection by default
dynamicClientActionSet := sets.NewString()
groupVersionResources := testGroupVersionResources()
for _, groupVersionResource := range groupVersionResources {
resources := testResources()
groupVersionResources, _ := discovery.GroupVersionResources(resources)
for groupVersionResource := range groupVersionResources {
urlPath := path.Join([]string{
dynamic.LegacyAPIPathResolverFunc(schema.GroupVersionKind{Group: groupVersionResource.Group, Version: groupVersionResource.Version}),
groupVersionResource.Group,
@ -170,8 +172,8 @@ func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersio
mockClient := fake.NewSimpleClientset(testInput.testNamespace)
clientPool := dynamic.NewClientPool(clientConfig, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
fn := func() ([]schema.GroupVersionResource, error) {
return groupVersionResources, nil
fn := func() ([]*metav1.APIResourceList, error) {
return resources, nil
}
err := syncNamespace(mockClient, clientPool, &operationNotSupportedCache{m: make(map[operationKey]bool)}, fn, testInput.testNamespace, v1.FinalizerKubernetes)
@ -243,8 +245,8 @@ func TestSyncNamespaceThatIsActive(t *testing.T) {
Phase: v1.NamespaceActive,
},
}
fn := func() ([]schema.GroupVersionResource, error) {
return testGroupVersionResources(), nil
fn := func() ([]*metav1.APIResourceList, error) {
return testResources(), nil
}
err := syncNamespace(mockClient, nil, &operationNotSupportedCache{m: make(map[operationKey]bool)}, fn, testNamespace, v1.FinalizerKubernetes)
if err != nil {
@ -295,11 +297,37 @@ func (f *fakeActionHandler) ServeHTTP(response http.ResponseWriter, request *htt
response.Write([]byte("{\"kind\": \"List\",\"items\":null}"))
}
// testGroupVersionResources returns a mocked up set of resources across different api groups for testing namespace controller.
func testGroupVersionResources() []schema.GroupVersionResource {
results := []schema.GroupVersionResource{}
results = append(results, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"})
results = append(results, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"})
results = append(results, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"})
// testResources returns a mocked up set of resources across different api groups for testing namespace controller.
func testResources() []*metav1.APIResourceList {
results := []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Name: "pods",
Namespaced: true,
Kind: "Pod",
Verbs: []string{"get", "list", "delete", "deletecollection", "create", "update"},
},
{
Name: "services",
Namespaced: true,
Kind: "Service",
Verbs: []string{"get", "list", "delete", "deletecollection", "create", "update"},
},
},
},
{
GroupVersion: "extensions/v1beta1",
APIResources: []metav1.APIResource{
{
Name: "deployments",
Namespaced: true,
Kind: "Deployment",
Verbs: []string{"get", "list", "delete", "deletecollection", "create", "update"},
},
},
},
}
return results
}

View File

@ -18,7 +18,6 @@ package namespace
import (
"fmt"
"sort"
"sync"
"time"
@ -26,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/api/v1"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema"
@ -344,16 +344,13 @@ func deleteAllContent(
kubeClient clientset.Interface,
clientPool dynamic.ClientPool,
opCache *operationNotSupportedCache,
groupVersionResources []schema.GroupVersionResource,
groupVersionResources map[schema.GroupVersionResource]struct{},
namespace string,
namespaceDeletedAt metav1.Time,
) (int64, error) {
estimate := int64(0)
glog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, gvrs: %v", namespace, groupVersionResources)
// iterate over each group version, and attempt to delete all of its resources
// we sort resources to delete in a priority order that deletes pods LAST
sort.Sort(sortableGroupVersionResources(groupVersionResources))
for _, gvr := range groupVersionResources {
for gvr := range groupVersionResources {
gvrEstimate, err := deleteAllContentForGroupVersionResource(kubeClient, clientPool, opCache, gvr, namespace, namespaceDeletedAt)
if err != nil {
return estimate, err
@ -371,7 +368,7 @@ func syncNamespace(
kubeClient clientset.Interface,
clientPool dynamic.ClientPool,
opCache *operationNotSupportedCache,
groupVersionResourcesFn func() ([]schema.GroupVersionResource, error),
discoverResourcesFn func() ([]*metav1.APIResourceList, error),
namespace *v1.Namespace,
finalizerToken v1.FinalizerName,
) error {
@ -422,7 +419,13 @@ func syncNamespace(
}
// there may still be content for us to remove
groupVersionResources, err := groupVersionResourcesFn()
resources, err := discoverResourcesFn()
if err != nil {
return err
}
// TODO(sttts): get rid of opCache and pass the verbs (especially "deletecollection") down into the deleter
deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)
groupVersionResources, err := discovery.GroupVersionResources(deletableResources)
if err != nil {
return err
}
@ -502,20 +505,3 @@ func estimateGracefulTerminationForPods(kubeClient clientset.Interface, ns strin
}
return estimate, nil
}
// sortableGroupVersionResources sorts the input set of resources for deletion, and orders pods to always be last.
// the idea is that the namespace controller will delete all things that spawn pods first in order to reduce the time
// those controllers spend creating pods only to be told NO in admission and potentially overwhelming cluster especially if they lack rate limiting.
type sortableGroupVersionResources []schema.GroupVersionResource
func (list sortableGroupVersionResources) Len() int {
return len(list)
}
func (list sortableGroupVersionResources) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list sortableGroupVersionResources) Less(i, j int) bool {
return list[j].Group == "" && list[j].Resource == "pods"
}

View File

@ -5968,8 +5968,22 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
Format: "",
},
},
"verbs": {
SchemaProps: spec.SchemaProps{
Description: "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"name", "namespaced", "kind"},
Required: []string{"name", "namespaced", "kind", "verbs"},
},
},
Dependencies: []string{},

View File

@ -97,9 +97,11 @@ go_test(
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/meta:go_default_library",
"//pkg/api/rest:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apimachinery:go_default_library",
"//pkg/apimachinery/registered:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/extensions:go_default_library",
@ -109,6 +111,7 @@ go_test(
"//pkg/auth/user:go_default_library",
"//pkg/generated/openapi:go_default_library",
"//pkg/genericapiserver/options:go_default_library",
"//pkg/runtime:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/storage/etcd/testing:go_default_library",
"//pkg/storage/storagebackend:go_default_library",

View File

@ -28,14 +28,18 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apis/extensions"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
openapigen "k8s.io/kubernetes/pkg/generated/openapi"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
utilnet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/sets"
@ -99,52 +103,165 @@ func TestInstallAPIGroups(t *testing.T) {
defer etcdserver.Terminate(t)
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
config.DiscoveryAddresses = DefaultDiscoveryAddresses{DefaultAddress: "ExternalAddress"}
s, err := config.SkipComplete().New()
if err != nil {
t.Fatalf("Error in bringing up the server: %v", err)
}
apiGroupMeta := registered.GroupOrDie(api.GroupName)
extensionsGroupMeta := registered.GroupOrDie(extensions.GroupName)
s.InstallLegacyAPIGroup("/apiPrefix", &APIGroupInfo{
// legacy group version
GroupMeta: *apiGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
})
testAPI := func(gv schema.GroupVersion) APIGroupInfo {
getter, noVerbs := testGetterStorage{}, testNoVerbsStorage{}
apiGroupsInfo := []APIGroupInfo{
{
// extensions group version
GroupMeta: *extensionsGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
OptionsExternalVersion: &apiGroupMeta.GroupVersion,
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
},
scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(gv.WithKind("Getter"), getter.New())
scheme.AddKnownTypeWithName(gv.WithKind("NoVerb"), noVerbs.New())
scheme.AddKnownTypes(v1.SchemeGroupVersion,
&v1.ListOptions{},
&v1.DeleteOptions{},
&metav1.ExportOptions{},
&metav1.Status{},
)
interfacesFor := func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
return &meta.VersionInterfaces{
ObjectConvertor: scheme,
MetadataAccessor: meta.NewAccessor(),
}, nil
}
mapper := api.NewDefaultRESTMapperFromScheme([]schema.GroupVersion{gv}, interfacesFor, "", sets.NewString(), sets.NewString(), scheme)
groupMeta := apimachinery.GroupMeta{
GroupVersion: gv,
GroupVersions: []schema.GroupVersion{gv},
RESTMapper: mapper,
InterfacesFor: interfacesFor,
}
return APIGroupInfo{
GroupMeta: groupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{
gv.Version: {
"getter": &testGetterStorage{Version: gv.Version},
"noverbs": &testNoVerbsStorage{Version: gv.Version},
},
},
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
Scheme: scheme,
}
}
for i := range apiGroupsInfo {
s.InstallAPIGroup(&apiGroupsInfo[i])
apis := []APIGroupInfo{
testAPI(schema.GroupVersion{Group: "", Version: "v1"}),
testAPI(schema.GroupVersion{Group: "extensions", Version: "v1"}),
testAPI(schema.GroupVersion{Group: "batch", Version: "v1"}),
}
err = s.InstallLegacyAPIGroup("/apiPrefix", &apis[0])
assert.NoError(err)
groupPaths := []string{
config.LegacyAPIGroupPrefixes.List()[0], // /apiPrefix
}
for _, api := range apis[1:] {
err = s.InstallAPIGroup(&api)
assert.NoError(err)
groupPaths = append(groupPaths, APIGroupPrefix+"/"+api.GroupMeta.GroupVersion.Group) // /apis/<group>
}
server := httptest.NewServer(s.InsecureHandler)
defer server.Close()
validPaths := []string{
// "/api"
config.LegacyAPIGroupPrefixes.List()[0],
// "/api/v1"
config.LegacyAPIGroupPrefixes.List()[0] + "/" + apiGroupMeta.GroupVersion.Version,
// "/apis/extensions"
APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.Group,
// "/apis/extensions/v1beta1"
APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.String(),
}
for _, path := range validPaths {
_, err := http.Get(server.URL + path)
if !assert.NoError(err) {
t.Errorf("unexpected error: %v, for path: %s", err, path)
for i := range apis {
// should serve APIGroup at group path
info := &apis[i]
path := groupPaths[i]
resp, err := http.Get(server.URL + path)
if err != nil {
t.Errorf("[%d] unexpected error getting path %q path: %v", i, path, err)
continue
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("[%d] unexpected error reading body at path %q: %v", i, path, err)
continue
}
t.Logf("[%d] json at %s: %s", i, path, string(body))
if i == 0 {
// legacy API returns APIVersions
group := metav1.APIVersions{}
err = json.Unmarshal(body, &group)
if err != nil {
t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err)
continue
}
} else {
// API groups return APIGroup
group := metav1.APIGroup{}
err = json.Unmarshal(body, &group)
if err != nil {
t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err)
continue
}
if got, expected := group.Name, info.GroupMeta.GroupVersion.Group; got != expected {
t.Errorf("[%d] unexpected group name at path %q: got=%q expected=%q", i, path, got, expected)
continue
}
if got, expected := group.PreferredVersion.Version, info.GroupMeta.GroupVersion.Version; got != expected {
t.Errorf("[%d] unexpected group version at path %q: got=%q expected=%q", i, path, got, expected)
continue
}
}
// should serve APIResourceList at group path + /<group-version>
path = path + "/" + info.GroupMeta.GroupVersion.Version
resp, err = http.Get(server.URL + path)
if err != nil {
t.Errorf("[%d] unexpected error getting path %q path: %v", i, path, err)
continue
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("[%d] unexpected error reading body at path %q: %v", i, path, err)
continue
}
t.Logf("[%d] json at %s: %s", i, path, string(body))
resources := metav1.APIResourceList{}
err = json.Unmarshal(body, &resources)
if err != nil {
t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err)
continue
}
if got, expected := resources.GroupVersion, info.GroupMeta.GroupVersion.String(); got != expected {
t.Errorf("[%d] unexpected groupVersion at path %q: got=%q expected=%q", i, path, got, expected)
continue
}
// the verbs should match the features of resources
for _, r := range resources.APIResources {
switch r.Name {
case "getter":
if got, expected := sets.NewString([]string(r.Verbs)...), sets.NewString("get"); !got.Equal(expected) {
t.Errorf("[%d] unexpected verbs for resource %s/%s: got=%v expected=%v", i, resources.GroupVersion, r.Name, got, expected)
}
case "noverbs":
if r.Verbs == nil {
t.Errorf("[%d] unexpected nil verbs slice. Expected: []string{}", i)
}
if got, expected := sets.NewString([]string(r.Verbs)...), sets.NewString(); !got.Equal(expected) {
t.Errorf("[%d] unexpected verbs for resource %s/%s: got=%v expected=%v", i, resources.GroupVersion, r.Name, got, expected)
}
}
}
}
}
@ -462,3 +579,33 @@ func TestGetServerAddressByClientCIDRs(t *testing.T) {
}
}
}
type testGetterStorage struct {
Version string
}
func (p *testGetterStorage) New() runtime.Object {
return &metav1.APIGroup{
TypeMeta: metav1.TypeMeta{
Kind: "Getter",
APIVersion: p.Version,
},
}
}
func (p *testGetterStorage) Get(ctx api.Context, name string) (runtime.Object, error) {
return nil, nil
}
type testNoVerbsStorage struct {
Version string
}
func (p *testNoVerbsStorage) New() runtime.Object {
return &metav1.APIGroup{
TypeMeta: metav1.TypeMeta{
Kind: "NoVerbs",
APIVersion: p.Version,
},
}
}

View File

@ -78,6 +78,7 @@ go_library(
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/typed/discovery:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/client/unversioned/portforward:go_default_library",

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/typed/discovery"
conditions "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
@ -367,20 +368,11 @@ func Run(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobr
}
// TODO turn this into reusable method checking available resources
func contains(resourcesList map[string]*metav1.APIResourceList, resource schema.GroupVersionResource) bool {
if resourcesList == nil {
return false
}
resourcesGroup, ok := resourcesList[resource.GroupVersion().String()]
if !ok {
return false
}
for _, item := range resourcesGroup.APIResources {
if resource.Resource == item.Name {
return true
}
}
return false
func contains(resourcesList []*metav1.APIResourceList, resource schema.GroupVersionResource) bool {
resources := discovery.FilteredBy(discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
return resource.GroupVersion().String() == gv && resource.Resource == r.Name
}), resourcesList)
return len(resources) != 0
}
// waitForPod watches the given pod until the exitCondition is true. Each two seconds

View File

@ -86,19 +86,19 @@ func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion stri
}
// ServerResources returns the supported resources for all groups and versions.
func (d *CachedDiscoveryClient) ServerResources() (map[string]*metav1.APIResourceList, error) {
func (d *CachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
apiGroups, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersions := metav1.ExtractGroupVersions(apiGroups)
result := map[string]*metav1.APIResourceList{}
result := []*metav1.APIResourceList{}
for _, groupVersion := range groupVersions {
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return nil, err
}
result[groupVersion] = resources
result = append(result, resources)
}
return result, nil
}
@ -209,11 +209,11 @@ func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
return d.delegate.RESTClient()
}
func (d *CachedDiscoveryClient) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
func (d *CachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return d.delegate.ServerPreferredResources()
}
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return d.delegate.ServerPreferredNamespacedResources()
}

View File

@ -139,19 +139,19 @@ func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string
return nil, errors.NewNotFound(schema.GroupResource{}, "")
}
func (c *fakeDiscoveryClient) ServerResources() (map[string]*metav1.APIResourceList, error) {
func (c *fakeDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
return map[string]*metav1.APIResourceList{}, nil
return []*metav1.APIResourceList{}, nil
}
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
return []schema.GroupVersionResource{}, nil
return nil, nil
}
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
return []schema.GroupVersionResource{}, nil
return nil, nil
}
func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {

View File

@ -51,21 +51,14 @@ func (e ShortcutExpander) getAll() []schema.GroupResource {
return e.All
}
availableResources := []schema.GroupVersionResource{}
for groupVersionString, resourceList := range apiResources {
currVersion, err := schema.ParseGroupVersion(groupVersionString)
if err != nil {
return e.All
}
for _, resource := range resourceList.APIResources {
availableResources = append(availableResources, currVersion.WithResource(resource.Name))
}
availableResources, err := discovery.GroupVersionResources(apiResources)
if err != nil {
return e.All
}
availableAll := []schema.GroupResource{}
for _, requestedResource := range e.All {
for _, availableResource := range availableResources {
for availableResource := range availableResources {
if requestedResource.Group == availableResource.Group &&
requestedResource.Resource == availableResource.Resource {
availableAll = append(availableAll, requestedResource)

View File

@ -1084,7 +1084,11 @@ func hasRemainingContent(c clientset.Interface, clientPool dynamic.ClientPool, n
}
// find out what content is supported on the server
groupVersionResources, err := c.Discovery().ServerPreferredNamespacedResources()
resources, err := c.Discovery().ServerPreferredNamespacedResources()
if err != nil {
return false, err
}
groupVersionResources, err := discovery.GroupVersionResources(resources)
if err != nil {
return false, err
}
@ -1095,7 +1099,7 @@ func hasRemainingContent(c clientset.Interface, clientPool dynamic.ClientPool, n
contentRemaining := false
// dump how many of resource type is on the server in a log.
for _, gvr := range groupVersionResources {
for gvr := range groupVersionResources {
// get a client for this group version...
dynamicClient, err := clientPool.ClientForGroupVersionResource(gvr)
if err != nil {

View File

@ -56,8 +56,8 @@ func (n *NamespaceController) Start() error {
return err
}
clientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
gvrFn := client.Discovery().ServerPreferredNamespacedResources
nc := namespacecontroller.NewNamespaceController(client, clientPool, gvrFn, ncResyncPeriod, v1.FinalizerKubernetes)
discoverResourcesFn := client.Discovery().ServerPreferredNamespacedResources
nc := namespacecontroller.NewNamespaceController(client, clientPool, discoverResourcesFn, ncResyncPeriod, v1.FinalizerKubernetes)
go nc.Run(ncConcurrency, n.stopCh)
return nil
}

View File

@ -29,12 +29,14 @@ import (
"github.com/golang/glog"
dto "github.com/prometheus/client_model/go"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
"k8s.io/kubernetes/pkg/controller/garbagecollector"
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
@ -128,16 +130,21 @@ func setup(t *testing.T) (*httptest.Server, *garbagecollector.GarbageCollector,
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
groupVersionResources, err := clientSet.Discovery().ServerPreferredResources()
preferredResources, err := clientSet.Discovery().ServerPreferredResources()
if err != nil {
t.Fatalf("Failed to get supported resources from server: %v", err)
}
deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, preferredResources)
deletableGroupVersionResources, err := discovery.GroupVersionResources(deletableResources)
if err != nil {
t.Fatalf("Failed to parse supported resources from server: %v", err)
}
config := &restclient.Config{Host: s.URL}
config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()}
metaOnlyClientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
config.ContentConfig.NegotiatedSerializer = nil
clientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
gc, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, registered.RESTMapper(), groupVersionResources)
gc, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, registered.RESTMapper(), deletableGroupVersionResources)
if err != nil {
t.Fatalf("Failed to create garbage collector")
}

View File

@ -571,6 +571,7 @@ k8s.io/kubernetes/pkg/apis/componentconfig,jbeda,1
k8s.io/kubernetes/pkg/apis/extensions,bgrant0607,1
k8s.io/kubernetes/pkg/apis/extensions/v1beta1,madhusudancs,1
k8s.io/kubernetes/pkg/apis/extensions/validation,nikhiljindal,1
k8s.io/kubernetes/pkg/apis/meta/v1,sttts,0
k8s.io/kubernetes/pkg/apis/policy/validation,deads2k,1
k8s.io/kubernetes/pkg/apis/rbac/validation,erictune,0
k8s.io/kubernetes/pkg/apis/storage/validation,caesarxuchao,1

1 name owner auto-assigned
571 k8s.io/kubernetes/pkg/apis/extensions bgrant0607 1
572 k8s.io/kubernetes/pkg/apis/extensions/v1beta1 madhusudancs 1
573 k8s.io/kubernetes/pkg/apis/extensions/validation nikhiljindal 1
574 k8s.io/kubernetes/pkg/apis/meta/v1 sttts 0
575 k8s.io/kubernetes/pkg/apis/policy/validation deads2k 1
576 k8s.io/kubernetes/pkg/apis/rbac/validation erictune 0
577 k8s.io/kubernetes/pkg/apis/storage/validation caesarxuchao 1