Skip to content

Commit

Permalink
Fix eclipse-jkube#382: Add support for merging fragment Route spec wi…
Browse files Browse the repository at this point in the history
…th default generated Route
  • Loading branch information
rohanKanojia committed Sep 25, 2020
1 parent e36a9b0 commit 10df4d9
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 97 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Usage:
```
### 1.0.1-SNAPSHOT
* Fix #381: Remove root as default user in AssemblyConfigurationUtils#getAssemblyConfigurationOrCreateDefault
* Fix #382: Add support for merging fragment Route spec with default generated Route
* Fix #358: Prometheus is enabled by default, opt-out via AB_PROMETHEUS_OFF required to disable (like in FMP)
* Fix #384: Enricher defined Container environment variables get merged with vars defined in Image Build Configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ private KubernetesResourceUtil() { }
.withCronJobVersion(CRONJOB_VERSION)
.withRbacVersioning(RBAC_VERSION);

public static final Set<Class<?>> SIMPLE_FIELD_TYPES = new HashSet<>();

public static final String CONTAINER_NAME_REGEX = "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$";

protected static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssX";
Expand Down Expand Up @@ -566,7 +564,7 @@ public static void mergeSimpleFields(Object targetValues, Object defaultValues)
}

Class<?> fieldType = targetGetMethod.getReturnType();
if (!SIMPLE_FIELD_TYPES.contains(fieldType)) {
if (!isSimpleFieldType(fieldType)) {
continue;
}

Expand Down Expand Up @@ -727,6 +725,27 @@ private static void ensureHasPort(Container container, ContainerPort port) {
ports.add(port);
}

private static boolean isSimpleFieldType(Class<?> type) {
ArrayList<Class<?>> simpleFieldTypes = new ArrayList<>();
simpleFieldTypes.add(String.class);
simpleFieldTypes.add(Double.class);
simpleFieldTypes.add(Float.class);
simpleFieldTypes.add(Long.class);
simpleFieldTypes.add(Integer.class);
simpleFieldTypes.add(Short.class);
simpleFieldTypes.add(Character.class);
simpleFieldTypes.add(Byte.class);
simpleFieldTypes.add(double.class);
simpleFieldTypes.add(float.class);
simpleFieldTypes.add(long.class);
simpleFieldTypes.add(int.class);
simpleFieldTypes.add(short.class);
simpleFieldTypes.add(char.class);
simpleFieldTypes.add(byte.class);

return simpleFieldTypes.contains(type);
}

public static String getSourceUrlAnnotation(HasMetadata item) {
return KubernetesHelper.getOrCreateAnnotations(item).get(Constants.RESOURCE_SOURCE_URL_ANNOTATION);
}
Expand Down Expand Up @@ -965,7 +984,7 @@ private static void mergeMetadata(PodTemplateSpec item1, PodTemplateSpec item2)
}
}

protected static void mergeMetadata(HasMetadata item1, HasMetadata item2) {
public static void mergeMetadata(HasMetadata item1, HasMetadata item2) {
if (item1 != null && item2 != null) {
ObjectMeta metadata1 = item1.getMetadata();
ObjectMeta metadata2 = item2.getMetadata();
Expand All @@ -983,16 +1002,24 @@ protected static void mergeMetadata(HasMetadata item1, HasMetadata item2) {
* when overriding
*/
private static Map<String, String> mergeMapsAndRemoveEmptyStrings(Map<String, String> overrideMap, Map<String, String> originalMap) {
Map<String, String> answer = MapUtil.mergeMaps(overrideMap, originalMap);
Set<Map.Entry<String, String>> entries = overrideMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
String value = entry.getValue();
if (value == null || value.isEmpty()) {
String key = entry.getKey();
answer.remove(key);
if (overrideMap == null && originalMap == null) {
return Collections.emptyMap();
} else if (originalMap == null) {
return overrideMap;
} else if (overrideMap == null) {
return originalMap;
} else {
Map<String, String> answer = MapUtil.mergeMaps(overrideMap, originalMap);
Set<Map.Entry<String, String>> entries = overrideMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
String value = entry.getValue();
if (value == null || value.isEmpty()) {
String key = entry.getKey();
answer.remove(key);
}
}
return answer;
}
return answer;
}

// lets use presence of an image name as a clue that we are just enriching things a little
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,22 +166,4 @@ private List<String> getContainersFromPodSpec(PodTemplateSpec spec) {
return containerNames;
}

static {
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(String.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Double.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Float.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Long.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Integer.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Short.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Character.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(Byte.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(double.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(float.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(long.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(int.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(short.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(char.class);
KubernetesResourceUtil.SIMPLE_FIELD_TYPES.add(byte.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
*/
package org.eclipse.jkube.enricher.generic.openshift;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.openshift.api.model.RouteSpec;
import org.eclipse.jkube.kit.common.Configs;
import org.eclipse.jkube.kit.common.util.FileUtil;
import org.eclipse.jkube.kit.config.resource.JKubeAnnotations;
Expand All @@ -40,6 +40,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil;

import static org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil.containsLabelInMetadata;
import static org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil.removeLabel;
Expand Down Expand Up @@ -81,25 +82,18 @@ public void create(PlatformMode platformMode, final KubernetesListBuilder listBu
}

if(platformMode == PlatformMode.openshift && isGenerateRoute()) {
final List<Route> routes = new ArrayList<>();
listBuilder.accept(new TypedVisitor<ServiceBuilder>() {

@Override
public void visit(ServiceBuilder serviceBuilder) {
addRoute(listBuilder, serviceBuilder, routes);
addRoute(listBuilder, serviceBuilder);
}
});

if (!routes.isEmpty()) {
Route[] routeArray = new Route[routes.size()];
routes.toArray(routeArray);
listBuilder.addToItems(routeArray);
}
}
}


private RoutePort createRoutePort(ServiceBuilder serviceBuilder) {
private static RoutePort createRoutePort(ServiceBuilder serviceBuilder) {
RoutePort routePort = null;
ServiceSpec spec = serviceBuilder.buildSpec();
if (spec != null) {
Expand Down Expand Up @@ -157,58 +151,111 @@ private boolean hasExactlyOneServicePort(ServiceBuilder service, String id) {
}
}

private void addRoute(KubernetesListBuilder listBuilder, ServiceBuilder serviceBuilder, List<Route> routes) {
private void addRoute(KubernetesListBuilder listBuilder, ServiceBuilder serviceBuilder) {
ObjectMeta serviceMetadata = serviceBuilder.buildMetadata();

if (serviceMetadata != null && StringUtils.isNotBlank(serviceMetadata.getName())
&& hasExactlyOneServicePort(serviceBuilder, serviceMetadata.getName()) && isExposedService(serviceMetadata)) {
String name = serviceMetadata.getName();
if (!hasRoute(listBuilder, name)) {
if (StringUtils.isNotBlank(routeDomainPostfix)) {
routeDomainPostfix = prepareHostForRoute(routeDomainPostfix, name);
} else {
routeDomainPostfix = "";
updateRouteDomainPostFixBasedOnServiceName(name);
Route opinionatedRoute = createOpinionatedRouteFromService(serviceBuilder, routeDomainPostfix);
if (opinionatedRoute != null) {
int routeFromFragmentIndex = getRouteIndexWithName(listBuilder, name);
if (routeFromFragmentIndex > 0) { // Merge fragment with Opinionated Route
Route routeFragment = (Route) listBuilder.buildItems().get(routeFromFragmentIndex);
Route mergedRoute = mergeRoute(routeFragment, opinionatedRoute);
KubernetesResourceUtil.removeItemFromKubernetesBuilder(listBuilder, listBuilder.getItems().get(routeFromFragmentIndex));
listBuilder.addToItems(mergedRoute);
} else { // No fragment provided. Use Opinionated Route.
listBuilder.addToItems(opinionatedRoute);
}
}
}
}

RoutePort routePort = createRoutePort(serviceBuilder);
if (routePort != null) {
RouteBuilder routeBuilder = new RouteBuilder().
withMetadata(serviceMetadata).
withNewSpec().
withPort(routePort).
withNewTo().withKind("Service").withName(name).endTo().
withHost(routeDomainPostfix.isEmpty() ? null : routeDomainPostfix).
endSpec();

// removing `expose : true` label from metadata.
removeLabel(routeBuilder.buildMetadata(), EXPOSE_LABEL, "true");
removeLabel(routeBuilder.buildMetadata(), JKubeAnnotations.SERVICE_EXPOSE_URL.value(), "true");
routeBuilder.withNewMetadataLike(routeBuilder.buildMetadata());
routes.add(routeBuilder.build());
}
private void updateRouteDomainPostFixBasedOnServiceName(String serviceName) {
if (StringUtils.isNotBlank(routeDomainPostfix)) {
routeDomainPostfix = prepareHostForRoute(routeDomainPostfix, serviceName);
} else {
routeDomainPostfix = "";
}
}

static Route mergeRoute(Route routeFromFragment, Route opinionatedRoute) {
// Update ApiVersion to route.openshift.io/v1
if (routeFromFragment.getApiVersion().equals("v1")) {
routeFromFragment.setApiVersion(opinionatedRoute.getApiVersion());
}

// Merge metadata
KubernetesResourceUtil.mergeMetadata(routeFromFragment, opinionatedRoute);

// Merge spec
if (routeFromFragment.getSpec() != null) {
routeFromFragment.setSpec(mergeRouteSpec(routeFromFragment.getSpec(), opinionatedRoute.getSpec()));
} else {
routeFromFragment.setSpec(opinionatedRoute.getSpec());
}
return routeFromFragment;
}

static RouteSpec mergeRouteSpec(RouteSpec fragmentSpec, RouteSpec opinionatedSpec) {
KubernetesResourceUtil.mergeSimpleFields(fragmentSpec, opinionatedSpec);
if (fragmentSpec.getAlternateBackends() == null && opinionatedSpec.getAlternateBackends() != null) {
fragmentSpec.setAlternateBackends(opinionatedSpec.getAlternateBackends());
}
if (fragmentSpec.getPort() == null && opinionatedSpec.getPort() != null) {
fragmentSpec.setPort(opinionatedSpec.getPort());
}
if (fragmentSpec.getTls() == null && opinionatedSpec.getTls() != null) {
fragmentSpec.setTls(opinionatedSpec.getTls());
}
if (fragmentSpec.getTo() == null && opinionatedSpec.getTo() != null) {
fragmentSpec.setTo(opinionatedSpec.getTo());
}

return fragmentSpec;
}

static int getRouteIndexWithName(final KubernetesListBuilder listBuilder, final String name) {
int routeInListIndex = -1;
for (int index = 0; index < listBuilder.buildItems().size(); index++) {
HasMetadata item = listBuilder.getItems().get(index);
if (item != null &&
item.getMetadata() != null &&
item.getMetadata().getName().equals(name) &&
item instanceof Route) {
routeInListIndex = index;
}
}
return routeInListIndex;
}

/**
* Returns true if we already have a route created for the given name
*/
private boolean hasRoute(final KubernetesListBuilder listBuilder, final String name) {
final AtomicBoolean answer = new AtomicBoolean(false);
listBuilder.accept(new TypedVisitor<RouteBuilder>() {
static Route createOpinionatedRouteFromService(ServiceBuilder serviceBuilder, String routeDomainPostfix) {
ObjectMeta serviceMetadata = serviceBuilder.buildMetadata();
if (serviceMetadata != null) {
String name = serviceMetadata.getName();
RoutePort routePort = createRoutePort(serviceBuilder);
if (routePort != null) {
RouteBuilder routeBuilder = new RouteBuilder().
withMetadata(serviceMetadata).
withNewSpec().
withPort(routePort).
withNewTo().withKind("Service").withName(name).endTo().
withHost(routeDomainPostfix.isEmpty() ? null : routeDomainPostfix).
endSpec();

@Override
public void visit(RouteBuilder builder) {
ObjectMeta metadata = builder.buildMetadata();
if (metadata != null && name.equals(metadata.getName())) {
answer.set(true);
}
// removing `expose : true` label from metadata.
removeLabel(routeBuilder.buildMetadata(), EXPOSE_LABEL, "true");
removeLabel(routeBuilder.buildMetadata(), JKubeAnnotations.SERVICE_EXPOSE_URL.value(), "true");
routeBuilder.withNewMetadataLike(routeBuilder.buildMetadata());
return routeBuilder.build();
}
});
return answer.get();
}
return null;
}

private static boolean isExposedService(ObjectMeta objectMeta) {
static boolean isExposedService(ObjectMeta objectMeta) {
return containsLabelInMetadata(objectMeta, EXPOSE_LABEL, "true") ||
containsLabelInMetadata(objectMeta, JKubeAnnotations.SERVICE_EXPOSE_URL.value(), "true");
}
Expand Down

0 comments on commit 10df4d9

Please sign in to comment.