fluent-plugin-kubernetes_metadata_filter 2.1.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +53 -0
  3. data/.gitignore +0 -2
  4. data/.rubocop.yml +57 -0
  5. data/Gemfile +4 -2
  6. data/Gemfile.lock +159 -0
  7. data/README.md +49 -60
  8. data/Rakefile +15 -11
  9. data/doc/benchmark/5m-1-2500lps-256b-baseline-01/cpu.png +0 -0
  10. data/doc/benchmark/5m-1-2500lps-256b-baseline-01/latency.png +0 -0
  11. data/doc/benchmark/5m-1-2500lps-256b-baseline-01/loss.png +0 -0
  12. data/doc/benchmark/5m-1-2500lps-256b-baseline-01/mem.png +0 -0
  13. data/doc/benchmark/5m-1-2500lps-256b-baseline-01/readme.md +88 -0
  14. data/doc/benchmark/5m-1-2500lps-256b-baseline-01/results.html +127 -0
  15. data/doc/benchmark/5m-1-2500lps-256b-kube-01-01/cpu.png +0 -0
  16. data/doc/benchmark/5m-1-2500lps-256b-kube-01-01/latency.png +0 -0
  17. data/doc/benchmark/5m-1-2500lps-256b-kube-01-01/loss.png +0 -0
  18. data/doc/benchmark/5m-1-2500lps-256b-kube-01-01/mem.png +0 -0
  19. data/doc/benchmark/5m-1-2500lps-256b-kube-01-01/readme.md +97 -0
  20. data/doc/benchmark/5m-1-2500lps-256b-kube-01-01/results.html +136 -0
  21. data/doc/benchmark/5m-1-2500lps-256b-kube-01-marshal-02/cpu.png +0 -0
  22. data/doc/benchmark/5m-1-2500lps-256b-kube-01-marshal-02/latency.png +0 -0
  23. data/doc/benchmark/5m-1-2500lps-256b-kube-01-marshal-02/loss.png +0 -0
  24. data/doc/benchmark/5m-1-2500lps-256b-kube-01-marshal-02/mem.png +0 -0
  25. data/doc/benchmark/5m-1-2500lps-256b-kube-01-marshal-02/readme.md +97 -0
  26. data/doc/benchmark/5m-1-2500lps-256b-kube-01-marshal-02/results.html +136 -0
  27. data/fluent-plugin-kubernetes_metadata_filter.gemspec +25 -28
  28. data/lib/fluent/plugin/filter_kubernetes_metadata.rb +207 -187
  29. data/lib/fluent/plugin/kubernetes_metadata_cache_strategy.rb +30 -23
  30. data/lib/fluent/plugin/kubernetes_metadata_common.rb +66 -24
  31. data/lib/fluent/plugin/kubernetes_metadata_stats.rb +21 -5
  32. data/lib/fluent/plugin/kubernetes_metadata_test_api_adapter.rb +68 -0
  33. data/lib/fluent/plugin/kubernetes_metadata_util.rb +33 -0
  34. data/lib/fluent/plugin/kubernetes_metadata_watch_namespaces.rb +154 -27
  35. data/lib/fluent/plugin/kubernetes_metadata_watch_pods.rb +171 -29
  36. data/release_notes.md +42 -0
  37. data/test/cassettes/kubernetes_docker_metadata_annotations.yml +0 -34
  38. data/test/cassettes/{kubernetes_docker_metadata_dotted_labels.yml → kubernetes_docker_metadata_dotted_slashed_labels.yml} +0 -34
  39. data/test/cassettes/kubernetes_get_api_v1.yml +193 -0
  40. data/test/cassettes/kubernetes_get_api_v1_using_token.yml +195 -0
  41. data/test/cassettes/kubernetes_get_namespace_default.yml +72 -0
  42. data/test/cassettes/kubernetes_get_namespace_default_using_token.yml +71 -0
  43. data/test/cassettes/{kubernetes_docker_metadata.yml → kubernetes_get_pod.yml} +0 -82
  44. data/test/cassettes/kubernetes_get_pod_container_init.yml +145 -0
  45. data/test/cassettes/{metadata_with_namespace_id.yml → kubernetes_get_pod_using_token.yml} +2 -130
  46. data/test/cassettes/{kubernetes_docker_metadata_using_bearer_token.yml → kubernetes_get_pod_with_ownerrefs.yml} +17 -109
  47. data/test/cassettes/metadata_from_tag_and_journald_fields.yml +153 -0
  48. data/test/cassettes/metadata_from_tag_journald_and_kubernetes_fields.yml +285 -0
  49. data/test/cassettes/{non_kubernetes_docker_metadata.yml → valid_kubernetes_api_server_using_token.yml} +4 -44
  50. data/test/helper.rb +20 -2
  51. data/test/plugin/test_cache_stats.rb +10 -13
  52. data/test/plugin/test_cache_strategy.rb +158 -160
  53. data/test/plugin/test_filter_kubernetes_metadata.rb +451 -314
  54. data/test/plugin/test_watch_namespaces.rb +209 -55
  55. data/test/plugin/test_watch_pods.rb +302 -71
  56. data/test/plugin/watch_test.rb +52 -33
  57. metadata +91 -70
  58. data/circle.yml +0 -17
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
5
  # Kubernetes metadata
