refactor: plugin custom api generate rules (#2211)

* refactor: plugin custom api generate rules

* refactor: boolean convert

* fix: unit test
pull/2212/head
guqing 2022-07-05 15:00:12 +08:00 committed by GitHub
parent 27435a1aeb
commit f7945081a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 158 additions and 123 deletions

View File

@ -6,7 +6,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;
@ -74,19 +73,13 @@ public class Role extends AbstractExtension {
*/
final String[] verbs;
/**
* If the plugin name exists, it means that the API is provided by the plugin.
*/
final String pluginName;
public PolicyRule() {
this(null, null, null, null, null, null);
this(null, null, null, null, null);
}
public PolicyRule(String pluginName, String[] apiGroups, String[] resources,
public PolicyRule(String[] apiGroups, String[] resources,
String[] resourceNames,
String[] nonResourceURLs, String[] verbs) {
this.pluginName = StringUtils.defaultString(pluginName);
this.apiGroups = nullElseEmpty(apiGroups);
this.resources = nullElseEmpty(resources);
this.resourceNames = nullElseEmpty(resourceNames);
@ -114,11 +107,6 @@ public class Role extends AbstractExtension {
String pluginName;
public Builder pluginName(String pluginName) {
this.pluginName = pluginName;
return this;
}
public Builder apiGroups(String... apiGroups) {
this.apiGroups = apiGroups;
return this;
@ -145,7 +133,7 @@ public class Role extends AbstractExtension {
}
public PolicyRule build() {
return new PolicyRule(pluginName, apiGroups, resources, resourceNames,
return new PolicyRule(apiGroups, resources, resourceNames,
nonResourceURLs,
verbs);
}

View File

@ -121,6 +121,7 @@ public class PluginRequestMappingHandlerMapping extends RequestMappingHandlerMap
protected String buildPrefix(String pluginId, String version) {
GroupVersion groupVersion = GroupVersion.parseAPIVersion(version);
return String.format("/api/%s/plugins/%s", groupVersion.version(), pluginId);
return String.format("/apis/plugin.api.halo.run/%s/plugins/%s", groupVersion.version(),
pluginId);
}
}

View File

@ -66,5 +66,5 @@ public interface Attributes {
*/
String getPath();
String pluginName();
String getSubName();
}

View File

@ -69,7 +69,7 @@ public class AttributesRecord implements Attributes {
}
@Override
public String pluginName() {
return requestInfo.getPluginName();
public String getSubName() {
return requestInfo.getSubName();
}
}

View File

@ -5,7 +5,6 @@ import java.util.Objects;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.Role.PolicyRule;
/**
* @author guqing
@ -35,17 +34,26 @@ public class RbacRequestEvaluation {
combinedResource =
requestAttributes.getResource() + "/" + requestAttributes.getSubresource();
}
return verbMatches(rule, requestAttributes.getVerb())
&& apiGroupMatches(rule, requestAttributes.getApiGroup())
&& resourceMatches(rule, combinedResource, requestAttributes.getSubresource())
&& resourceNameMatches(rule, requestAttributes.getName())
&& pluginNameMatches(rule, requestAttributes.pluginName());
&& resourceNameMatches(rule,
combineResourceName(requestAttributes.getName(), requestAttributes.getSubName()));
}
return verbMatches(rule, requestAttributes.getVerb())
&& nonResourceURLMatches(rule, requestAttributes.getPath());
}
private String combineResourceName(String name, String subName) {
if (StringUtils.isBlank(name)) {
return subName;
}
if (StringUtils.isBlank(subName)) {
return name;
}
return name + "/" + subName;
}
protected boolean verbMatches(Role.PolicyRule rule, String requestedVerb) {
for (String ruleVerb : rule.getVerbs()) {
if (Objects.equals(ruleVerb, WildCard.VerbAll)) {
@ -71,7 +79,7 @@ public class RbacRequestEvaluation {
}
protected boolean resourceMatches(Role.PolicyRule rule, String combinedRequestedResource,
String requestedSubresource) {
String requestedSubresource) {
for (String ruleResource : rule.getResources()) {
// if everything is allowed, we match
if (Objects.equals(ruleResource, WildCard.ResourceAll)) {
@ -125,8 +133,4 @@ public class RbacRequestEvaluation {
}
return false;
}
protected boolean pluginNameMatches(PolicyRule rule, String pluginName) {
return StringUtils.equals(rule.getPluginName(), pluginName);
}
}

View File

@ -17,34 +17,40 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
public class RequestInfo {
boolean isResourceRequest;
final String path;
String pluginName;
String namespace;
String verb;
String apiPrefix;
String apiGroup;
String apiVersion;
String resource;
String subresource;
String name;
String subresource;
String subName;
String[] parts;
public RequestInfo(boolean isResourceRequest, String path, String verb) {
this(isResourceRequest, path, null, verb, null, null, null, null, null, null, null);
this(isResourceRequest, path, null, verb, null, null, null, null, null, null, null, null);
}
public RequestInfo(boolean isResourceRequest, String path, String pluginName, String verb,
public RequestInfo(boolean isResourceRequest, String path, String namespace, String verb,
String apiPrefix,
String apiGroup,
String apiVersion, String resource, String subresource, String name,
String apiVersion, String resource, String name, String subresource, String subName,
String[] parts) {
this.isResourceRequest = isResourceRequest;
this.path = StringUtils.defaultString(path, "");
this.pluginName = StringUtils.defaultString(pluginName, "");
this.namespace = StringUtils.defaultString(namespace, "");
this.verb = StringUtils.defaultString(verb, "");
this.apiPrefix = StringUtils.defaultString(apiPrefix, "");
this.apiGroup = StringUtils.defaultString(apiGroup, "");
this.apiVersion = StringUtils.defaultString(apiVersion, "");
this.resource = StringUtils.defaultString(resource, "");
this.subresource = StringUtils.defaultString(subresource, "");
this.subName = StringUtils.defaultString(subName, "");
this.name = StringUtils.defaultString(name, "");
this.parts = Objects.requireNonNullElseGet(parts, () -> new String[] {});
}

View File

@ -52,10 +52,11 @@ public class RequestInfoFactory {
* Valid Inputs:
* <p>Resource paths</p>
* <pre>
* /api/{version}/plugins
* /api/{version}/plugins/{pluginName}
* /api/{version}/plugins/{pluginName}/{resource}
* /api/{version}/plugins/{pluginName}/{resource}/{resourceName}
* /apis/{api-group}/{version}/namespaces
* /api/{version}/namespaces
* /api/{version}/namespaces/{namespace}
* /api/{version}/namespaces/{namespace}/{resource}
* /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
* /api/{version}/{resource}
* /api/{version}/{resource}/{resourceName}
* </pre>
@ -138,22 +139,22 @@ public class RequestInfoFactory {
default -> "";
};
}
// URL forms: /plugins/{plugin-name}/{kind}/*, where parts are adjusted to be relative
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative
// to kind
if (Objects.equals(currentParts[0], "plugins")
&& StringUtils.isEmpty(requestInfo.getApiGroup())) {
Set<String> namespaceSubresources = Set.of("status", "finalize");
if (Objects.equals(currentParts[0], "namespaces")) {
if (currentParts.length > 1) {
requestInfo.pluginName = currentParts[1];
requestInfo.namespace = currentParts[1];
// if there is another step after the plugin name and it is not a known
// plugins subresource
// if there is another step after the namespace name and it is not a known
// namespace subresource
// move currentParts to include it as a resource in its own right
if (currentParts.length > 2) {
if (currentParts.length > 2 && !namespaceSubresources.contains(currentParts[2])) {
currentParts = Arrays.copyOfRange(currentParts, 2, currentParts.length);
}
}
} else {
requestInfo.pluginName = "";
requestInfo.namespace = "";
}
// parsing successful, so we now know the proper value for .Parts
@ -163,6 +164,10 @@ public class RequestInfoFactory {
if (requestInfo.parts.length >= 3 && !specialVerbs.contains(
requestInfo.verb)) {
requestInfo.subresource = requestInfo.parts[2];
// if there is another step after the subresource name and it is not a known
if (requestInfo.parts.length >= 4) {
requestInfo.subName = requestInfo.parts[3];
}
}
if (requestInfo.parts.length >= 2) {
@ -177,7 +182,7 @@ public class RequestInfoFactory {
// verb is a list or a watch
if (requestInfo.name.length() == 0 && "get".equals(requestInfo.verb)) {
var watch = request.getQueryParams().getFirst("watch");
if (isWatch(watch)) {
if (Boolean.parseBoolean(watch)) {
requestInfo.verb = "watch";
} else {
requestInfo.verb = "list";
@ -185,17 +190,15 @@ public class RequestInfoFactory {
}
// if there's no name on the request and we thought it was a deleted before, then the
// actual verb is deletecollection
if (requestInfo.name.length() == 0
&& Objects.equals(requestInfo.verb, "delete")) {
requestInfo.verb = "deletecollection";
if (Objects.equals(requestInfo.verb, "delete")) {
var deleteAll = request.getQueryParams().getFirst("all");
if (Boolean.parseBoolean(deleteAll)) {
requestInfo.verb = "deletecollection";
}
}
return requestInfo;
}
boolean isWatch(String requestParam) {
return "1".equals(requestParam) || "true".equals(requestParam);
}
private String[] splitPath(String path) {
path = StringUtils.strip(path, "/");
if (StringUtils.isEmpty(path)) {

View File

@ -69,7 +69,8 @@ class PluginRequestMappingHandlerMappingTest {
assertThat(info).isNotNull();
assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(
Collections.singleton(
new PathPatternParser().parse("/api/v1alpha1/plugins/fakePlugin/user/{id}")));
new PathPatternParser().parse(
"/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/user/{id}")));
}
@Test
@ -94,7 +95,7 @@ class PluginRequestMappingHandlerMappingTest {
List<RequestMappingInfo> mappings = handlerMapping.getMappings("fakePlugin");
assertThat(mappings).hasSize(1);
assertThat(mappings.get(0).toString()).isEqualTo(
"{GET /api/v1alpha1/plugins/fakePlugin/user/{id}}");
"{GET /apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/user/{id}}");
}
@Test
@ -120,7 +121,8 @@ class PluginRequestMappingHandlerMappingTest {
// get handler by mock exchange
ServerWebExchange exchange =
MockServerWebExchange.from(get("/api/v1alpha1/plugins/fakePlugin/foo"));
MockServerWebExchange.from(
get("/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/foo"));
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
assertThat(hm).isNotNull();
@ -135,7 +137,7 @@ class PluginRequestMappingHandlerMappingTest {
Method expected =
ResolvableMethod.on(TestController.class).annot(getMapping("/foo").params("p")).build();
String requestPath = "/api/v1alpha1/plugins/fakePlugin/foo?p=anything";
String requestPath = "/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/foo?p=anything";
ServerWebExchange exchange = MockServerWebExchange.from(get(requestPath));
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
@ -150,7 +152,7 @@ class PluginRequestMappingHandlerMappingTest {
Method expected =
ResolvableMethod.on(TestController.class).annot(getMapping("")).build();
String requestPath = "/api/v1alpha1/plugins/fakePlugin";
String requestPath = "/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin";
ServerWebExchange exchange = MockServerWebExchange.from(get(requestPath));
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
@ -163,7 +165,7 @@ class PluginRequestMappingHandlerMappingTest {
// register handler methods first
handlerMapping.registerHandlerMethods("fakePlugin", new TestController());
String requestPath = "/api/v1alpha1/plugins/fakePlugin/bar";
String requestPath = "/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/bar";
ServerWebExchange exchange = MockServerWebExchange.from(post(requestPath));
Mono<Object> mono = this.handlerMapping.getHandler(exchange);

View File

@ -28,11 +28,10 @@ class PolicyRuleTest {
@Test
public void constructPolicyRule() throws JsonProcessingException, JSONException {
Role.PolicyRule policyRule = new Role.PolicyRule(null, null, null, null, null, null);
Role.PolicyRule policyRule = new Role.PolicyRule(null, null, null, null, null);
assertThat(policyRule).isNotNull();
JSONAssert.assertEquals("""
{
"pluginName": "",
"apiGroups": [],
"resources": [],
"resourceNames": [],
@ -46,7 +45,6 @@ class PolicyRuleTest {
Role.PolicyRule policyByBuilder = new Role.PolicyRule.Builder().build();
JSONAssert.assertEquals("""
{
"pluginName": "",
"apiGroups": [],
"resources": [],
"resourceNames": [],
@ -58,7 +56,6 @@ class PolicyRuleTest {
true);
Role.PolicyRule policyNonNull = new Role.PolicyRule.Builder()
.pluginName("fakePluginName")
.apiGroups("group")
.resources("resource-1", "resource-2")
.resourceNames("resourceName")
@ -68,7 +65,6 @@ class PolicyRuleTest {
JsonNode expected = objectMapper.readTree("""
{
"pluginName": "fakePluginName",
"apiGroups": [
"group"
],

View File

@ -87,23 +87,28 @@ public class RequestInfoResolverTest {
@Test
void pluginsScopedAndPluginManage() {
List<SuccessCase> testCases =
List.of(new SuccessCase("DELETE", "/apis/extensions/v1/plugins/other/posts",
"delete", "apis", "extensions", "v1", "", "plugins", "posts", "other",
List<CustomSuccessCase> testCases =
List.of(
new CustomSuccessCase("DELETE", "/apis/plugin.api.halo.run/v1/plugins/other/posts",
"delete", "apis", "plugin.api.halo.run", "v1", "", "plugins", "posts", "", "",
new String[] {"plugins", "other", "posts"}),
// api group identification
new SuccessCase("POST", "/apis/extensions/v1/plugins/other/posts", "create", "apis",
"extensions", "v1", "", "plugins", "posts", "other",
new String[] {"plugins", "other", "posts"}),
new CustomSuccessCase("POST",
"/apis/plugin.api.halo.run/v1/plugins/other/posts/foo",
"create", "apis",
"plugin.api.halo.run", "v1", "", "plugins", "posts", "other", "foo",
new String[] {"plugins", "other", "posts", "foo"}),
// api version identification
new SuccessCase("POST", "/apis/extensions/v1beta3/plugins/other/posts", "create",
"apis", "extensions", "v1beta3", "", "plugins", "posts", "other",
new String[] {"plugins", "other", "posts"}));
new CustomSuccessCase("POST",
"/apis/plugin.api.halo.run/v1beta3/plugins/other/posts/bar", "create",
"apis", "plugin.api.halo.run", "v1beta3", "", "plugins", "posts", "other",
"bar",
new String[] {"plugins", "other", "posts", "bar"}));
// 以 /apis 开头的 plugins 资源为 core 中管理插件使用的资源
for (SuccessCase successCase : testCases) {
for (CustomSuccessCase successCase : testCases) {
var request =
method(HttpMethod.valueOf(successCase.method),
successCase.url).build();
@ -112,21 +117,19 @@ public class RequestInfoResolverTest {
assertRequestInfoCase(successCase, requestInfo);
}
List<SuccessCase> pluginScopedCases =
List.of(new SuccessCase("DELETE", "/api/v1/plugins/other/posts",
"deletecollection", "api", "", "v1", "other", "posts", "", "",
new String[] {"posts"}),
List<CustomSuccessCase> pluginScopedCases =
List.of(
new CustomSuccessCase("DELETE", "/apis/plugin.api.halo.run/v1/plugins/other/posts",
"delete", "apis", "plugin.api.halo.run", "v1", "", "plugins", "posts",
"other", "", new String[] {"plugins", "other", "posts"}),
// api group identification
new SuccessCase("POST", "/api/v1/plugins/other/posts", "create", "api",
"", "v1", "other", "posts", "", "", new String[] {"posts"}),
new CustomSuccessCase("POST",
"/apis/plugin.api.halo.run/v1/plugins/other/posts/some-name", "create", "apis",
"plugin.api.halo.run", "v1", "other", "plugins", "posts", "other", "some-name",
new String[] {"plugins", "other", "posts", "some-name"}));
// api version identification
new SuccessCase("POST", "/api/v1beta3/plugins/other/posts", "create",
"api", "", "v1beta3", "other", "posts", "", "",
new String[] {"posts"}));
for (SuccessCase pluginScopedCase : pluginScopedCases) {
for (CustomSuccessCase pluginScopedCase : pluginScopedCases) {
var request =
method(HttpMethod.valueOf(pluginScopedCase.method),
pluginScopedCase.url).build();
@ -136,14 +139,16 @@ public class RequestInfoResolverTest {
}
}
private void assertRequestInfoCase(SuccessCase pluginScopedCase, RequestInfo requestInfo) {
assertThat(requestInfo.getPluginName()).isEqualTo(pluginScopedCase.expectedPluginName);
private void assertRequestInfoCase(CustomSuccessCase pluginScopedCase,
RequestInfo requestInfo) {
assertThat(requestInfo.getVerb()).isEqualTo(pluginScopedCase.expectedVerb);
assertThat(requestInfo.getParts()).isEqualTo(pluginScopedCase.expectedParts);
assertThat(requestInfo.getApiGroup()).isEqualTo(pluginScopedCase.expectedAPIGroup);
assertThat(requestInfo.getResource()).isEqualTo(pluginScopedCase.expectedResource);
assertThat(requestInfo.getSubresource())
.isEqualTo(pluginScopedCase.expectedSubresource());
assertThat(requestInfo.getSubName())
.isEqualTo(pluginScopedCase.expectedSubName());
}
@Test
@ -174,11 +179,11 @@ public class RequestInfoResolverTest {
List<PolicyRule> rules = List.of(
new PolicyRule.Builder().apiGroups("").resources("posts").verbs("list", "get")
.build(),
new PolicyRule.Builder().pluginName("fakePlugin").apiGroups("").resources("posts")
.verbs("list", "get").build(),
new PolicyRule.Builder().pluginName("fakePlugin").apiGroups("")
.resources("posts/tags").verbs("list", "get").build(),
new PolicyRule.Builder().apiGroups("").resources("categories").verbs("*").build(),
new PolicyRule.Builder().apiGroups("plugin.api.halo.run").resources("plugins/users")
.resourceNames("foo/bar").verbs("*").build(),
new PolicyRule.Builder().apiGroups("plugin.api.halo.run").resources("plugins/users")
.resourceNames("foo").verbs("*").build(),
new PolicyRule.Builder().nonResourceURLs("/healthy").verbs("get", "post", "head")
.build());
role.setRules(rules);
@ -230,14 +235,17 @@ public class RequestInfoResolverTest {
new RequestResolveCase("/api/v1/posts", "DELETE", false),
new RequestResolveCase("/api/v1/posts/aName", "UPDATE", false),
// plugin resource
new RequestResolveCase("/api/v1/plugins/fakePlugin/posts", "GET", true),
new RequestResolveCase("/api/v1/plugins/fakePlugin/posts/some-name", "GET", true),
new RequestResolveCase("/api/v1/plugins/fakePlugin/posts/some-name/tags", "GET", true),
// group resource url
new RequestResolveCase("/apis/group/v1/posts", "GET", false),
// plugin custom resource url
new RequestResolveCase("/apis/plugin.api.halo.run/v1alpha1/plugins/foo/users", "GET",
true),
new RequestResolveCase("/apis/plugin.api.halo.run/v1alpha1/plugins/foo/users/bar",
"GET", true),
new RequestResolveCase("/apis/plugin.api.halo.run/v1alpha1/plugins/foo/posts/bar",
"GET", false),
// non resource url
new RequestResolveCase("/healthy", "GET", true),
new RequestResolveCase("/healthy", "POST", true),
@ -255,40 +263,47 @@ public class RequestInfoResolverTest {
public record SuccessCase(String method, String url, String expectedVerb,
String expectedAPIPrefix, String expectedAPIGroup,
String expectedAPIVersion, String expectedPluginName,
String expectedAPIVersion, String expectedNamespace,
String expectedResource, String expectedSubresource,
String expectedName, String[] expectedParts) {
}
public record CustomSuccessCase(String method, String url, String expectedVerb,
String expectedAPIPrefix, String expectedAPIGroup,
String expectedAPIVersion, String expectedNamespace,
String expectedResource, String expectedSubresource,
String expectedName, String expectedSubName,
String[] expectedParts) {
}
List<SuccessCase> getTestRequestInfos() {
String namespaceAll = "*";
return List.of(
new SuccessCase("GET", "/api/v1/plugins", "list", "api", "", "v1", "", "plugins",
"", "", new String[] {"plugins"}),
new SuccessCase("GET", "/api/v1/plugins/other", "get", "api", "", "v1", "other",
"plugins", "", "other", new String[] {"plugins", "other"}),
new SuccessCase("GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces",
"", "", new String[] {"namespaces"}),
new SuccessCase("GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other",
"namespaces", "", "other", new String[] {"namespaces", "other"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts", "list", "api", "", "v1",
new SuccessCase("GET", "/api/v1/namespaces/other/posts", "list", "api", "", "v1",
"other", "posts", "", "", new String[] {"posts"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo", "get", "api", "", "v1",
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo", "get", "api", "", "v1",
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
new SuccessCase("HEAD", "/api/v1/plugins/other/posts/foo", "get", "api", "", "v1",
new SuccessCase("HEAD", "/api/v1/namespaces/other/posts/foo", "get", "api", "", "v1",
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
new SuccessCase("GET", "/api/v1/posts", "list", "api", "", "v1", namespaceAll, "posts",
"", "", new String[] {"posts"}),
new SuccessCase("HEAD", "/api/v1/posts", "list", "api", "", "v1", namespaceAll, "posts",
"", "", new String[] {"posts"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo", "get", "api", "", "v1",
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo", "get", "api", "", "v1",
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts", "list", "api", "", "v1",
new SuccessCase("GET", "/api/v1/namespaces/other/posts", "list", "api", "", "v1",
"other", "posts", "", "", new String[] {"posts"}),
// special verbs
new SuccessCase("GET", "/api/v1/proxy/plugins/other/posts/foo", "proxy", "api", "",
new SuccessCase("GET", "/api/v1/proxy/namespaces/other/posts/foo", "proxy", "api", "",
"v1", "other", "posts", "", "foo", new String[] {"posts", "foo"}),
new SuccessCase("GET",
"/api/v1/proxy/plugins/other/posts/foo/subpath/not/a/subresource", "proxy",
"/api/v1/proxy/namespaces/other/posts/foo/subpath/not/a/subresource", "proxy",
"api", "", "v1", "other", "posts", "", "foo",
new String[] {"posts", "foo", "subpath", "not", "a", "subresource"}),
new SuccessCase("GET", "/api/v1/watch/posts", "watch", "api", "", "v1", namespaceAll,
@ -297,35 +312,55 @@ public class RequestInfoResolverTest {
namespaceAll, "posts", "", "", new String[] {"posts"}),
new SuccessCase("GET", "/api/v1/posts?watch=false", "list", "api", "", "v1",
namespaceAll, "posts", "", "", new String[] {"posts"}),
new SuccessCase("GET", "/api/v1/watch/plugins/other/posts", "watch", "api", "", "v1",
new SuccessCase("GET", "/api/v1/watch/namespaces/other/posts", "watch", "api", "", "v1",
"other", "posts", "", "", new String[] {"posts"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts?watch=1", "watch", "api", "",
new SuccessCase("GET", "/api/v1/namespaces/other/posts?watch=true", "watch", "api", "",
"v1", "other", "posts", "", "", new String[] {"posts"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts?watch=0", "list", "api", "",
new SuccessCase("GET", "/api/v1/namespaces/other/posts?watch=false", "list", "api", "",
"v1", "other", "posts", "", "", new String[] {"posts"}),
// subresource identification
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo/status", "get", "api", "",
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo/status", "get", "api", "",
"v1", "other", "posts", "status", "foo", new String[] {"posts", "foo", "status"}),
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo/proxy/subpath", "get", "api",
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo/proxy/subpath", "get", "api",
"", "v1", "other", "posts", "proxy", "foo",
new String[] {"posts", "foo", "proxy", "subpath"}),
new SuccessCase("PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1",
"other", "namespaces", "finalize", "other",
new String[] {"namespaces", "other", "finalize"}),
new SuccessCase("PUT", "/api/v1/namespaces/other/status", "update", "api", "", "v1",
"other", "namespaces", "status", "other",
new String[] {"namespaces", "other", "status"}),
// verb identification
new SuccessCase("PATCH", "/api/v1/plugins/other/posts/foo", "patch", "api", "", "v1",
new SuccessCase("PATCH", "/api/v1/namespaces/other/posts/foo", "patch", "api", "", "v1",
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
new SuccessCase("DELETE", "/api/v1/plugins/other/posts/foo", "delete", "api", "",
new SuccessCase("DELETE", "/api/v1/namespaces/other/posts/foo", "delete", "api", "",
"v1", "other", "posts", "", "foo", new String[] {"posts", "foo"}),
new SuccessCase("POST", "/api/v1/plugins/other/posts", "create", "api", "", "v1",
new SuccessCase("POST", "/api/v1/namespaces/other/posts", "create", "api", "", "v1",
"other", "posts", "", "", new String[] {"posts"}),
// deletecollection verb identification
new SuccessCase("DELETE", "/api/v1/nodes", "deletecollection", "api", "", "v1", "",
new SuccessCase("DELETE", "/api/v1/nodes?all=true", "deletecollection", "api", "", "v1",
"",
"nodes", "", "", new String[] {"nodes"}),
new SuccessCase("DELETE", "/api/v1/plugins", "deletecollection", "api", "", "v1", "",
"plugins", "", "", new String[] {"plugins"}),
new SuccessCase("DELETE", "/api/v1/plugins/other/posts", "deletecollection", "api",
"", "v1", "other", "posts", "", "", new String[] {"posts"}));
new SuccessCase("DELETE", "/api/v1/namespaces?all=false", "delete", "api", "",
"v1", "",
"namespaces", "", "", new String[] {"namespaces"}),
new SuccessCase("DELETE", "/api/v1/namespaces/other/posts?all=true", "deletecollection",
"api",
"", "v1", "other", "posts", "", "", new String[] {"posts"}),
new SuccessCase("DELETE", "/apis/extensions/v1/namespaces/other/posts?all=true",
"deletecollection", "apis", "extensions", "v1", "other", "posts", "", "",
new String[] {"posts"}),
// api group identification
new SuccessCase("POST", "/apis/extensions/v1/namespaces/other/posts", "create", "apis",
"extensions", "v1", "other", "posts", "", "", new String[] {"posts"}),
// api version identification
new SuccessCase("POST", "/apis/extensions/v1beta3/namespaces/other/posts", "create",
"apis", "extensions", "v1beta3", "other", "posts", "", "", new String[] {"posts"}));
}
}