com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials.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.security;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Suppliers;
import com.netflix.spectator.api.Clock;
import com.netflix.spectator.api.Registry;
import com.netflix.spinnaker.clouddriver.kubernetes.config.CustomKubernetesResource;
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.job.KubectlJobExecutor;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.job.KubectlJobExecutor.KubectlException;
import io.kubernetes.client.models.V1DeleteOptions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
public class KubernetesV2Credentials implements KubernetesCredentials {
    private final KubectlJobExecutor jobExecutor;
    private final Registry registry;
    private final Clock clock;
    private final String accountName;
    @Getter
    private final List<String> namespaces;
    @Getter
    private final List<String> omitNamespaces;
    private final List<KubernetesKind> kinds;
    private final List<KubernetesKind> omitKinds;
    @Getter
    private final boolean serviceAccount;

    // TODO(lwander) make configurable
    private final static int namespaceExpirySeconds = 30;

    private final com.google.common.base.Supplier<List<String>> liveNamespaceSupplier;

    @Getter
    private final List<CustomKubernetesResource> customResources;

    // remove when kubectl is no longer a dependency
    @Getter
    private final String kubectlExecutable;

    // remove when kubectl is no longer a dependency
    @Getter
    private final String kubeconfigFile;

    // remove when kubectl is no longer a dependency
    @Getter
    private final String context;

    @JsonIgnore
    @Getter
    private final String oAuthServiceAccount;

    @JsonIgnore
    @Getter
    private final List<String> oAuthScopes;

    private final String defaultNamespace = "default";
    private String cachedDefaultNamespace;

    private final Path serviceAccountNamespacePath = Paths
            .get("/var/run/secrets/kubernetes.io/serviceaccount/namespace");

    public boolean isValidKind(KubernetesKind kind) {
        if (!this.kinds.isEmpty()) {
            return kinds.contains(kind);
        } else if (!this.omitKinds.isEmpty()) {
            return !omitKinds.contains(kind);
        } else {
            return true;
        }
    }

    public String getDefaultNamespace() {
        if (StringUtils.isEmpty(cachedDefaultNamespace)) {
            cachedDefaultNamespace = lookupDefaultNamespace();
        }

        return cachedDefaultNamespace;
    }

    public String lookupDefaultNamespace() {
        String namespace = defaultNamespace;
        try {
            Optional<String> serviceAccountNamespace = Files
                    .lines(serviceAccountNamespacePath, StandardCharsets.UTF_8).findFirst();
            namespace = serviceAccountNamespace.orElse("");
        } catch (IOException e) {
            try {
                namespace = jobExecutor.defaultNamespace(this);
            } catch (KubectlException ke) {
                log.debug("Failure looking up desired namespace, defaulting to {}", defaultNamespace, ke);
            }
        } catch (Exception e) {
            log.debug("Error encountered looking up default namespace, defaulting to {}", defaultNamespace, e);
        }
        if (StringUtils.isEmpty(namespace)) {
            namespace = defaultNamespace;
        }
        return namespace;
    }

    @Getter
    private final boolean debug;

    public static class Builder {
        String accountName;
        String kubeconfigFile;
        String context;
        String kubectlExecutable;
        String oAuthServiceAccount;
        List<String> oAuthScopes;
        String userAgent;
        List<String> namespaces = new ArrayList<>();
        List<String> omitNamespaces = new ArrayList<>();
        Registry registry;
        KubectlJobExecutor jobExecutor;
        List<CustomKubernetesResource> customResources;
        List<String> kinds;
        List<String> omitKinds;
        boolean debug;
        boolean serviceAccount;

        public Builder accountName(String accountName) {
            this.accountName = accountName;
            return this;
        }

        public Builder kubeconfigFile(String kubeconfigFile) {
            this.kubeconfigFile = kubeconfigFile;
            return this;
        }

        public Builder kubectlExecutable(String kubectlExecutable) {
            this.kubectlExecutable = kubectlExecutable;
            return this;
        }

        public Builder context(String context) {
            this.context = context;
            return this;
        }

        public Builder userAgent(String userAgent) {
            this.userAgent = userAgent;
            return this;
        }

