Skip to content

Commit e686cc9

Browse files
authored
Merge pull request #377 from cben/fix-non-suffix-plurals
[v4.1.z] Fix underscores for non-suffix plurals
2 parents 55b8fe0 + cd34590 commit e686cc9

File tree

5 files changed

+273
-14
lines changed

5 files changed

+273
-14
lines changed

CHANGELOG.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ Notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
55
Kubeclient release versioning follows [SemVer](https://semver.org/).
66

7-
## 4.1.0 — 2018-11-28
7+
## Unreleased
8+
9+
### Fixed
10+
11+
- Fixed method names for non-suffix plurals such as y -> ies (#377).
12+
13+
## 4.1.0 — 2018-11-28 — REGRESSION
14+
15+
This version broke method names where plural is not just adding a suffix, notably y -> ies (bug #376).
816

917
### Fixed
1018
- Support custom resources with lowercase `kind` (#361).
@@ -33,7 +41,7 @@ Kubeclient release versioning follows [SemVer](https://semver.org/).
3341

3442
## 3.1.1 - 2018-06-01 — REGRESSION
3543

36-
In this version `Kubeclient::Config.read` raises Psych::DisallowedClass on legal yaml configs containing a timestamp, for example gcp access-token expiry (#337).
44+
In this version `Kubeclient::Config.read` raises Psych::DisallowedClass on legal yaml configs containing a timestamp, for example gcp access-token expiry (bug #337).
3745

3846
### Security
3947
- Changed `Kubeclient::Config.read` to use `YAML.safe_load` (#334).

lib/kubeclient/common.rb

+21-12
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ def discover
132132
end
133133

134134
def self.parse_definition(kind, name)
135-
# Kubernetes gives us have 3 inputs:
136-
# kind: "ComponentStatus"
137-
# name: "componentstatuses"
138-
# singularName: "componentstatus" (usually kind.downcase)
135+
# Kubernetes gives us 3 inputs:
136+
# kind: "ComponentStatus", "NetworkPolicy", "Endpoints"
137+
# name: "componentstatuses", "networkpolicies", "endpoints"
138+
# singularName: "componentstatus" etc (usually omitted, defaults to kind.downcase)
139139
# and want to derive singular and plural method names, with underscores:
140-
# "component_status"
141-
# "component_statuses"
140+
# "network_policy"
141+
# "network_policies"
142142
# kind's CamelCase word boundaries determine our placement of underscores.
143143

144144
if IRREGULAR_NAMES[kind]
@@ -150,13 +150,22 @@ def self.parse_definition(kind, name)
150150
# But how? If it differs from kind.downcase, kind's word boundaries don't apply.
151151
singular_name = kind.downcase
152152

153-
if name.start_with?(kind.downcase)
154-
plural_suffix = name[kind.downcase.length..-1] # "es"
155-
singular_underscores = ClientMixin.underscore_entity(kind) # "component_status"
156-
method_names = [singular_underscores, singular_underscores + plural_suffix]
157-
else
158-
# Something weird, can't infer underscores for plural so just give them up
153+
if !(/[A-Z]/ =~ kind)
154+
# Some custom resources have a fully lowercase kind - can't infer underscores.
159155
method_names = [singular_name, name]
156+
else
157+
# Some plurals are not exact suffixes, e.g. NetworkPolicy -> networkpolicies.
158+
# So don't expect full last word to match.
159+
/^(?<prefix>(.*[A-Z]))(?<singular_suffix>[^A-Z]*)$/ =~ kind # "NetworkP", "olicy"
160+
if name.start_with?(prefix.downcase)
161+
plural_suffix = name[prefix.length..-1] # "olicies"
162+
prefix_underscores = ClientMixin.underscore_entity(prefix) # "network_p"
163+
method_names = [prefix_underscores + singular_suffix, # "network_policy"
164+
prefix_underscores + plural_suffix] # "network_policies"
165+
else
166+
# Something weird, can't infer underscores for plural so just give them up
167+
method_names = [singular_name, name]
168+
end
160169
end
161170
end
162171

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
{
2+
"kind": "APIResourceList",
3+
"groupVersion": "extensions/v1beta1",
4+
"resources": [
5+
{
6+
"name": "daemonsets",
7+
"singularName": "",
8+
"namespaced": true,
9+
"kind": "DaemonSet",
10+
"verbs": [
11+
"create",
12+
"delete",
13+
"deletecollection",
14+
"get",
15+
"list",
16+
"patch",
17+
"update",
18+
"watch"
19+
],
20+
"shortNames": [
21+
"ds"
22+
]
23+
},
24+
{
25+
"name": "daemonsets/status",
26+
"singularName": "",
27+
"namespaced": true,
28+
"kind": "DaemonSet",
29+
"verbs": [
30+
"get",
31+
"patch",
32+
"update"
33+
]
34+
},
35+
{
36+
"name": "deployments",
37+
"singularName": "",
38+
"namespaced": true,
39+
"kind": "Deployment",
40+
"verbs": [
41+
"create",
42+
"delete",
43+
"deletecollection",
44+
"get",
45+
"list",
46+
"patch",
47+
"update",
48+
"watch"
49+
],
50+
"shortNames": [
51+
"deploy"
52+
]
53+
},
54+
{
55+
"name": "deployments/rollback",
56+
"singularName": "",
57+
"namespaced": true,
58+
"kind": "DeploymentRollback",
59+
"verbs": [
60+
"create"
61+
]
62+
},
63+
{
64+
"name": "deployments/scale",
65+
"singularName": "",
66+
"namespaced": true,
67+
"group": "extensions",
68+
"version": "v1beta1",
69+
"kind": "Scale",
70+
"verbs": [
71+
"get",
72+
"patch",
73+
"update"
74+
]
75+
},
76+
{
77+
"name": "deployments/status",
78+
"singularName": "",
79+
"namespaced": true,
80+
"kind": "Deployment",
81+
"verbs": [
82+
"get",
83+
"patch",
84+
"update"
85+
]
86+
},
87+
{
88+
"name": "ingresses",
89+
"singularName": "",
90+
"namespaced": true,
91+
"kind": "Ingress",
92+
"verbs": [
93+
"create",
94+
"delete",
95+
"deletecollection",
96+
"get",
97+
"list",
98+
"patch",
99+
"update",
100+
"watch"
101+
],
102+
"shortNames": [
103+
"ing"
104+
]
105+
},
106+
{
107+
"name": "ingresses/status",
108+
"singularName": "",
109+
"namespaced": true,
110+
"kind": "Ingress",
111+
"verbs": [
112+
"get",
113+
"patch",
114+
"update"
115+
]
116+
},
117+
{
118+
"name": "networkpolicies",
119+
"singularName": "",
120+
"namespaced": true,
121+
"kind": "NetworkPolicy",
122+
"verbs": [
123+
"create",
124+
"delete",
125+
"deletecollection",
126+
"get",
127+
"list",
128+
"patch",
129+
"update",
130+
"watch"
131+
],
132+
"shortNames": [
133+
"netpol"
134+
]
135+
},
136+
{
137+
"name": "podsecuritypolicies",
138+
"singularName": "",
139+
"namespaced": false,
140+
"kind": "PodSecurityPolicy",
141+
"verbs": [
142+
"create",
143+
"delete",
144+
"deletecollection",
145+
"get",
146+
"list",
147+
"patch",
148+
"update",
149+
"watch"
150+
],
151+
"shortNames": [
152+
"psp"
153+
]
154+
},
155+
{
156+
"name": "replicasets",
157+
"singularName": "",
158+
"namespaced": true,
159+
"kind": "ReplicaSet",
160+
"verbs": [
161+
"create",
162+
"delete",
163+
"deletecollection",
164+
"get",
165+
"list",
166+
"patch",
167+
"update",
168+
"watch"
169+
],
170+
"shortNames": [
171+
"rs"
172+
]
173+
},
174+
{
175+
"name": "replicasets/scale",
176+
"singularName": "",
177+
"namespaced": true,
178+
"group": "extensions",
179+
"version": "v1beta1",
180+
"kind": "Scale",
181+
"verbs": [
182+
"get",
183+
"patch",
184+
"update"
185+
]
186+
},
187+
{
188+
"name": "replicasets/status",
189+
"singularName": "",
190+
"namespaced": true,
191+
"kind": "ReplicaSet",
192+
"verbs": [
193+
"get",
194+
"patch",
195+
"update"
196+
]
197+
},
198+
{
199+
"name": "replicationcontrollers",
200+
"singularName": "",
201+
"namespaced": true,
202+
"kind": "ReplicationControllerDummy",
203+
"verbs": []
204+
},
205+
{
206+
"name": "replicationcontrollers/scale",
207+
"singularName": "",
208+
"namespaced": true,
209+
"kind": "Scale",
210+
"verbs": [
211+
"get",
212+
"patch",
213+
"update"
214+
]
215+
}
216+
]
217+
}

test/test_common.rb

+13
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,17 @@ def test_format_datetime_with_time
7474
formatted = client.send(:format_datetime, value)
7575
assert_equal(formatted, '2018-04-30T19:20:33.000000000+00:00')
7676
end
77+
78+
def test_parse_definition_with_unconventional_names
79+
%w[
80+
PluralPolicy pluralpolicies plural_policy plural_policies
81+
LatinDatum latindata latin_datum latin_data
82+
Noseparator noseparators noseparator noseparators
83+
lowercase lowercases lowercase lowercases
84+
].each_slice(4) do |kind, plural, expected_single, expected_plural|
85+
method_names = Kubeclient::ClientMixin.parse_definition(kind, plural).method_names
86+
assert_equal(method_names[0], expected_single)
87+
assert_equal(method_names[1], expected_plural)
88+
end
89+
end
7790
end

test/test_missing_methods.rb

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ def test_missing
4141
end
4242
end
4343

44+
def test_nonsuffix_plurals
45+
stub_request(:get, %r{/apis/extensions/v1beta1$}).to_return(
46+
body: open_test_file('extensions_v1beta1_api_resource_list.json'),
47+
status: 200
48+
)
49+
client = Kubeclient::Client.new('http://localhost:8080/apis/extensions', 'v1beta1')
50+
assert_equal(true, client.respond_to?(:get_network_policy))
51+
assert_equal(true, client.respond_to?(:get_network_policies))
52+
assert_equal(true, client.respond_to?(:get_pod_security_policy))
53+
assert_equal(true, client.respond_to?(:get_pod_security_policies))
54+
end
55+
4456
def test_irregular_names
4557
stub_core_api_list
4658
client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1')

0 commit comments

Comments
 (0)