@@ -16,46 +18,186 @@
16
18
  # See the License for the specific language governing permissions and
17
19
  # limitations under the License.
18
20
  #
21
+ # TODO: this is mostly copy-paste from kubernetes_metadata_watch_namespaces.rb unify them
19
22
  require_relative 'kubernetes_metadata_common'
20
23
 
21
24
  module KubernetesMetadata
22
25
  module WatchPods
23
-
24
26
  include ::KubernetesMetadata::Common
25
27
 
28
+ def set_up_pod_thread
29
+ # Any failures / exceptions in the initial setup should raise
30
+ # Fluent:ConfigError, so that users can inspect potential errors in
31
+ # the configuration.
32
+ pod_watcher = start_pod_watch
33
+
34
+ Thread.current[:pod_watch_retry_backoff_interval] = @watch_retry_interval
35
+ Thread.current[:pod_watch_retry_count] = 0
36
+
37
+ # Any failures / exceptions in the followup watcher notice
38
+ # processing will be swallowed and retried. These failures /
39
+ # exceptions could be caused by Kubernetes API being temporarily
40
+ # down. We assume the configuration is correct at this point.
41
+ loop do
42
+ pod_watcher ||= get_pods_and_start_watcher
43
+ process_pod_watcher_notices(pod_watcher)
44
+ rescue GoneError => e
45
+ # Expected error. Quietly go back through the loop in order to
46
+ # start watching from the latest resource versions
47
+ @stats.bump(:pod_watch_gone_errors)
48
+ log.info('410 Gone encountered. Restarting pod watch to reset resource versions.', e)
49
+ pod_watcher = nil
50
+ rescue KubeException => e
51
+ if e.error_code == 401
52
+ # recreate client to refresh token
53
+ log.info("Encountered '401 Unauthorized' exception in watch, recreating client to refresh token")
54
+ create_client()
55
+ pod_watcher = nil
56
+ else
57
+ # treat all other errors the same as StandardError, log, swallow and reset
58
+ @stats.bump(:pod_watch_failures)
59
+ if Thread.current[:pod_watch_retry_count] < @watch_retry_max_times
60
+ # Instead of raising exceptions and crashing Fluentd, swallow
61
+ # the exception and reset the watcher.
62
+ log.info(
63
+ 'Exception encountered parsing pod watch event. The ' \
64
+ 'connection might have been closed. Sleeping for ' \
65
+ "#{Thread.current[:pod_watch_retry_backoff_interval]} " \
66
+ 'seconds and resetting the pod watcher.', e
67
+ )
68
+ sleep(Thread.current[:pod_watch_retry_backoff_interval])
69
+ Thread.current[:pod_watch_retry_count] += 1
70
+ Thread.current[:pod_watch_retry_backoff_interval] *= @watch_retry_exponential_backoff_base
71
+ pod_watcher = nil
72
+ else
73
+ # Since retries failed for many times, log as errors instead
74
+ # of info and raise exceptions and trigger Fluentd to restart.
75
+ message =
76
+ 'Exception encountered parsing pod watch event. The ' \
77
+ 'connection might have been closed. Retried ' \
78
+ "#{@watch_retry_max_times} times yet still failing. Restarting."
79
+ log.error(message, e)
80
+ raise Fluent::UnrecoverableError, message
81
+ end
82
+ end
83
+ rescue StandardError => e
84
+ @stats.bump(:pod_watch_failures)
85
+ if Thread.current[:pod_watch_retry_count] < @watch_retry_max_times
86
+ # Instead of raising exceptions and crashing Fluentd, swallow
87
+ # the exception and reset the watcher.
88
+ log.info(
89
+ 'Exception encountered parsing pod watch event. The ' \
90
+ 'connection might have been closed. Sleeping for ' \
91
+ "#{Thread.current[:pod_watch_retry_backoff_interval]} " \
92
+ 'seconds and resetting the pod watcher.', e
93
+ )
94
+ sleep(Thread.current[:pod_watch_retry_backoff_interval])
95
+ Thread.current[:pod_watch_retry_count] += 1
96
+ Thread.current[:pod_watch_retry_backoff_interval] *= @watch_retry_exponential_backoff_base
97
+ pod_watcher = nil
98
+ else
99
+ # Since retries failed for many times, log as errors instead
100
+ # of info and raise exceptions and trigger Fluentd to restart.
101
+ message =
102
+ 'Exception encountered parsing pod watch event. The ' \
103
+ 'connection might have been closed. Retried ' \
104
+ "#{@watch_retry_max_times} times yet still failing. Restarting."
105
+ log.error(message, e)
106
+ raise Fluent::UnrecoverableError, message
107
+ end
108
+ end
109
+ end
110
+
26
111
  def start_pod_watch