        public Builder namespaces(List<String> namespaces) {
            this.namespaces = namespaces;
            return this;
        }

        public Builder omitNamespaces(List<String> omitNamespaces) {
            this.omitNamespaces = omitNamespaces;
            return this;
        }

        public Builder registry(Registry registry) {
            this.registry = registry;
            return this;
        }

        public Builder jobExecutor(KubectlJobExecutor jobExecutor) {
            this.jobExecutor = jobExecutor;
            return this;
        }

        public Builder customResources(List<CustomKubernetesResource> customResources) {
            this.customResources = customResources;
            return this;
        }

        public Builder debug(boolean debug) {
            this.debug = debug;
            return this;
        }

        public Builder serviceAccount(boolean serviceAccount) {
            this.serviceAccount = serviceAccount;
            return this;
        }

        public Builder oAuthServiceAccount(String oAuthServiceAccount) {
            this.oAuthServiceAccount = oAuthServiceAccount;
            return this;
        }

        public Builder oAuthScopes(List<String> oAuthScopes) {
            this.oAuthScopes = oAuthScopes;
            return this;
        }

        public Builder kinds(List<String> kinds) {
            this.kinds = kinds;
            return this;
        }

        public Builder omitKinds(List<String> omitKinds) {
            this.omitKinds = omitKinds;
            return this;
        }

        public KubernetesV2Credentials build() {
            namespaces = namespaces == null ? new ArrayList<>() : namespaces;
            omitNamespaces = omitNamespaces == null ? new ArrayList<>() : omitNamespaces;
            customResources = customResources == null ? new ArrayList<>() : customResources;
            kinds = kinds == null ? new ArrayList<>() : kinds;
            omitKinds = omitKinds == null ? new ArrayList<>() : omitKinds;

            return new KubernetesV2Credentials(accountName, jobExecutor, namespaces, omitNamespaces, registry,
                    kubeconfigFile, kubectlExecutable, context, oAuthServiceAccount, oAuthScopes, serviceAccount,
                    customResources, KubernetesKind.fromStringList(kinds), KubernetesKind.fromStringList(omitKinds),
                    debug);
        }
    }

    private KubernetesV2Credentials(@NotNull String accountName, @NotNull KubectlJobExecutor jobExecutor,
            @NotNull List<String> namespaces, @NotNull List<String> omitNamespaces, @NotNull Registry registry,
            String kubeconfigFile, String kubectlExecutable, String context, String oAuthServiceAccount,
            List<String> oAuthScopes, boolean serviceAccount,
            @NotNull List<CustomKubernetesResource> customResources, @NotNull List<KubernetesKind> kinds,
            @NotNull List<KubernetesKind> omitKinds, boolean debug) {
        this.registry = registry;
        this.clock = registry.clock();
        this.accountName = accountName;
        this.namespaces = namespaces;
        this.omitNamespaces = omitNamespaces;
        this.jobExecutor = jobExecutor;
        this.debug = debug;
        this.kubectlExecutable = kubectlExecutable;
        this.kubeconfigFile = kubeconfigFile;
        this.context = context;
        this.oAuthServiceAccount = oAuthServiceAccount;
        this.oAuthScopes = oAuthScopes;
        this.serviceAccount = serviceAccount;
        this.customResources = customResources;
        this.kinds = kinds;
        this.omitKinds = omitKinds;

        this.liveNamespaceSupplier = Suppliers.memoizeWithExpiration(
                () -> jobExecutor.list(this, Collections.singletonList(KubernetesKind.NAMESPACE), "").stream()
                        .map(KubernetesManifest::getName).collect(Collectors.toList()),
                namespaceExpirySeconds, TimeUnit.SECONDS);
    }

    @Override
    public List<String> getDeclaredNamespaces() {
        List<String> result;
        if (!namespaces.isEmpty()) {
            result = namespaces;
        } else {
            try {
                result = liveNamespaceSupplier.get();

            } catch (KubectlException e) {
                throw new RuntimeException(e);
            }
        }

        if (!omitNamespaces.isEmpty()) {
            result = result.stream().filter(n -> !omitNamespaces.contains(n)).collect(Collectors.toList());
        }

        return result;
    }

