/*
 * Copyright 2018, Strimzi authors.
 * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
 */
package io.strimzi.controller.cluster.operator.resource;

import com.fasterxml.jackson.databind.JsonNode;
import io.fabric8.kubernetes.api.model.extensions.StatefulSet;
import io.fabric8.zjsonpatch.JsonDiff;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static io.fabric8.kubernetes.client.internal.PatchUtils.patchMapper;
import static java.util.Arrays.asList;

public class StatefulSetDiff {

    private static final Logger log = LoggerFactory.getLogger(StatefulSetDiff.class.getName());

    private static final List<Pattern> IGNORABLE_PATHS;
    static {
        IGNORABLE_PATHS = asList(
            "/spec/revisionHistoryLimit",
            "/spec/template/spec/containers/0/imagePullPolicy",
            "/spec/template/spec/containers/0/livenessProbe/failureThreshold",
            "/spec/template/spec/containers/0/livenessProbe/periodSeconds",
            "/spec/template/spec/containers/0/livenessProbe/successThreshold",
            "/spec/template/spec/containers/0/readinessProbe/failureThreshold",
            "/spec/template/spec/containers/0/readinessProbe/periodSeconds",
            "/spec/template/spec/containers/0/readinessProbe/successThreshold",
            "/spec/template/spec/containers/0/resources",
            "/spec/template/spec/containers/0/terminationMessagePath",
            "/spec/template/spec/containers/0/terminationMessagePolicy",
            "/spec/template/spec/dnsPolicy",
            "/spec/template/spec/restartPolicy",
            "/spec/template/spec/schedulerName",
            "/spec/template/spec/securityContext",
            "/spec/template/spec/terminationGracePeriodSeconds",
            "/spec/template/spec/volumes/[0-9]+/configMap/defaultMode",
            "/status").stream().map(Pattern::compile).collect(Collectors.toList());
    }

    private static boolean containsPathOrChild(Iterable<String> paths, String path) {
        for (String pathValue : paths) {
            if (equalsOrPrefix(path, pathValue)) {
                return true;
            }
        }
        return false;
    }

    private static boolean equalsOrPrefix(String path, String pathValue) {
        return pathValue.equals(path)
                || pathValue.startsWith(path + "/");
    }

    private final boolean changesVolumeClaimTemplate;
    private final boolean isEmpty;
    private final boolean changesSpecTemplateSpec;
    private final boolean changesLabels;
    private final boolean changesSpecReplicas;

    public StatefulSetDiff(StatefulSet current, StatefulSet updated) {
        JsonNode diff = JsonDiff.asJson(patchMapper().valueToTree(current), patchMapper().valueToTree(updated));
        Set<String> paths = new HashSet<>();
        outer: for (JsonNode d : diff) {
            String pathValue = d.get("path").asText();
            for (Pattern pattern : IGNORABLE_PATHS) {
                if (pattern.matcher(pathValue).matches()) {
                    log.debug("StatefulSet {}/{} ignoring diff {}", current.getMetadata().getNamespace(), current.getMetadata().getName(), d);
                    continue outer;
                }
            }
            log.debug("StatefulSet {}/{} differs at path {}", current.getMetadata().getNamespace(), current.getMetadata().getName(), pathValue);
            paths.add(pathValue);
        }
        isEmpty = paths.isEmpty();
        changesVolumeClaimTemplate = containsPathOrChild(paths, "/spec/volumeClaimTemplates");
        // Change changes to /spec/template/spec, except to imagePullPolicy, which gets changed
        // by k8s
        changesSpecTemplateSpec = containsPathOrChild(paths, "/spec/template/spec");
        changesLabels = containsPathOrChild(paths, "/metadata/labels");
        changesSpecReplicas = containsPathOrChild(paths, "/spec/replicas");
    }

    public boolean isEmpty() {
        return isEmpty;
    }

    public boolean changesVolumeClaimTemplates() {
        return changesVolumeClaimTemplate;
    }

    public boolean changesSpecTemplateSpec() {
        return changesSpecTemplateSpec;
    }

    public boolean changesLabels() {
        return changesLabels;
    }

    public boolean changesSpecReplicas() {
        return changesSpecReplicas;
    }
}

@@ -1998,24 +1998,80 @@
 faultMode%22,%0A
+            %22/spec/volumeClaimTemplates/%5B0-9%5D+/status%22,%0A
             

Add /spec/volumeClaimTemplates/0/status to list of ignorable paths (#366)