27
- begin
28
- resource_version = @client.get_pods.resourceVersion
29
- watcher = @client.watch_pods(resource_version)
30
- rescue Exception => e
31
- message = "Exception encountered fetching metadata from Kubernetes API endpoint: #{e.message}"
32
- message += " (#{e.response})" if e.respond_to?(:response)
33
-
34
- raise Fluent::ConfigError, message
112
+ get_pods_and_start_watcher
113
+ rescue StandardError => e
114
+ message = 'start_pod_watch: Exception encountered setting up pod watch ' \
115
+ "from Kubernetes API #{@apiVersion} endpoint " \
116
+ "#{@kubernetes_url}: #{e.message}"
117
+ message += " (#{e.response})" if e.respond_to?(:response)
118
+ log.debug(message)
119
+
120
+ raise Fluent::ConfigError, message
121
+ end
122
+
123
+ # List all pods, record the resourceVersion and return a watcher starting
124
+ # from that resourceVersion.
125
+ def get_pods_and_start_watcher
126
+ options = {
127
+ resource_version: '0' # Fetch from API server cache instead of etcd quorum read
128
+ }
129
+ if ENV['K8S_NODE_NAME']
130
+ options[:field_selector] = 'spec.nodeName=' + ENV['K8S_NODE_NAME']
131
+ end
132
+ if @last_seen_resource_version
133
+ options[:resource_version] = @last_seen_resource_version
134
+ else
135
+ pods = @client.get_pods(options)
136
+ pods[:items].each do |pod|
137
+ cache_key = pod[:metadata][:uid]
138
+ @cache[cache_key] = parse_pod_metadata(pod)
139
+ @stats.bump(:pod_cache_host_updates)
140
+ end
141
+
142
+ # continue watching from most recent resourceVersion
143
+ options[:resource_version] = pods[:metadata][:resourceVersion]
35
144
  end
36
145
 
146
+ watcher = @client.watch_pods(options)
147
+ reset_pod_watch_retry_stats
148
+ watcher
149
+ end
150
+
151
+ # Reset pod watch retry count and backoff interval as there is a
152
+ # successful watch notice.
153
+ def reset_pod_watch_retry_stats
154
+ Thread.current[:pod_watch_retry_count] = 0
155
+ Thread.current[:pod_watch_retry_backoff_interval] = @watch_retry_interval
156
+ end
157
+
158
+ # Process a watcher notice and potentially raise an exception.
159
+ def process_pod_watcher_notices(watcher)
37
160
  watcher.each do |notice|