    public KubernetesManifest get(KubernetesKind kind, String namespace, String name) {
        return runAndRecordMetrics("get", kind, namespace, () -> jobExecutor.get(this, kind, namespace, name));
    }

    public List<KubernetesManifest> list(KubernetesKind kind, String namespace) {
        return runAndRecordMetrics("list", kind, namespace,
                () -> jobExecutor.list(this, Collections.singletonList(kind), namespace));
    }

    public List<KubernetesManifest> list(List<KubernetesKind> kinds, String namespace) {
        return runAndRecordMetrics("list", kinds, namespace, () -> jobExecutor.list(this, kinds, namespace));
    }

    public String logs(String namespace, String podName, String containerName) {
        return runAndRecordMetrics("logs", KubernetesKind.POD, namespace,
                () -> jobExecutor.logs(this, namespace, podName, containerName));
    }

    public void scale(KubernetesKind kind, String namespace, String name, int replicas) {
        runAndRecordMetrics("scale", kind, namespace,
                () -> jobExecutor.scale(this, kind, namespace, name, replicas));
    }

    public List<String> delete(KubernetesKind kind, String namespace, String name,
            KubernetesSelectorList labelSelectors, V1DeleteOptions options) {
        return runAndRecordMetrics("delete", kind, namespace,
                () -> jobExecutor.delete(this, kind, namespace, name, labelSelectors, options));
    }

    public void deploy(KubernetesManifest manifest) {
        runAndRecordMetrics("deploy", manifest.getKind(), manifest.getNamespace(),
                () -> jobExecutor.deploy(this, manifest));
    }

    public List<Integer> historyRollout(KubernetesKind kind, String namespace, String name) {
        return runAndRecordMetrics("historyRollout", kind, namespace,
                () -> jobExecutor.historyRollout(this, kind, namespace, name));
    }

    public void undoRollout(KubernetesKind kind, String namespace, String name, int revision) {
        runAndRecordMetrics("undoRollout", kind, namespace,
                () -> jobExecutor.undoRollout(this, kind, namespace, name, revision));
    }

    public void pauseRollout(KubernetesKind kind, String namespace, String name) {
        runAndRecordMetrics("pauseRollout", kind, namespace,
                () -> jobExecutor.pauseRollout(this, kind, namespace, name));
    }

    public void resumeRollout(KubernetesKind kind, String namespace, String name) {
        runAndRecordMetrics("resumeRollout", kind, namespace,
                () -> jobExecutor.resumeRollout(this, kind, namespace, name));
    }

    private <T> T runAndRecordMetrics(String action, KubernetesKind kind, String namespace, Supplier<T> op) {
        return runAndRecordMetrics(action, Collections.singletonList(kind), namespace, op);
    }

    private <T> T runAndRecordMetrics(String action, List<KubernetesKind> kinds, String namespace, Supplier<T> op) {
        T result = null;
        Throwable failure = null;
        KubectlException apiException = null;
        long startTime = clock.monotonicTime();
        try {
            result = op.get();
        } catch (KubectlException e) {
            apiException = e;
        } catch (Exception e) {
            failure = e;
        } finally {
            Map<String, String> tags = new HashMap<>();
            tags.put("action", action);
            if (kinds.size() == 1) {
                tags.put("kind", kinds.get(0).toString());
            } else {
                tags.put("kinds", String.join(",",
                        kinds.stream().map(KubernetesKind::toString).collect(Collectors.toList())));
            }
            tags.put("account", accountName);
            tags.put("namespace", StringUtils.isEmpty(namespace) ? "none" : namespace);
            if (failure == null) {
                tags.put("success", "true");
            } else {
                tags.put("success", "false");
                tags.put("reason", failure.getClass().getSimpleName() + ": " + failure.getMessage());
            }

            registry.timer(registry.createId("kubernetes.api", tags)).record(clock.monotonicTime() - startTime,
                    TimeUnit.NANOSECONDS);

            if (failure != null) {
                throw new KubectlJobExecutor.KubectlException(
                        "Failure running " + action + " on " + kinds + ": " + failure.getMessage(), failure);
            } else if (apiException != null) {
                throw apiException;
            } else {
                return result;
            }
        }
    }
}