Java tutorial
/* * Copyright 2017 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.frigga.Names; import com.netflix.spinnaker.kork.artifacts.model.Artifact; import com.netflix.spinnaker.moniker.Moniker; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Map; import java.util.Optional; @Slf4j public class KubernetesManifestAnnotater { private static final String SPINNAKER_ANNOTATION = "spinnaker.io"; private static final String RELATIONSHIP_ANNOTATION_PREFIX = "relationships." + SPINNAKER_ANNOTATION; private static final String ARTIFACT_ANNOTATION_PREFIX = "artifact." + SPINNAKER_ANNOTATION; private static final String MONIKER_ANNOTATION_PREFIX = "moniker." + SPINNAKER_ANNOTATION; private static final String CACHING_ANNOTATION_PREFIX = "caching." + SPINNAKER_ANNOTATION; private static final String STRATEGY_ANNOTATION_PREFIX = "strategy." + SPINNAKER_ANNOTATION; private static final String LOAD_BALANCERS = RELATIONSHIP_ANNOTATION_PREFIX + "/loadBalancers"; private static final String SECURITY_GROUPS = RELATIONSHIP_ANNOTATION_PREFIX + "/securityGroups"; private static final String CLUSTER = MONIKER_ANNOTATION_PREFIX + "/cluster"; private static final String APPLICATION = MONIKER_ANNOTATION_PREFIX + "/application"; private static final String STACK = MONIKER_ANNOTATION_PREFIX + "/stack"; private static final String DETAIL = MONIKER_ANNOTATION_PREFIX + "/detail"; private static final String SEQUENCE = MONIKER_ANNOTATION_PREFIX + "/sequence"; private static final String TYPE = ARTIFACT_ANNOTATION_PREFIX + "/type"; private static final String NAME = ARTIFACT_ANNOTATION_PREFIX + "/name"; private static final String LOCATION = ARTIFACT_ANNOTATION_PREFIX + "/location"; private static final String VERSION = ARTIFACT_ANNOTATION_PREFIX + "/version"; private static final String IGNORE_CACHING = CACHING_ANNOTATION_PREFIX + "/ignore"; private static final String VERSIONED = STRATEGY_ANNOTATION_PREFIX + "/versioned"; private static final String MAX_VERSION_HISTORY = STRATEGY_ANNOTATION_PREFIX + "/max-version-history"; private static final String USE_SOURCE_CAPACITY = STRATEGY_ANNOTATION_PREFIX + "/use-source-capacity"; private static final String KUBERNETES_ANNOTATION = "kubernetes.io"; private static final String KUBECTL_ANNOTATION_PREFIX = "kubectl." + KUBERNETES_ANNOTATION; private static final String DEPLOYMENT_ANNOTATION_PREFIX = "deployment." + KUBERNETES_ANNOTATION; private static final String DEPLOYMENT_REVISION = DEPLOYMENT_ANNOTATION_PREFIX + "/revision"; private static final String KUBECTL_LAST_APPLIED_CONFIGURATION = KUBECTL_ANNOTATION_PREFIX + "/last-applied-configuration"; private static ObjectMapper objectMapper = new ObjectMapper(); private static void storeAnnotation(Map<String, String> annotations, String key, Object value) { if (value == null) { return; } if (annotations.containsKey(key)) { return; } try { if (value instanceof String) { // The "write value as string" method will attach quotes which are ugly to read annotations.put(key, (String) value); } else { annotations.put(key, objectMapper.writeValueAsString(value)); } } catch (JsonProcessingException e) { throw new IllegalArgumentException("Illegal annotation value for '" + key + "': " + e); } } private static <T> T getAnnotation(Map<String, String> annotations, String key, TypeReference<T> typeReference) { return getAnnotation(annotations, key, typeReference, null); } private static boolean stringTypeReference(TypeReference typeReference) { if (typeReference.getType() == null || typeReference.getType().getTypeName() == null) { log.warn("Malformed type reference {}", typeReference); return false; } return typeReference.getType().getTypeName().equals(String.class.getName()); } // This is to read values that were annotated with the ObjectMapper with quotes, before we started ignoring the quotes private static boolean looksLikeSerializedString(String value) { if (StringUtils.isEmpty(value) || value.length() == 1) { return false; } return value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"'; } private static <T> T getAnnotation(Map<String, String> annotations, String key, TypeReference<T> typeReference, T defaultValue) { String value = annotations.get(key); if (value == null) { return defaultValue; } try { boolean wantsString = stringTypeReference(typeReference); if (wantsString && !looksLikeSerializedString(value)) { return (T) value; } else { return objectMapper.readValue(value, typeReference); } } catch (Exception e) { log.warn("Illegally annotated resource for '" + key + "': " + e); return null; } } public static void annotateManifest(KubernetesManifest manifest, KubernetesManifestSpinnakerRelationships relationships) { Map<String, String> annotations = manifest.getAnnotations(); storeAnnotations(annotations, relationships); manifest.getSpecTemplateAnnotations().flatMap(a -> { storeAnnotations(a, relationships); return Optional.empty(); }); } public static void annotateManifest(KubernetesManifest manifest, Moniker moniker) { Map<String, String> annotations = manifest.getAnnotations(); storeAnnotations(annotations, moniker); manifest.getSpecTemplateAnnotations().flatMap(a -> { storeAnnotations(a, moniker); return Optional.empty(); }); } public static void annotateManifest(KubernetesManifest manifest, Artifact artifact) { Map<String, String> annotations = manifest.getAnnotations(); storeAnnotations(annotations, artifact); manifest.getSpecTemplateAnnotations().flatMap(a -> { storeAnnotations(a, artifact); return Optional.empty(); }); } private static void storeAnnotations(Map<String, String> annotations, Moniker moniker) { if (moniker == null) { throw new IllegalArgumentException("Every resource deployed via spinnaker must be assigned a moniker"); } storeAnnotation(annotations, CLUSTER, moniker.getCluster()); storeAnnotation(annotations, APPLICATION, moniker.getApp()); storeAnnotation(annotations, STACK, moniker.getStack()); storeAnnotation(annotations, DETAIL, moniker.getDetail()); storeAnnotation(annotations, SEQUENCE, moniker.getSequence()); } private static void storeAnnotations(Map<String, String> annotations, KubernetesManifestSpinnakerRelationships relationships) { if (relationships == null) { return; } storeAnnotation(annotations, LOAD_BALANCERS, relationships.getLoadBalancers()); storeAnnotation(annotations, SECURITY_GROUPS, relationships.getSecurityGroups()); } private static void storeAnnotations(Map<String, String> annotations, Artifact artifact) { if (artifact == null) { return; } storeAnnotation(annotations, TYPE, artifact.getType()); storeAnnotation(annotations, NAME, artifact.getName()); storeAnnotation(annotations, LOCATION, artifact.getLocation()); storeAnnotation(annotations, VERSION, artifact.getVersion()); } public static KubernetesManifestSpinnakerRelationships getManifestRelationships(KubernetesManifest manifest) { Map<String, String> annotations = manifest.getAnnotations(); return new KubernetesManifestSpinnakerRelationships() .setLoadBalancers(getAnnotation(annotations, LOAD_BALANCERS, new TypeReference<List<String>>() { })) .setSecurityGroups(getAnnotation(annotations, SECURITY_GROUPS, new TypeReference<List<String>>() { })); } public static Optional<Artifact> getArtifact(KubernetesManifest manifest) { Map<String, String> annotations = manifest.getAnnotations(); String type = getAnnotation(annotations, TYPE, new TypeReference<String>() { }); if (StringUtils.isEmpty(type)) { return Optional.empty(); } return Optional.of( Artifact.builder().type(type).name(getAnnotation(annotations, NAME, new TypeReference<String>() { })).location(getAnnotation(annotations, LOCATION, new TypeReference<String>() { })).version(getAnnotation(annotations, VERSION, new TypeReference<String>() { })).build()); } public static Moniker getMoniker(KubernetesManifest manifest) { Names parsed = Names.parseName(manifest.getName()); Map<String, String> annotations = manifest.getAnnotations(); return Moniker.builder().cluster(getAnnotation(annotations, CLUSTER, new TypeReference<String>() { }, parsed.getCluster())).app(getAnnotation(annotations, APPLICATION, new TypeReference<String>() { }, parsed.getApp())).stack(getAnnotation(annotations, STACK, new TypeReference<String>() { }, null)).detail(getAnnotation(annotations, DETAIL, new TypeReference<String>() { }, null)).sequence(getAnnotation(annotations, SEQUENCE, new TypeReference<Integer>() { }, getAnnotation(annotations, DEPLOYMENT_REVISION, new TypeReference<Integer>() { }, null))).build(); } public static KubernetesCachingProperties getCachingProperties(KubernetesManifest manifest) { Map<String, String> annotations = manifest.getAnnotations(); return KubernetesCachingProperties.builder() .ignore(getAnnotation(annotations, IGNORE_CACHING, new TypeReference<Boolean>() { }, false)).build(); } public static KubernetesManifestStrategy getStrategy(KubernetesManifest manifest) { Map<String, String> annotations = manifest.getAnnotations(); return KubernetesManifestStrategy.builder() .versioned(getAnnotation(annotations, VERSIONED, new TypeReference<Boolean>() { })).maxVersionHistory(getAnnotation(annotations, MAX_VERSION_HISTORY, new TypeReference<Integer>() { })).useSourceCapacity(getAnnotation(annotations, USE_SOURCE_CAPACITY, new TypeReference<Boolean>() { })).build(); } public static KubernetesManifest getLastAppliedConfiguration(KubernetesManifest manifest) { Map<String, String> annotations = manifest.getAnnotations(); return getAnnotation(annotations, KUBECTL_LAST_APPLIED_CONFIGURATION, new TypeReference<KubernetesManifest>() { }, null); } }