38
- case notice.type
39
- when 'MODIFIED'
40
- cache_key = notice.object['metadata']['uid']
41
- cached = @cache[cache_key]
42
- if cached
43
- @cache[cache_key] = parse_pod_metadata(notice.object)
44
- @stats.bump(:pod_cache_watch_updates)
45
- elsif ENV['K8S_NODE_NAME'] == notice.object['spec']['nodeName'] then
46
- @cache[cache_key] = parse_pod_metadata(notice.object)
47
- @stats.bump(:pod_cache_host_updates)
48
- else
49
- @stats.bump(:pod_cache_watch_misses)
50
- end
51
- when 'DELETED'
52
- # ignore and let age out for cases where pods
53
- # deleted but still processing logs
54
- @stats.bump(:pod_cache_watch_delete_ignored)
161
+ # store version we processed to not reprocess it ... do not unset when there is no version in response
162
+ version = ( # TODO: replace with &.dig once we are on ruby 2.5+
163
+ notice[:object] && notice[:object][:metadata] && notice[:object][:metadata][:resourceVersion]
164
+ )
165
+ @last_seen_resource_version = version if version
166
+
167
+ case notice[:type]
168
+ when 'MODIFIED'
169
+ reset_pod_watch_retry_stats
170
+ cache_key = notice.dig(:object, :metadata, :uid)
171
+ cached = @cache[cache_key]
172
+ if cached
173
+ @cache[cache_key] = parse_pod_metadata(notice[:object])
174
+ @stats.bump(:pod_cache_watch_updates)
175
+ elsif ENV['K8S_NODE_NAME'] == notice[:object][:spec][:nodeName]
176
+ @cache[cache_key] = parse_pod_metadata(notice[:object])
177
+ @stats.bump(:pod_cache_host_updates)
178
+ else
179
+ @stats.bump(:pod_cache_watch_misses)
180
+ end
181
+ when 'DELETED'
182
+ reset_pod_watch_retry_stats
183
+ # ignore and let age out for cases where pods
184
+ # deleted but still processing logs
185
+ @stats.bump(:pod_cache_watch_delete_ignored)
186
+ when 'ERROR'
187
+ if notice[:object] && notice[:object][:code] == 410
188
+ @last_seen_resource_version = nil # requested resourceVersion was too old, need to reset
189
+ @stats.bump(:pod_watch_gone_notices)
190
+ raise GoneError
55
191
  else
56
- # Don't pay attention to creations, since the created pod may not
57
- # end up on this node.
58
- @stats.bump(:pod_cache_watch_ignored)
192
+ @stats.bump(:pod_watch_error_type_notices)
193
+ message = notice[:object][:message] if notice[:object] && notice[:object][:message]
194
+ raise "Error while watching pods: #{message}"
195
+ end
196
+ else
197
+ reset_pod_watch_retry_stats
198
+ # Don't pay attention to creations, since the created pod may not
199
+ # end up on this node.
200
+ @stats.bump(:pod_cache_watch_ignored)
59
201
  end
60
202
  end
61
203
  end
