Skip to content

Commit

Permalink
fix fabric8io#3216: further refining the mock logic
Browse files Browse the repository at this point in the history
added awareness of apiVersion
switched to using plural rather than kind
  • Loading branch information
shawkins authored and manusa committed Jun 11, 2021
1 parent ac7ad10 commit c05afa7
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 149 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Fix #3144: walking back the assumption that resource/status should be a subresource for the crud mock server, now it will be only if a registered crd indicates that it should be
* Fix #3194: the mock server will now infer the namespace from the path
* Fix #3076: the MetadataObject for CustomResource is now seen as Buildable
* Fix #3216: made the mock server aware of apiVersions

#### Improvements
* Fix #3078: adding javadocs to further clarify patch, edit, replace, etc. and note the possibility of items being modified.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ public static <T> T unmarshal(InputStream is, ObjectMapper mapper, Map<String, S
throw KubernetesClientException.launderThrowable(e);
}
}

/**
* Unmarshals a {@link String}
* @param str The {@link String}.
* @param <T> template argument denoting type
* @return returns de-serialized object
*/
public static<T> T unmarshal(String str) {
try (InputStream is = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))) {
return unmarshal(is);
} catch (IOException e) {
throw KubernetesClientException.launderThrowable(e);
}
}

/**
* Unmarshals a {@link String}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;
import io.fabric8.kubernetes.client.utils.Serialization;

import java.util.Optional;
import java.util.Map;

/**
* Holds state related to crds by manipulating the crds known to the attributes extractor
Expand Down Expand Up @@ -49,23 +49,19 @@ public void process(String path, String crdString, boolean delete) {
return;
}
if (delete) {
extractor.getCrdContexts().remove(context.getPlural());
extractor.removeCrdContext(context);
} else {
extractor.getCrdContexts().put(context.getPlural(), context);
extractor.addCrdContext(context);
}
}

public boolean isStatusSubresource(String kind) {
if (kind == null) {
public boolean isStatusSubresource(Map<String, String> pathValues) {
CustomResourceDefinitionContext context = extractor.getCrdContext(pathValues.get(KubernetesAttributesExtractor.API),
pathValues.get(KubernetesAttributesExtractor.VERSION), pathValues.get(KubernetesAttributesExtractor.PLURAL));
if (context == null) {
return false;
}
// we are only holding by plural, lookup now by kind
Optional<CustomResourceDefinitionContext> context =
extractor.getCrdContexts().values().stream().filter(c -> kind.equalsIgnoreCase(c.getKind())).findFirst();
if (!context.isPresent()) {
return false;
}
return context.get().isStatusSubresource();
return context.isStatusSubresource();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;
import io.fabric8.kubernetes.client.utils.ApiVersionUtil;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.fabric8.kubernetes.client.utils.Utils;
import io.fabric8.mockwebserver.crud.Attribute;
Expand All @@ -28,16 +28,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -53,9 +50,14 @@ public class KubernetesAttributesExtractor implements AttributeExtractor<HasMeta

public static final String KEY = "key";
public static final String KIND = "kind";
public static final String API = "api";
public static final String VERSION = "version";
public static final String NAME = "name";
public static final String NAMESPACE = "namespace";
public static final String VALUE = "value";
public static final String PLURAL = "plural";

public static final String UNKNOWN_KIND = "%unknown";

private static final String API_GROUP = "/o?api(s/[a-zA-Z0-9-_.]+)?";
private static final String VERSION_GROUP = "(/(?<version>[a-zA-Z0-9-_]+))?";
Expand Down Expand Up @@ -83,15 +85,23 @@ public class KubernetesAttributesExtractor implements AttributeExtractor<HasMeta
// values are not important, HttpUrl expects the scheme to be http or https.
private static final String SCHEME = "http";
private static final String HOST = "localhost";
private Map<String, CustomResourceDefinitionContext> crdContexts;
private Map<String, String> pluralToKind = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

private Map<List<String>, CustomResourceDefinitionContext> crdContexts;

public KubernetesAttributesExtractor() {
this(Collections.emptyList());
}

public KubernetesAttributesExtractor(List<CustomResourceDefinitionContext> crdContexts) {
this.crdContexts = crdContexts.stream().collect(Collectors.toMap(CustomResourceDefinitionContext::getPlural, Function.identity()));
this.crdContexts = crdContexts.stream().collect(Collectors.toMap(c -> pluralKey(c), Function.identity()));
}

private static List<String> pluralKey(CustomResourceDefinitionContext c) {
return pluralKey(c.getGroup(), c.getVersion(), c.getPlural());
}

private static List<String> pluralKey(String api, String version, String plural) {
return Arrays.asList(api, version, plural);
}

private HttpUrl parseUrlFromPathAndQuery(String s) {
Expand All @@ -102,7 +112,7 @@ private HttpUrl parseUrlFromPathAndQuery(String s) {
}

/**
* Get the name, namespace, and kind from the path
* Get the name, namespace, api, version, plural, and kind from the path
*/
public Map<String, String> fromKubernetesPath(String s) {
if (s == null || s.isEmpty()) {
Expand Down Expand Up @@ -170,6 +180,19 @@ public AttributeSet extract(String s) {
@Override
public AttributeSet extract(HasMetadata hasMetadata) {
AttributeSet metadataAttributes = new AttributeSet();
String apiVersion = hasMetadata.getApiVersion();
String api = null;
String version = null;
if (!Utils.isNullOrEmpty(apiVersion)) {
api = ApiVersionUtil.trimGroup(apiVersion);
version = ApiVersionUtil.trimVersion(apiVersion);
if (!api.equals(apiVersion)) {
metadataAttributes = metadataAttributes.add(new Attribute(API, api));
} else {
api = null;
}
metadataAttributes = metadataAttributes.add(new Attribute(VERSION, version));
}
if (!Utils.isNullOrEmpty(hasMetadata.getMetadata().getName())) {
metadataAttributes = metadataAttributes.add(new Attribute(NAME, hasMetadata.getMetadata().getName()));
}
Expand All @@ -183,25 +206,42 @@ public AttributeSet extract(HasMetadata hasMetadata) {
metadataAttributes = metadataAttributes.add(new Attribute(LABEL_KEY_PREFIX + label.getKey(), label.getValue()));
}
}
if (!Utils.isNullOrEmpty(hasMetadata.getKind())) {
String kind = hasMetadata.getKind().toLowerCase(Locale.ROOT);
metadataAttributes = metadataAttributes.add(new Attribute(KIND, kind));
if (hasMetadata instanceof GenericKubernetesResource) {
pluralToKind.put(getPluralForKind(hasMetadata.getKind(), hasMetadata.getApiVersion()), kind);
} else {
pluralToKind.put(hasMetadata.getPlural(), kind);
}
if (!(hasMetadata instanceof GenericKubernetesResource)) {
metadataAttributes = metadataAttributes.add(new Attribute(PLURAL, hasMetadata.getPlural()));
} else {
Optional<CustomResourceDefinitionContext> context = findCrd(api, version);
if (context.isPresent()) {
metadataAttributes = metadataAttributes.add(new Attribute(PLURAL, context.get().getPlural()));
} // else we shouldn't infer the plural without a crd registered - it will come from the path instead
}
return metadataAttributes;
}

private Optional<CustomResourceDefinitionContext> findCrd(String api, String version) {
return crdContexts.values()
.stream()
.filter(c -> Objects.equals(api, c.getGroup()) && Objects.equals(version, c.getVersion()))
.findFirst();
}

private Map<String, String> extract(Matcher m) {
Map<String, String> attributes = new HashMap<>();
if (m.matches()) {

String api = m.group(1);
if (api != null) {
api = api.substring(2);
attributes.put(API, api);
}

String version = m.group(VERSION);
if (!Utils.isNullOrEmpty(version)) {
attributes.put(VERSION, version);
}

String kind = m.group(KIND);
if (!Utils.isNullOrEmpty(kind)) {
kind = resolveKindFromPlural(kind);
attributes.put(KIND, kind);
attributes.put(PLURAL, kind);
}

String namespace = m.group(NAMESPACE);
Expand All @@ -221,31 +261,6 @@ private Map<String, String> extract(Matcher m) {
return attributes;
}

private String resolveKindFromPlural(String plural) {
String result = getCustomResourceKindFromPlural(plural);
if (result != null) {
return result;
}
return pluralToKind.getOrDefault(plural, plural.substring(0, plural.length() - 1));
}

/**
* Find the plural for standard types by consulting the deserializer
*/
private static String getPluralForKind(String kind, String apiVersion) {
GenericKubernetesResource gkr = new GenericKubernetesResource();
gkr.setApiVersion(apiVersion);
gkr.setKind(kind);
try {
HasMetadata result = Serialization.unmarshal(new ByteArrayInputStream(Serialization.asJson(gkr).getBytes(StandardCharsets.UTF_8)));
if (result != null) {
return result.getPlural();
}
} catch (KubernetesClientException e) {
}
return kind + "s";
}

private static AttributeSet extractQueryParameters(HttpUrl url) {
AttributeSet attributes = new AttributeSet();
String labelSelector = url.queryParameter("labelSelector");
Expand Down Expand Up @@ -287,23 +302,23 @@ private static Attribute parseLabel(String label) {
return null;
}

static GenericKubernetesResource toKubernetesResource(String s) {
try (InputStream stream = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8))) {
return Serialization.unmarshal(stream, GenericKubernetesResource.class);
} catch (IOException e) {
throw new RuntimeException(e); // unexpected
static HasMetadata toKubernetesResource(String s) {
HasMetadata result = Serialization.unmarshal(s);
if (result == null) {
throw new IllegalArgumentException("Required value: kind and apiVersion are required");
}
return result;
}

private String getCustomResourceKindFromPlural(String plural) {
CustomResourceDefinitionContext crdContext = crdContexts.get(plural);
return crdContext != null && crdContext.getKind() != null ? crdContext.getKind().toLowerCase(Locale.ROOT) : null;
public CustomResourceDefinitionContext getCrdContext(String api, String version, String plural) {
return this.crdContexts.get(pluralKey(api, version, plural));
}

/**
* A mapping of plural name to context
*/
public Map<String, CustomResourceDefinitionContext> getCrdContexts() {
return crdContexts;
public void removeCrdContext(CustomResourceDefinitionContext context) {
this.crdContexts.remove(pluralKey(context));
}

public void addCrdContext(CustomResourceDefinitionContext context) {
this.crdContexts.put(pluralKey(context), context);
}
}
Loading

0 comments on commit c05afa7

Please sign in to comment.