com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer.java

Source

/*
 * 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.artifact;

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.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.kubernetes.v2.description.manifest.KubernetesManifest;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class ArtifactReplacer {
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final Configuration configuration = Configuration.builder()
            .jsonProvider(new JacksonJsonNodeJsonProvider()).mappingProvider(new JacksonMappingProvider()).build();

    List<Replacer> replacers = new ArrayList<>();

    public ArtifactReplacer addReplacer(Replacer replacer) {
        replacers.add(replacer);
        return this;
    }

    public ReplaceResult replaceAll(KubernetesManifest input, List<Artifact> artifacts) {
        log.info("Doing replacement on {} using {}", input, artifacts);
        DocumentContext document;
        try {
            document = JsonPath.using(configuration).parse(mapper.writeValueAsString(input));
        } catch (JsonProcessingException e) {
            log.error("Malformed manifest", e);
            throw new RuntimeException(e);
        }

        Set<Artifact> replacedArtifacts = replacers.stream().map(
                r -> artifacts.stream().filter(a -> r.replaceIfPossible(document, a)).collect(Collectors.toSet()))
                .flatMap(Collection::stream).collect(Collectors.toSet());

        try {
            return ReplaceResult.builder()
                    .manifest(mapper.readValue(document.jsonString(), KubernetesManifest.class))
                    .boundArtifacts(replacedArtifacts).build();
        } catch (IOException e) {
            log.error("Malformed Document Context", e);
            throw new RuntimeException(e);
        }
    }

    public Set<Artifact> findAll(KubernetesManifest input) {
        DocumentContext document;
        try {
            document = JsonPath.using(configuration).parse(mapper.writeValueAsString(input));
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Malformed manifest", e);
        }

        return replacers.stream().map(r -> {
            try {
                return ((List<String>) mapper.convertValue(r.findAll(document), new TypeReference<List<String>>() {
                })).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().toString()).reference(s).name(name).build();
                    } else {
                        return null;
                    }
                }).filter(Objects::nonNull);
            } catch (Exception e) {
                // This happens when a manifest isn't fully defined (e.g. not all properties are there)
                log.debug("Failure converting artifacts for {} using {} (skipping)", input.getFullResourceName(), r,
                        e);
                return Stream.<Artifact>empty();
            }
        }).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

        @Getter
        private final ArtifactTypes 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 (namePattern == null) {
                return null;
            }

            Matcher m = namePattern.matcher(reference);
            if (m.find() && m.groupCount() > 0 && StringUtils.isNotEmpty(m.group(1))) {
                return m.group(1);
            } 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.toString())) {
                return false;
            }

            String jsonPath = processPath(replacePath, artifact);

            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;
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class ReplaceResult {
        private KubernetesManifest manifest;
        private Set<Artifact> boundArtifacts = new HashSet<>();
    }
}