data/release_notes.md ADDED
@@ -0,0 +1,42 @@
1
+ # Release Notes
2
+
3
+ ## 2.9.4
4
+ As of this release, the 'de_dot' functionality is depricated and will be removed in future releases.
5
+ Ref: https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter/issues/320
6
+
7
+ ## v2.1.4
8
+ The use of `use_journal` is **DEPRECATED**. If this setting is not present, the plugin will
9
+ attempt to figure out the source of the metadata fields from the following:
10
+ - If `lookup_from_k8s_field true` (the default) and the following fields are present in the record:
11
+ `docker.container_id`, `kubernetes.namespace_name`, `kubernetes.pod_name`, `kubernetes.container_name`,
12
+ then the plugin will use those values as the source to use to lookup the metadata
13
+ - If `use_journal true`, or `use_journal` is unset, and the fields `CONTAINER_NAME` and `CONTAINER_ID_FULL` are present in the record,
14
+ then the plugin will parse those values using `container_name_to_kubernetes_regexp` and use those as the source to lookup the metadata
15
+ - Otherwise, if the tag matches `tag_to_kubernetes_name_regexp`, the plugin will parse the tag and use those values to
16
+ lookup the metdata
17
+
18
+ ## v2.1.x
19
+
20
+ As of the release 2.1.x of this plugin, it no longer supports parsing the source message into JSON and attaching it to the
21
+ payload. The following configuration options are removed:
22
+
23
+ * `merge_json_log`
24
+ * `preserve_json_log`
25
+
26
+ One way of preserving JSON logs can be through the [parser plugin](https://docs.fluentd.org/filter/parser).
27
+ It can parsed with the parser plugin like this:
28
+
29
+ ```
30
+ <filter kubernetes.**>
31
+ @type parser
32
+ key_name log
33
+ <parse>
34
+ @type json
35
+ json_parser json
36
+ </parse>
37
+ replace_invalid_sequence true
38
+ reserve_data true # this preserves unparsable log lines
39
+ emit_invalid_record_to_error false # In case of unparsable log lines keep the error log clean
40
+ reserve_time # the time was already parsed in the source, we don't want to overwrite it with current time.
41
+ </filter>
42
+ ```
@@ -18,40 +18,6 @@
18
18
  #
19
19
  ---
20
20
  http_interactions:
21
- - request:
22
- method: get
23
- uri: https://localhost:8443/api
24
- body:
25
- encoding: US-ASCII
26
- string: ''
27
- headers:
28
- Accept:
29
- - "*/*; q=0.5, application/xml"
30
- Accept-Encoding:
31
- - gzip, deflate
32
- User-Agent:
33
- - Ruby
34
- response:
35
- status:
36
- code: 200
37
- message: OK
38
- headers:
39
- Content-Type:
40
- - application/json
41
- Date:
42
- - Fri, 08 May 2015 10:35:37 GMT
43
- Content-Length:
44
- - '67'
45
- body:
46
- encoding: UTF-8
47
- string: |-
48
- {
49
- "versions": [
50
- "v1"
51
- ]
52
- }
53
- http_version:
54
- recorded_at: Fri, 08 May 2015 10:35:37 GMT
55
21
  - request:
56
22
  method: get
57
23
  uri: https://localhost:8443/api/v1/namespaces/default/pods/fabric8-console-controller-98rqc
@@ -18,40 +18,6 @@
18
18
  #
19
19
  ---
20
20
  http_interactions:
21
- - request:
22
- method: get
23
- uri: https://localhost:8443/api
24
- body:
25
- encoding: US-ASCII
26
- string: ''
27
- headers:
28
- Accept:
29
- - "*/*; q=0.5, application/xml"
30
- Accept-Encoding:
31
- - gzip, deflate
32
- User-Agent:
33
- - Ruby
34
- response:
35
- status:
36
- code: 200
37
- message: OK
38
- headers:
39
- Content-Type:
40
- - application/json
41
- Date:
42
- - Fri, 08 May 2015 10:35:37 GMT
43
- Content-Length:
44
- - '67'
45
- body:
46
- encoding: UTF-8
47
- string: |-
48
- {
49
- "versions": [
50
- "v1"
51
- ]
52
- }
53
- http_version:
54
- recorded_at: Fri, 08 May 2015 10:35:37 GMT
55
21
  - request:
56
22
  method: get
57
23
  uri: https://localhost:8443/api/v1/namespaces/default/pods/fabric8-console-controller-98rqc
@@ -0,0 +1,193 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2015 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ ---
20
+ http_interactions:
21
+ - request:
22
+ method: get
23
+ uri: https://localhost:8443/api/v1
24
+ body:
25
+ encoding: US-ASCII
26
+ string: ''
27
+ headers:
28
+ Accept:
29
+ - "*/*; q=0.5, application/xml"
30
+ Accept-Encoding:
31
+ - gzip, deflate
32
+ User-Agent:
33
+ - Ruby
34
+ response:
35
+ status:
36
+ code: 200
37
+ message: OK
38
+ headers:
39
+ Content-Type:
40
+ - application/json
41
+ Date:
42
+ - Fri, 08 May 2015 10:35:37 GMT
43
+ Transfer-Encoding:
44
+ - chunked
45
+ body:
46
+ encoding: UTF-8
47
+ string: |-
48
+ {
49
+ "kind": "APIResourceList",
50
+ "groupVersion": "v1",
51
+ "resources": [
52
+ {
53
+ "name": "bindings",
54
+ "singularName": "",
55
+ "namespaced": true,
56
+ "kind": "Binding",
57
+ "verbs": [
58
+ "create"
59
+ ]
60
+ },
61
+ {
62
+ "name": "namespaces",
63
+ "singularName": "",
64
+ "namespaced": false,
65
+ "kind": "Namespace",
66
+ "verbs": [
67
+ "create",
68
+ "delete",
69
+ "get",
70
+ "list",
71
+ "patch",
72
+ "update",
73
+ "watch"
74
+ ],
75
+ "shortNames": [
76
+ "ns"
77
+ ]
78
+ },
79
+ {
80
+ "name": "namespaces/finalize",
81
+ "singularName": "",
82
+ "namespaced": false,
83
+ "kind": "Namespace",
84
+ "verbs": [
85
+ "update"
86
+ ]
87
+ },
88
+ {
89
+ "name": "namespaces/status",
90
+ "singularName": "",
91
+ "namespaced": false,
92
+ "kind": "Namespace",
93
+ "verbs": [
94
+ "get",
95
+ "patch",
96
+ "update"
97
+ ]
98
+ },
99
+ {
100
+ "name": "pods",
101
+ "singularName": "",
102
+ "namespaced": true,
103
+ "kind": "Pod",
104
+ "verbs": [
105
+ "create",
106
+ "delete",
107
+ "deletecollection",
108
+ "get",
109
+ "list",
110
+ "patch",
111
+ "update",
112
+ "watch"
113
+ ],
114
+ "shortNames": [
115
+ "po"
116
+ ],
117
+ "categories": [
118
+ "all"
119
+ ]
120
+ },
121
+ {
122
+ "name": "pods/attach",
123
+ "singularName": "",
124
+ "namespaced": true,
125
+ "kind": "Pod",
126
+ "verbs": []
127
+ },
128
+ {
129
+ "name": "pods/binding",
130
+ "singularName": "",
131
+ "namespaced": true,
132
+ "kind": "Binding",
133
+ "verbs": [
134
+ "create"
135
+ ]
136
+ },
137
+ {
138
+ "name": "pods/eviction",
139
+ "singularName": "",
140
+ "namespaced": true,
141
+ "group": "policy",
142
+ "version": "v1beta1",
143
+ "kind": "Eviction",
144
+ "verbs": [
145
+ "create"
146
+ ]
147
+ },
148
+ {
149
+ "name": "pods/exec",
150
+ "singularName": "",
151
+ "namespaced": true,
152
+ "kind": "Pod",
153
+ "verbs": []
154
+ },
155
+ {
156
+ "name": "pods/log",
157
+ "singularName": "",
158
+ "namespaced": true,
159
+ "kind": "Pod",
160
+ "verbs": [
161
+ "get"
162
+ ]
163
+ },
164
+ {
165
+ "name": "pods/portforward",
166
+ "singularName": "",
167
+ "namespaced": true,
168
+ "kind": "Pod",
169
+ "verbs": []
170
+ },
171
+ {
172
+ "name": "pods/proxy",
173
+ "singularName": "",
174
+ "namespaced": true,
175
+ "kind": "Pod",
176
+ "verbs": []
177
+ },
178
+ {
179
+ "name": "pods/status",
180
+ "singularName": "",
181
+ "namespaced": true,
182
+ "kind": "Pod",
183
+ "verbs": [
184
+ "get",
185
+ "patch",
186
+ "update"
187
+ ]
188
+ }
189
+ ]
190
+ }
191
+ http_version:
192
+ recorded_at: Fri, 08 May 2015 10:35:37 GMT
193
+ recorded_with: VCR 2.9.3