From d179f8d8ccd6fe92c09450e919d15af7788fffb0 Mon Sep 17 00:00:00 2001 From: Eric Zimanyi Date: Thu, 12 Sep 2019 12:49:56 -0400 Subject: [PATCH] refactor(kubernetes): Clean up ArtifactReplacer (#4020) * test(kubernetes): Fix ArtifactReplacer tests We should be using when/then instead of expect * refactor(kubernetes): Make ArtifactReplacer immutable We always create an ArtifactReplacer then immediately call registerReplacer a number of times to register all replacers. Replace this with just passing the replacers into the constructor. * refactor(kubernetes): Delete unused static variables At some point in the past, this class was refactored and all references to these static variables were removed, but the variables themselves were never removed. Remove them. * refactor(kubernetes): Replace static methods with constants As the Replacer class is immutable, it is safe to just return the same Replacer ever time we need one. Let's replace the static methods to generate a replacer with static constants. * refactor(kubernetes): Improve encapsulation of Replacer class The ArtifactReplacer and Replacer classes don't clearly define a boundary of responsibility; this commit is a few changes to disentangle the classes a bit, as well as make a few minor other improvements to them. In particular: * Replace stream .filter() and .map() operations that have side- effects with forEach * Add nullity annotations where appropriate * Remove the unused (always null) namePattern field from replacer * Consolidate the API of Replacer down to getArtifacts and ReplaceArtifacts * refactor(kubernetes): Move Replacer class to top level This is a reasonably complex class to be an inner class, which reduces its ability to encapsulate its complexity. This commit is just moving the existing class, with no other changes. * refactor(kubernetes): Make Replacer builder private Defining a replacer is reasonably complex, so ideally this would only be done by the class itself, exposing useful replacers for consumers. Move the one replacer defined outside the class into the class, and make the builder private. * refactor(kubernetes): Address PR review comments * Have ArtifactReplacer accept a Collection instead of an ImmutableList * Replace public static replacers with static methods --- .../v2/artifact/ArtifactReplacer.java | 159 ++-------- .../v2/artifact/ArtifactReplacerFactory.java | 162 ---------- .../kubernetes/v2/artifact/Replacer.java | 283 ++++++++++++++++++ .../view/model/KubernetesV2ServerGroup.java | 12 +- .../op/handler/KubernetesCronJobHandler.java | 22 +- .../handler/KubernetesDaemonSetHandler.java | 22 +- .../handler/KubernetesDeploymentHandler.java | 22 +- .../v2/op/handler/KubernetesHandler.java | 16 +- ...ernetesHorizontalPodAutoscalerHandler.java | 10 +- .../v2/op/handler/KubernetesJobHandler.java | 23 +- .../v2/op/handler/KubernetesPodHandler.java | 15 +- .../handler/KubernetesReplicaSetHandler.java | 23 +- .../handler/KubernetesStatefulSetHandler.java | 23 +- .../v2/artifact/ArtifactReplacerSpec.groovy | 19 +- 14 files changed, 428 insertions(+), 383 deletions(-) delete mode 100644 clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerFactory.java create mode 100644 clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/Replacer.java diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacer.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacer.java index 6e5e280fc44..513b94fc15f 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacer.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacer.java @@ -17,37 +17,31 @@ package com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; + import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; -import com.netflix.spinnaker.clouddriver.artifacts.kubernetes.KubernetesArtifactType; import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest; import com.netflix.spinnaker.kork.artifacts.model.Artifact; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; +@ParametersAreNonnullByDefault @Slf4j public class ArtifactReplacer { private static final ObjectMapper mapper = new ObjectMapper(); @@ -57,14 +51,13 @@ public class ArtifactReplacer { .mappingProvider(new JacksonMappingProvider()) .build(); - private final List replacers = new ArrayList<>(); + private final ImmutableList replacers; - public ArtifactReplacer addReplacer(Replacer replacer) { - replacers.add(replacer); - return this; + public ArtifactReplacer(Collection replacers) { + this.replacers = ImmutableList.copyOf(replacers); } - private static List filterKubernetesArtifactsByNamespaceAndAccount( + private static ImmutableList filterKubernetesArtifactsByNamespaceAndAccount( String namespace, String account, List artifacts) { return artifacts.stream() // Keep artifacts that either aren't k8s, or are in the same namespace and account as our @@ -98,14 +91,14 @@ private static List filterKubernetesArtifactsByNamespaceAndAccount( return accountMatches && locationMatches; }) - .collect(Collectors.toList()); + .collect(toImmutableList()); } + @Nonnull public ReplaceResult replaceAll( KubernetesManifest input, List artifacts, String namespace, String account) { log.debug("Doing replacement on {} using {}", input, artifacts); - // final to use in below lambda - final List finalArtifacts = + ImmutableList filteredArtifacts = filterKubernetesArtifactsByNamespaceAndAccount(namespace, account, artifacts); DocumentContext document; try { @@ -115,26 +108,23 @@ public ReplaceResult replaceAll( throw new RuntimeException(e); } - Set replacedArtifacts = - replacers.stream() - .map( - r -> - finalArtifacts.stream() - .filter(a -> r.replaceIfPossible(document, a)) - .collect(Collectors.toSet())) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + ImmutableSet.Builder replacedArtifacts = new ImmutableSet.Builder<>(); + replacers.forEach( + replacer -> + replacedArtifacts.addAll(replacer.replaceArtifacts(document, filteredArtifacts))); try { return new ReplaceResult( - mapper.readValue(document.jsonString(), KubernetesManifest.class), replacedArtifacts); + mapper.readValue(document.jsonString(), KubernetesManifest.class), + replacedArtifacts.build()); } catch (IOException e) { log.error("Malformed Document Context", e); throw new RuntimeException(e); } } - public Set findAll(KubernetesManifest input) { + @Nonnull + public ImmutableSet findAll(KubernetesManifest input) { DocumentContext document; try { document = JsonPath.using(configuration).parse(mapper.writeValueAsString(input)); @@ -146,25 +136,7 @@ public Set findAll(KubernetesManifest input) { .map( r -> { try { - return ((List) - mapper.convertValue( - r.findAll(document), new TypeReference>() {})) - .stream() - .map( - s -> { - String nameFromReference = r.getNameFromReference(s); - String name = nameFromReference == null ? s : nameFromReference; - if (r.namePattern == null || nameFromReference != null) { - return Artifact.builder() - .type(r.getType().getType()) - .reference(s) - .name(name) - .build(); - } else { - return null; - } - }) - .filter(Objects::nonNull); + return r.getArtifacts(document); } catch (Exception e) { // This happens when a manifest isn't fully defined (e.g. not all properties are // there) @@ -173,89 +145,16 @@ public Set findAll(KubernetesManifest input) { input.getFullResourceName(), r, e); - return Stream.empty(); + return Collections.emptyList(); } }) - .flatMap(x -> x) - .collect(Collectors.toSet()); - } - - @Slf4j - @Builder - @AllArgsConstructor - public static class Replacer { - private final String replacePath; - private final String findPath; - private final Pattern namePattern; // the first group should be the artifact name - private final Function nameFromReference; - - @Getter private final KubernetesArtifactType type; - - private static String substituteField(String result, String fieldName, String field) { - field = field == null ? "" : field; - return result.replace("{%" + fieldName + "%}", field); - } - - private static String processPath(String path, Artifact artifact) { - String result = substituteField(path, "name", artifact.getName()); - result = substituteField(result, "type", artifact.getType()); - result = substituteField(result, "version", artifact.getVersion()); - result = substituteField(result, "reference", artifact.getReference()); - return result; - } - - ArrayNode findAll(DocumentContext obj) { - return obj.read(findPath); - } - - String getNameFromReference(String reference) { - if (nameFromReference != null) { - return nameFromReference.apply(reference); - } else if (namePattern != null) { - Matcher m = namePattern.matcher(reference); - if (m.find() && m.groupCount() > 0 && StringUtils.isNotEmpty(m.group(1))) { - return m.group(1); - } else { - return null; - } - } else { - return null; - } - } - - boolean replaceIfPossible(DocumentContext obj, Artifact artifact) { - if (artifact == null || StringUtils.isEmpty(artifact.getType())) { - throw new IllegalArgumentException("Artifact and artifact type must be set."); - } - - if (!artifact.getType().equals(type.getType())) { - return false; - } - - String jsonPath = processPath(replacePath, artifact); - - log.debug("Processed jsonPath == {}", jsonPath); - - Object get; - try { - get = obj.read(jsonPath); - } catch (PathNotFoundException e) { - return false; - } - if (get == null || (get instanceof ArrayNode && ((ArrayNode) get).size() == 0)) { - return false; - } - - log.info("Found valid swap for " + artifact + " using " + jsonPath + ": " + get); - obj.set(jsonPath, artifact.getReference()); - - return true; - } + .flatMap(Collection::stream) + .collect(toImmutableSet()); } @Value public static class ReplaceResult { private final KubernetesManifest manifest; - private final Set boundArtifacts; + private final ImmutableSet boundArtifacts; } } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerFactory.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerFactory.java deleted file mode 100644 index 8c6c903de8d..00000000000 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerFactory.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2018 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.artifact; - -import com.netflix.spinnaker.clouddriver.artifacts.kubernetes.KubernetesArtifactType; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer.Replacer; -import java.util.regex.Pattern; - -public class ArtifactReplacerFactory { - // The following was derived from - // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go - private static final String DOCKER_NAME_COMPONENT = - "[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?"; - private static final String DOCKER_OPTIONAL_TAG = "(?::[\\w][\\w.-]{0,127})?"; - private static final String DOCKER_OPTIONAL_DIGEST = - "(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][0-9A-Fa-f]{32,})?"; - private static final String DOCKER_DOMAIN = - "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:(?:\\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?"; - private static final String DOCKER_OPTIONAL_PORT = "(?::[0-9]+)?"; - private static final String DOCKER_OPTIONAL_DOMAIN_AND_PORT = - "(?:" + DOCKER_DOMAIN + DOCKER_OPTIONAL_PORT + "/)?"; - private static final String DOCKER_IMAGE_NAME = - "(" - + DOCKER_OPTIONAL_DOMAIN_AND_PORT - + DOCKER_NAME_COMPONENT - + "(?:/" - + DOCKER_NAME_COMPONENT - + ")*)"; - private static final String DOCKER_IMAGE_REFERENCE = - DOCKER_IMAGE_NAME + "(" + DOCKER_OPTIONAL_TAG + "|" + DOCKER_OPTIONAL_DIGEST + ")"; - - // the image reference pattern has two capture groups. - // - the first captures the image name - // - the second captures the image tag (including the leading ":") or digest (including the - // leading "@"). - public static final Pattern DOCKER_IMAGE_REFERENCE_PATTERN = - Pattern.compile("^" + DOCKER_IMAGE_REFERENCE + "$"); - - public static Replacer dockerImageReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec['containers', 'initContainers'].[?( @.image == \"{%name%}\" )].image") - .findPath("$..spec.template.spec['containers', 'initContainers'].*.image") - .nameFromReference( - ref -> { - int atIndex = ref.indexOf('@'); - // @ can only show up in image references denoting a digest - // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go#L70 - if (atIndex >= 0) { - return ref.substring(0, atIndex); - } - - // : can be used to denote a port, part of a digest (already matched) or a tag - // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go#L69 - int lastColonIndex = ref.lastIndexOf(':'); - - if (lastColonIndex < 0) { - return ref; - } - - // we don't need to check if this is a tag, or a port. ports will be matched lazily if - // they are numeric, and are treated as tags first: - // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go#L34 - return ref.substring(0, lastColonIndex); - }) - .type(KubernetesArtifactType.DockerImage) - .build(); - } - - public static Replacer configMapVolumeReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec.volumes.[?( @.configMap.name == \"{%name%}\" )].configMap.name") - .findPath("$..spec.template.spec.volumes.*.configMap.name") - .type(KubernetesArtifactType.ConfigMap) - .build(); - } - - public static Replacer secretVolumeReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec.volumes.[?( @.secret.secretName == \"{%name%}\" )].secret.secretName") - .findPath("$..spec.template.spec.volumes.*.secret.secretName") - .type(KubernetesArtifactType.Secret) - .build(); - } - - public static Replacer configMapKeyValueFromReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec['containers', 'initContainers'].*.env.[?( @.valueFrom.configMapKeyRef.name == \"{%name%}\" )].valueFrom.configMapKeyRef.name") - .findPath( - "$..spec.template.spec['containers', 'initContainers'].*.env.*.valueFrom.configMapKeyRef.name") - .type(KubernetesArtifactType.ConfigMap) - .build(); - } - - public static Replacer secretKeyValueFromReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec['containers', 'initContainers'].*.env.[?( @.valueFrom.secretKeyRef.name == \"{%name%}\" )].valueFrom.secretKeyRef.name") - .findPath( - "$..spec.template.spec['containers', 'initContainers'].*.env.*.valueFrom.secretKeyRef.name") - .type(KubernetesArtifactType.Secret) - .build(); - } - - public static Replacer configMapEnvFromReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec['containers', 'initContainers'].*.envFrom.[?( @.configMapRef.name == \"{%name%}\" )].configMapRef.name") - .findPath( - "$..spec.template.spec['containers', 'initContainers'].*.envFrom.*.configMapRef.name") - .type(KubernetesArtifactType.ConfigMap) - .build(); - } - - public static Replacer secretEnvFromReplacer() { - return Replacer.builder() - .replacePath( - "$..spec.template.spec['containers', 'initContainers'].*.envFrom.[?( @.secretRef.name == \"{%name%}\" )].secretRef.name") - .findPath( - "$..spec.template.spec['containers', 'initContainers'].*.envFrom.*.secretRef.name") - .type(KubernetesArtifactType.Secret) - .build(); - } - - public static Replacer hpaDeploymentReplacer() { - return Replacer.builder() - .replacePath( - "$[?( (@.spec.scaleTargetRef.kind == \"Deployment\" || @.spec.scaleTargetRef.kind == \"deployment\") && @.spec.scaleTargetRef.name == \"{%name%}\" )].spec.scaleTargetRef.name") - .findPath( - "$[?( @.spec.scaleTargetRef.kind == \"Deployment\" || @.spec.scaleTargetRef.kind == \"deployment\" )].spec.scaleTargetRef.name") - .type(KubernetesArtifactType.Deployment) - .build(); - } - - public static Replacer hpaReplicaSetReplacer() { - return Replacer.builder() - .replacePath( - "$[?( (@.spec.scaleTargetRef.kind == \"ReplicaSet\" || @.spec.scaleTargetRef.kind == \"replicaSet\") && @.spec.scaleTargetRef.name == \"{%name%}\" )].spec.scaleTargetRef.name") - .findPath( - "$[?( @.spec.scaleTargetRef.kind == \"ReplicaSet\" || @.spec.scaleTargetRef.kind == \"replicaSet\" )].spec.scaleTargetRef.name") - .type(KubernetesArtifactType.ReplicaSet) - .build(); - } -} diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/Replacer.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/Replacer.java new file mode 100644 index 00000000000..144cc3450f3 --- /dev/null +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/Replacer.java @@ -0,0 +1,283 @@ +/* + * Copyright 2019 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.artifact; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.PathNotFoundException; +import com.netflix.spinnaker.clouddriver.artifacts.kubernetes.KubernetesArtifactType; +import com.netflix.spinnaker.kork.artifacts.model.Artifact; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; + +@Builder(access = AccessLevel.PRIVATE) +@ParametersAreNonnullByDefault +@Slf4j +public class Replacer { + private static final ObjectMapper mapper = new ObjectMapper(); + + @Nonnull private final String replacePath; + @Nonnull private final String findPath; + @Nullable private final Function nameFromReference; + @Nonnull private final KubernetesArtifactType type; + + private static String substituteField(String result, String fieldName, @Nullable String field) { + return result.replace("{%" + fieldName + "%}", Optional.ofNullable(field).orElse("")); + } + + private static String processPath(String path, Artifact artifact) { + String result = substituteField(path, "name", artifact.getName()); + result = substituteField(result, "type", artifact.getType()); + result = substituteField(result, "version", artifact.getVersion()); + result = substituteField(result, "reference", artifact.getReference()); + return result; + } + + private ArrayNode findAll(DocumentContext obj) { + return obj.read(findPath); + } + + @Nonnull + private Artifact artifactFromReference(String s) { + return Artifact.builder().type(type.getType()).reference(s).name(nameFromReference(s)).build(); + } + + @Nonnull + private String nameFromReference(String s) { + if (nameFromReference != null) { + return nameFromReference.apply(s); + } else { + return s; + } + } + + @Nonnull + ImmutableCollection getArtifacts(DocumentContext document) { + return mapper + .>convertValue(findAll(document), new TypeReference>() {}) + .stream() + .map(this::artifactFromReference) + .collect(toImmutableList()); + } + + @Nonnull + ImmutableCollection replaceArtifacts( + DocumentContext obj, Collection artifacts) { + ImmutableSet.Builder replacedArtifacts = new ImmutableSet.Builder<>(); + artifacts.forEach( + artifact -> { + boolean wasReplaced = replaceIfPossible(obj, artifact); + if (wasReplaced) { + replacedArtifacts.add(artifact); + } + }); + return replacedArtifacts.build(); + } + + private boolean replaceIfPossible(DocumentContext obj, @Nullable Artifact artifact) { + if (artifact == null || StringUtils.isEmpty(artifact.getType())) { + throw new IllegalArgumentException("Artifact and artifact type must be set."); + } + + if (!artifact.getType().equals(type.getType())) { + return false; + } + + String jsonPath = processPath(replacePath, artifact); + + log.debug("Processed jsonPath == {}", jsonPath); + + Object get; + try { + get = obj.read(jsonPath); + } catch (PathNotFoundException e) { + return false; + } + if (get == null || (get instanceof ArrayNode && ((ArrayNode) get).size() == 0)) { + return false; + } + + log.info("Found valid swap for " + artifact + " using " + jsonPath + ": " + get); + obj.set(jsonPath, artifact.getReference()); + + return true; + } + + private static final Replacer DOCKER_IMAGE = + builder() + .replacePath( + "$..spec.template.spec['containers', 'initContainers'].[?( @.image == \"{%name%}\" )].image") + .findPath("$..spec.template.spec['containers', 'initContainers'].*.image") + .nameFromReference( + ref -> { + int atIndex = ref.indexOf('@'); + // @ can only show up in image references denoting a digest + // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go#L70 + if (atIndex >= 0) { + return ref.substring(0, atIndex); + } + + // : can be used to denote a port, part of a digest (already matched) or a tag + // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go#L69 + int lastColonIndex = ref.lastIndexOf(':'); + + if (lastColonIndex < 0) { + return ref; + } + + // we don't need to check if this is a tag, or a port. ports will be matched lazily + // if + // they are numeric, and are treated as tags first: + // https://github.com/docker/distribution/blob/95daa793b83a21656fe6c13e6d5cf1c3999108c7/reference/regexp.go#L34 + return ref.substring(0, lastColonIndex); + }) + .type(KubernetesArtifactType.DockerImage) + .build(); + private static final Replacer POD_DOCKER_IMAGE = + builder() + .replacePath("$.spec.containers.[?( @.image == \"{%name%}\" )].image") + .findPath("$.spec.containers.*.image") + .type(KubernetesArtifactType.DockerImage) + .build(); + private static final Replacer CONFIG_MAP_VOLUME = + builder() + .replacePath( + "$..spec.template.spec.volumes.[?( @.configMap.name == \"{%name%}\" )].configMap.name") + .findPath("$..spec.template.spec.volumes.*.configMap.name") + .type(KubernetesArtifactType.ConfigMap) + .build(); + private static final Replacer SECRET_VOLUME = + builder() + .replacePath( + "$..spec.template.spec.volumes.[?( @.secret.secretName == \"{%name%}\" )].secret.secretName") + .findPath("$..spec.template.spec.volumes.*.secret.secretName") + .type(KubernetesArtifactType.Secret) + .build(); + private static final Replacer CONFIG_MAP_KEY_VALUE = + builder() + .replacePath( + "$..spec.template.spec['containers', 'initContainers'].*.env.[?( @.valueFrom.configMapKeyRef.name == \"{%name%}\" )].valueFrom.configMapKeyRef.name") + .findPath( + "$..spec.template.spec['containers', 'initContainers'].*.env.*.valueFrom.configMapKeyRef.name") + .type(KubernetesArtifactType.ConfigMap) + .build(); + private static final Replacer SECRET_KEY_VALUE = + builder() + .replacePath( + "$..spec.template.spec['containers', 'initContainers'].*.env.[?( @.valueFrom.secretKeyRef.name == \"{%name%}\" )].valueFrom.secretKeyRef.name") + .findPath( + "$..spec.template.spec['containers', 'initContainers'].*.env.*.valueFrom.secretKeyRef.name") + .type(KubernetesArtifactType.Secret) + .build(); + private static final Replacer CONFIG_MAP_ENV = + builder() + .replacePath( + "$..spec.template.spec['containers', 'initContainers'].*.envFrom.[?( @.configMapRef.name == \"{%name%}\" )].configMapRef.name") + .findPath( + "$..spec.template.spec['containers', 'initContainers'].*.envFrom.*.configMapRef.name") + .type(KubernetesArtifactType.ConfigMap) + .build(); + private static final Replacer SECRET_ENV = + builder() + .replacePath( + "$..spec.template.spec['containers', 'initContainers'].*.envFrom.[?( @.secretRef.name == \"{%name%}\" )].secretRef.name") + .findPath( + "$..spec.template.spec['containers', 'initContainers'].*.envFrom.*.secretRef.name") + .type(KubernetesArtifactType.Secret) + .build(); + private static final Replacer HPA_DEPLOYMENT = + builder() + .replacePath( + "$[?( (@.spec.scaleTargetRef.kind == \"Deployment\" || @.spec.scaleTargetRef.kind == \"deployment\") && @.spec.scaleTargetRef.name == \"{%name%}\" )].spec.scaleTargetRef.name") + .findPath( + "$[?( @.spec.scaleTargetRef.kind == \"Deployment\" || @.spec.scaleTargetRef.kind == \"deployment\" )].spec.scaleTargetRef.name") + .type(KubernetesArtifactType.Deployment) + .build(); + private static final Replacer HPA_REPLICA_SET = + builder() + .replacePath( + "$[?( (@.spec.scaleTargetRef.kind == \"ReplicaSet\" || @.spec.scaleTargetRef.kind == \"replicaSet\") && @.spec.scaleTargetRef.name == \"{%name%}\" )].spec.scaleTargetRef.name") + .findPath( + "$[?( @.spec.scaleTargetRef.kind == \"ReplicaSet\" || @.spec.scaleTargetRef.kind == \"replicaSet\" )].spec.scaleTargetRef.name") + .type(KubernetesArtifactType.ReplicaSet) + .build(); + + @Nonnull + public static Replacer dockerImage() { + return DOCKER_IMAGE; + } + + @Nonnull + public static Replacer podDockerImage() { + return POD_DOCKER_IMAGE; + } + + @Nonnull + public static Replacer configMapVolume() { + return CONFIG_MAP_VOLUME; + } + + @Nonnull + public static Replacer secretVolume() { + return SECRET_VOLUME; + } + + @Nonnull + public static Replacer configMapKeyValue() { + return CONFIG_MAP_KEY_VALUE; + } + + @Nonnull + public static Replacer secretKeyValue() { + return SECRET_KEY_VALUE; + } + + @Nonnull + public static Replacer configMapEnv() { + return CONFIG_MAP_ENV; + } + + @Nonnull + public static Replacer secretEnv() { + return SECRET_ENV; + } + + @Nonnull + public static Replacer hpaDeployment() { + return HPA_DEPLOYMENT; + } + + @Nonnull + public static Replacer hpaReplicaSet() { + return HPA_REPLICA_SET; + } +} diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/caching/view/model/KubernetesV2ServerGroup.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/caching/view/model/KubernetesV2ServerGroup.java index 2d070d3e978..a2b110e9b31 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/caching/view/model/KubernetesV2ServerGroup.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/caching/view/model/KubernetesV2ServerGroup.java @@ -20,12 +20,13 @@ import static java.util.Collections.singletonList; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Ints; import com.netflix.spinnaker.cats.cache.CacheData; import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesCloudProvider; import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.view.provider.data.KubernetesV2ServerGroupCacheData; @@ -69,12 +70,9 @@ public class KubernetesV2ServerGroup extends ManifestBasedModel implements Serve private KubernetesManifest manifest; private Keys.InfrastructureCacheKey key; - @JsonIgnore private static final ArtifactReplacer dockerImageReplacer; - - static { - dockerImageReplacer = new ArtifactReplacer(); - dockerImageReplacer.addReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - } + @JsonIgnore + private static final ArtifactReplacer dockerImageReplacer = + new ArtifactReplacer(ImmutableList.of(Replacer.dockerImage())); @Override public ServerGroup.InstanceCounts getInstanceCounts() { diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesCronJobHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesCronJobHandler.java index d67a1199ae3..48886bfe2c0 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesCronJobHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesCronJobHandler.java @@ -19,7 +19,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_CONTROLLER_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgentFactory; @@ -36,14 +37,17 @@ public class KubernetesCronJobHandler extends KubernetesHandler implements CanDelete, ServerGroupHandler { - public KubernetesCronJobHandler() { - registerReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.secretVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapKeyValueFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretKeyValueFromReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of( + Replacer.dockerImage(), + Replacer.configMapVolume(), + Replacer.secretVolume(), + Replacer.configMapEnv(), + Replacer.secretEnv(), + Replacer.configMapKeyValue(), + Replacer.secretKeyValue()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDaemonSetHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDaemonSetHandler.java index 601dc021311..6b9fad98e9a 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDaemonSetHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDaemonSetHandler.java @@ -19,7 +19,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_CONTROLLER_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; @@ -39,14 +40,17 @@ public class KubernetesDaemonSetHandler extends KubernetesHandler implements CanResize, CanPauseRollout, CanResumeRollout, CanUndoRollout, ServerGroupHandler { - public KubernetesDaemonSetHandler() { - registerReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.secretVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapKeyValueFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretKeyValueFromReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of( + Replacer.dockerImage(), + Replacer.configMapVolume(), + Replacer.secretVolume(), + Replacer.configMapEnv(), + Replacer.secretEnv(), + Replacer.configMapKeyValue(), + Replacer.secretKeyValue()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDeploymentHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDeploymentHandler.java index 2e4e2e10ada..635f9fc94c7 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDeploymentHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDeploymentHandler.java @@ -22,7 +22,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesApiVersion.EXTENSIONS_V1BETA1; import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_CONTROLLER_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgentFactory; @@ -45,14 +46,17 @@ public class KubernetesDeploymentHandler extends KubernetesHandler CanUndoRollout, ServerGroupManagerHandler { - public KubernetesDeploymentHandler() { - registerReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.secretVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapKeyValueFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretKeyValueFromReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of( + Replacer.dockerImage(), + Replacer.configMapVolume(), + Replacer.secretVolume(), + Replacer.configMapEnv(), + Replacer.secretEnv(), + Replacer.configMapKeyValue(), + Replacer.secretKeyValue()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHandler.java index 07b3d47baa8..cb5eac8f490 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHandler.java @@ -19,10 +19,13 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.netflix.spectator.api.Registry; import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesNamedAccountCredentials; import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer.ReplaceResult; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgent; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgentFactory; @@ -44,7 +47,11 @@ public abstract class KubernetesHandler implements CanDeploy, CanDelete, CanPatch { protected static final ObjectMapper objectMapper = new ObjectMapper(); - private final ArtifactReplacer artifactReplacer = new ArtifactReplacer(); + private final ArtifactReplacer artifactReplacer; + + public KubernetesHandler() { + this.artifactReplacer = new ArtifactReplacer(artifactReplacers()); + } public abstract int deployPriority(); @@ -66,8 +73,9 @@ protected List sensitiveKeys() { return new ArrayList<>(); } - protected void registerReplacer(ArtifactReplacer.Replacer replacer) { - artifactReplacer.addReplacer(replacer); + @Nonnull + protected ImmutableList artifactReplacers() { + return ImmutableList.of(); } public ReplaceResult replaceArtifacts( @@ -82,7 +90,7 @@ public ReplaceResult replaceArtifacts( protected abstract KubernetesV2CachingAgentFactory cachingAgentFactory(); - public Set listArtifacts(KubernetesManifest manifest) { + public ImmutableSet listArtifacts(KubernetesManifest manifest) { return artifactReplacer.findAll(manifest); } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHorizontalPodAutoscalerHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHorizontalPodAutoscalerHandler.java index 8e4ded6c780..26d1b02db0c 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHorizontalPodAutoscalerHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesHorizontalPodAutoscalerHandler.java @@ -19,7 +19,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_ATTACHMENT_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgentFactory; import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesSpinnakerKindMap.SpinnakerKind; @@ -31,9 +32,10 @@ @Component public class KubernetesHorizontalPodAutoscalerHandler extends KubernetesHandler { - public KubernetesHorizontalPodAutoscalerHandler() { - registerReplacer(ArtifactReplacerFactory.hpaDeploymentReplacer()); - registerReplacer(ArtifactReplacerFactory.hpaReplicaSetReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of(Replacer.hpaDeployment(), Replacer.hpaReplicaSet()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesJobHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesJobHandler.java index 61c9e520d62..cb4de721de1 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesJobHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesJobHandler.java @@ -19,7 +19,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_CONTROLLER_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgentFactory; @@ -39,15 +40,17 @@ @Component public class KubernetesJobHandler extends KubernetesHandler implements ServerGroupHandler { - - public KubernetesJobHandler() { - registerReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.secretVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapKeyValueFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretKeyValueFromReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of( + Replacer.dockerImage(), + Replacer.configMapVolume(), + Replacer.secretVolume(), + Replacer.configMapEnv(), + Replacer.secretEnv(), + Replacer.configMapKeyValue(), + Replacer.secretKeyValue()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesPodHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesPodHandler.java index 0231dea66a7..ec7827cb794 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesPodHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesPodHandler.java @@ -19,8 +19,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_PRIORITY; -import com.netflix.spinnaker.clouddriver.artifacts.kubernetes.KubernetesArtifactType; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; @@ -38,13 +38,10 @@ @Component public class KubernetesPodHandler extends KubernetesHandler { - public KubernetesPodHandler() { - registerReplacer( - ArtifactReplacer.Replacer.builder() - .replacePath("$.spec.containers.[?( @.image == \"{%name%}\" )].image") - .findPath("$.spec.containers.*.image") - .type(KubernetesArtifactType.DockerImage) - .build()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of(Replacer.podDockerImage()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesReplicaSetHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesReplicaSetHandler.java index da0e45abc11..a12265410ae 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesReplicaSetHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesReplicaSetHandler.java @@ -21,7 +21,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesApiVersion.EXTENSIONS_V1BETA1; import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_CONTROLLER_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; @@ -46,15 +47,17 @@ @Component public class KubernetesReplicaSetHandler extends KubernetesHandler implements CanResize, CanScale, HasPods, ServerGroupHandler { - - public KubernetesReplicaSetHandler() { - registerReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.secretVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapKeyValueFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretKeyValueFromReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of( + Replacer.dockerImage(), + Replacer.configMapVolume(), + Replacer.secretVolume(), + Replacer.configMapEnv(), + Replacer.secretEnv(), + Replacer.configMapKeyValue(), + Replacer.secretKeyValue()); } @Override diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesStatefulSetHandler.java b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesStatefulSetHandler.java index 84a1ff19117..3fb22893a98 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesStatefulSetHandler.java +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesStatefulSetHandler.java @@ -21,7 +21,8 @@ import static com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind.STATEFUL_SET; import static com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler.DeployPriority.WORKLOAD_CONTROLLER_PRIORITY; -import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacerFactory; +import com.google.common.collect.ImmutableList; +import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter; import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent; @@ -52,15 +53,17 @@ public class KubernetesStatefulSetHandler extends KubernetesHandler CanResumeRollout, CanUndoRollout, ServerGroupHandler { - - public KubernetesStatefulSetHandler() { - registerReplacer(ArtifactReplacerFactory.dockerImageReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.secretVolumeReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretEnvFromReplacer()); - registerReplacer(ArtifactReplacerFactory.configMapKeyValueFromReplacer()); - registerReplacer(ArtifactReplacerFactory.secretKeyValueFromReplacer()); + @Nonnull + @Override + protected ImmutableList artifactReplacers() { + return ImmutableList.of( + Replacer.dockerImage(), + Replacer.configMapVolume(), + Replacer.secretVolume(), + Replacer.configMapEnv(), + Replacer.secretEnv(), + Replacer.configMapKeyValue(), + Replacer.secretKeyValue()); } @Override diff --git a/clouddriver-kubernetes/src/test/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerSpec.groovy b/clouddriver-kubernetes/src/test/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerSpec.groovy index b1d797a9f96..0a9ee30d3e9 100644 --- a/clouddriver-kubernetes/src/test/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerSpec.groovy +++ b/clouddriver-kubernetes/src/test/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/artifact/ArtifactReplacerSpec.groovy @@ -1,6 +1,7 @@ package com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact import com.fasterxml.jackson.databind.ObjectMapper +import com.google.common.collect.ImmutableList import com.netflix.spinnaker.clouddriver.artifacts.kubernetes.KubernetesArtifactType import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest import com.netflix.spinnaker.kork.artifacts.model.Artifact @@ -33,8 +34,7 @@ spec: kind: Deployment name: $name """ - def artifactReplacer = new ArtifactReplacer() - artifactReplacer.addReplacer(ArtifactReplacerFactory.hpaDeploymentReplacer()) + def artifactReplacer = new ArtifactReplacer(ImmutableList.of(Replacer.hpaDeployment())) def manifest = stringToManifest(hpaManifest) def artifacts = artifactReplacer.findAll(manifest) @@ -60,8 +60,7 @@ spec: kind: UNKNOWN name: $name """ - def artifactReplacer = new ArtifactReplacer() - artifactReplacer.addReplacer(ArtifactReplacerFactory.hpaDeploymentReplacer()) + def artifactReplacer = new ArtifactReplacer(ImmutableList.of(Replacer.hpaDeployment())) def manifest = stringToManifest(hpaManifest) def artifacts = artifactReplacer.findAll(manifest) @@ -71,7 +70,7 @@ spec: @Unroll def "correctly extracts Docker artifacts from image names"() { - expect: + when: def deploymentManifest = """ apiVersion: apps/v1 kind: Deployment @@ -95,11 +94,11 @@ spec: ports: - containerPort: 80 """ - def artifactReplacer = new ArtifactReplacer() - artifactReplacer.addReplacer(ArtifactReplacerFactory.dockerImageReplacer()) + def artifactReplacer = new ArtifactReplacer(ImmutableList.of(Replacer.dockerImage())) def manifest = stringToManifest(deploymentManifest) def artifacts = artifactReplacer.findAll(manifest) + then: artifacts.size() == 1 Artifact artifact = artifacts.toList().get(0) artifact.getType() == KubernetesArtifactType.DockerImage.type @@ -124,7 +123,7 @@ spec: @Unroll def "correctly extracts Docker artifacts from image names in initContainers"() { - expect: + when: def deploymentManifest = """ apiVersion: apps/v1 kind: Deployment @@ -148,11 +147,11 @@ spec: ports: - containerPort: 80 """ - def artifactReplacer = new ArtifactReplacer() - artifactReplacer.addReplacer(ArtifactReplacerFactory.dockerImageReplacer()) + def artifactReplacer = new ArtifactReplacer(ImmutableList.of(Replacer.dockerImage())) def manifest = stringToManifest(deploymentManifest) def artifacts = artifactReplacer.findAll(manifest) + then: artifacts.size() == 1 Artifact artifact = artifacts.toList().get(0) artifact.getType() == KubernetesArtifactType.DockerImage.type