diff --git a/build.gradle b/build.gradle index d51269f1b..bac794d74 100644 --- a/build.gradle +++ b/build.gradle @@ -17,17 +17,6 @@ sourceSets { compileClasspath += sourceSets.main.output + configurations.runtimeClasspath runtimeClasspath += output + compileClasspath } - main { - java.srcDirs("src/main/java") - kotlin.srcDirs("src/main/kotlin") - } - test { - java.srcDirs("src/test/java") - kotlin.srcDirs("src/test/kotlin") - // #779: unit tests need to see kubernetes-client classes in src/main/java - compileClasspath += sourceSets.main.output + configurations.runtimeClasspath - runtimeClasspath += output + compileClasspath - } } task integrationTest(type: Test) { diff --git a/gradle.properties b/gradle.properties index 1b0da083b..2cc897239 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ jetBrainsToken=invalid jetBrainsChannel=stable intellijPluginVersion=1.16.1 kotlinJvmPluginVersion=1.8.0 -intellijCommonVersion=1.9.7-SNAPSHOT +intellijCommonVersion=1.9.6 telemetryPluginVersion=1.1.0.52 kotlin.stdlib.default.dependency = false kotlinVersion=1.6.21 diff --git a/src/main/java/io/fabric8/kubernetes/client/Config.java b/src/main/java/io/fabric8/kubernetes/client/Config.java deleted file mode 100644 index f36a301d2..000000000 --- a/src/main/java/io/fabric8/kubernetes/client/Config.java +++ /dev/null @@ -1,1902 +0,0 @@ -/* - * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.fabric8.kubernetes.api.model.AuthInfo; -import io.fabric8.kubernetes.api.model.AuthProviderConfig; -import io.fabric8.kubernetes.api.model.Cluster; -import io.fabric8.kubernetes.api.model.ConfigBuilder; -import io.fabric8.kubernetes.api.model.Context; -import io.fabric8.kubernetes.api.model.ExecConfig; -import io.fabric8.kubernetes.api.model.ExecEnvVar; -import io.fabric8.kubernetes.api.model.NamedAuthInfo; -import io.fabric8.kubernetes.api.model.NamedContext; -import io.fabric8.kubernetes.client.http.TlsVersion; -import io.fabric8.kubernetes.client.internal.CertUtils; -import io.fabric8.kubernetes.client.internal.KubeConfigUtils; -import io.fabric8.kubernetes.client.internal.SSLUtils; -import io.fabric8.kubernetes.client.readiness.Readiness; -import io.fabric8.kubernetes.client.utils.IOHelpers; -import io.fabric8.kubernetes.client.utils.Serialization; -import io.fabric8.kubernetes.client.utils.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(allowGetters = true, allowSetters = true) -public class Config { - - private static final Logger LOGGER = LoggerFactory.getLogger(Config.class); - - /** - * Disables auto-configuration based on opinionated defaults in a {@link Config} object in the all arguments constructor - */ - public static final String KUBERNETES_DISABLE_AUTO_CONFIG_SYSTEM_PROPERTY = "kubernetes.disable.autoConfig"; - public static final String KUBERNETES_MASTER_SYSTEM_PROPERTY = "kubernetes.master"; - public static final String KUBERNETES_API_VERSION_SYSTEM_PROPERTY = "kubernetes.api.version"; - public static final String KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY = "kubernetes.trust.certificates"; - public static final String KUBERNETES_DISABLE_HOSTNAME_VERIFICATION_SYSTEM_PROPERTY = "kubernetes.disable.hostname.verification"; - public static final String KUBERNETES_CA_CERTIFICATE_FILE_SYSTEM_PROPERTY = "kubernetes.certs.ca.file"; - public static final String KUBERNETES_CA_CERTIFICATE_DATA_SYSTEM_PROPERTY = "kubernetes.certs.ca.data"; - public static final String KUBERNETES_CLIENT_CERTIFICATE_FILE_SYSTEM_PROPERTY = "kubernetes.certs.client.file"; - public static final String KUBERNETES_CLIENT_CERTIFICATE_DATA_SYSTEM_PROPERTY = "kubernetes.certs.client.data"; - public static final String KUBERNETES_CLIENT_KEY_FILE_SYSTEM_PROPERTY = "kubernetes.certs.client.key.file"; - public static final String KUBERNETES_CLIENT_KEY_DATA_SYSTEM_PROPERTY = "kubernetes.certs.client.key.data"; - public static final String KUBERNETES_CLIENT_KEY_ALGO_SYSTEM_PROPERTY = "kubernetes.certs.client.key.algo"; - public static final String KUBERNETES_CLIENT_KEY_PASSPHRASE_SYSTEM_PROPERTY = "kubernetes.certs.client.key.passphrase"; - public static final String KUBERNETES_AUTH_BASIC_USERNAME_SYSTEM_PROPERTY = "kubernetes.auth.basic.username"; - public static final String KUBERNETES_AUTH_BASIC_PASSWORD_SYSTEM_PROPERTY = "kubernetes.auth.basic.password"; - public static final String KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY = "kubernetes.auth.tryKubeConfig"; - public static final String KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY = "kubernetes.auth.tryServiceAccount"; - public static final String KUBERNETES_AUTH_SERVICEACCOUNT_TOKEN_FILE_SYSTEM_PROPERTY = "kubernetes.auth.serviceAccount.token"; - public static final String KUBERNETES_OAUTH_TOKEN_SYSTEM_PROPERTY = "kubernetes.auth.token"; - public static final String KUBERNETES_WATCH_RECONNECT_INTERVAL_SYSTEM_PROPERTY = "kubernetes.watch.reconnectInterval"; - public static final String KUBERNETES_WATCH_RECONNECT_LIMIT_SYSTEM_PROPERTY = "kubernetes.watch.reconnectLimit"; - public static final String KUBERNETES_CONNECTION_TIMEOUT_SYSTEM_PROPERTY = "kubernetes.connection.timeout"; - public static final String KUBERNETES_UPLOAD_REQUEST_TIMEOUT_SYSTEM_PROPERTY = "kubernetes.upload.request.timeout"; - public static final String KUBERNETES_REQUEST_TIMEOUT_SYSTEM_PROPERTY = "kubernetes.request.timeout"; - public static final String KUBERNETES_REQUEST_RETRY_BACKOFFLIMIT_SYSTEM_PROPERTY = "kubernetes.request.retry.backoffLimit"; - public static final String KUBERNETES_REQUEST_RETRY_BACKOFFINTERVAL_SYSTEM_PROPERTY = "kubernetes.request.retry.backoffInterval"; - public static final String KUBERNETES_LOGGING_INTERVAL_SYSTEM_PROPERTY = "kubernetes.logging.interval"; - public static final String KUBERNETES_SCALE_TIMEOUT_SYSTEM_PROPERTY = "kubernetes.scale.timeout"; - public static final String KUBERNETES_WEBSOCKET_PING_INTERVAL_SYSTEM_PROPERTY = "kubernetes.websocket.ping.interval"; - public static final String KUBERNETES_MAX_CONCURRENT_REQUESTS = "kubernetes.max.concurrent.requests"; - public static final String KUBERNETES_MAX_CONCURRENT_REQUESTS_PER_HOST = "kubernetes.max.concurrent.requests.per.host"; - - public static final String KUBERNETES_IMPERSONATE_USERNAME = "kubernetes.impersonate.username"; - public static final String KUBERNETES_IMPERSONATE_GROUP = "kubernetes.impersonate.group"; - - public static final String KUBERNETES_TRUSTSTORE_PASSPHRASE_PROPERTY = "kubernetes.truststore.passphrase"; - public static final String KUBERNETES_TRUSTSTORE_FILE_PROPERTY = "kubernetes.truststore.file"; - public static final String KUBERNETES_KEYSTORE_PASSPHRASE_PROPERTY = "kubernetes.keystore.passphrase"; - public static final String KUBERNETES_KEYSTORE_FILE_PROPERTY = "kubernetes.keystore.file"; - - public static final String KUBERNETES_TLS_VERSIONS = "kubernetes.tls.versions"; - - public static final String KUBERNETES_TRYNAMESPACE_PATH_SYSTEM_PROPERTY = "kubernetes.tryNamespacePath"; - public static final String KUBERNETES_NAMESPACE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; - public static final String KUBERNETES_NAMESPACE_FILE = "kubenamespace"; - public static final String KUBERNETES_NAMESPACE_SYSTEM_PROPERTY = "kubernetes.namespace"; - /** - * @deprecated use {@link #KUBERNETES_KUBECONFIG_FILES} instead. - */ - @Deprecated - public static final String KUBERNETES_KUBECONFIG_FILE = "kubeconfig"; - public static final String KUBERNETES_KUBECONFIG_FILES = "kubeconfig"; - public static final String KUBERNETES_SERVICE_HOST_PROPERTY = "KUBERNETES_SERVICE_HOST"; - public static final String KUBERNETES_SERVICE_PORT_PROPERTY = "KUBERNETES_SERVICE_PORT"; - public static final String KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"; - public static final String KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; - public static final String KUBERNETES_HTTP2_DISABLE = "http2.disable"; - public static final String KUBERNETES_HTTP_PROXY = "http.proxy"; - public static final String KUBERNETES_HTTPS_PROXY = "https.proxy"; - public static final String KUBERNETES_ALL_PROXY = "all.proxy"; - public static final String KUBERNETES_NO_PROXY = "no.proxy"; - public static final String KUBERNETES_PROXY_USERNAME = "proxy.username"; - public static final String KUBERNETES_PROXY_PASSWORD = "proxy.password"; - - public static final String KUBERNETES_USER_AGENT = "kubernetes.user.agent"; - - public static final String DEFAULT_MASTER_URL = "https://kubernetes.default.svc"; - public static final Long DEFAULT_SCALE_TIMEOUT = 10 * 60 * 1000L; - public static final int DEFAULT_REQUEST_TIMEOUT = 10 * 1000; - public static final int DEFAULT_LOGGING_INTERVAL = 20 * 1000; - public static final Long DEFAULT_WEBSOCKET_PING_INTERVAL = 30 * 1000L; - - public static final Integer DEFAULT_MAX_CONCURRENT_REQUESTS = 64; - public static final Integer DEFAULT_MAX_CONCURRENT_REQUESTS_PER_HOST = 5; - - public static final Integer DEFAULT_REQUEST_RETRY_BACKOFFLIMIT = 10; - public static final Integer DEFAULT_REQUEST_RETRY_BACKOFFINTERVAL = 100; - - public static final int DEFAULT_UPLOAD_REQUEST_TIMEOUT = 120 * 1000; - - public static final String HTTP_PROTOCOL_PREFIX = "http://"; - public static final String HTTPS_PROTOCOL_PREFIX = "https://"; - - public static final File DEFAULT_KUBECONFIG_FILE = Paths.get(System.getProperty("user.home"), ".kube", "config").toFile(); - - private static final String ACCESS_TOKEN = "access-token"; - private static final String ID_TOKEN = "id-token"; - private static final int DEFAULT_WATCH_RECONNECT_INTERVAL = 1000; - private static final int DEFAULT_CONNECTION_TIMEOUT = 10 * 1000; - private static final String DEFAULT_CLIENT_KEY_PASSPHRASE = "changeit"; - private static final String SOCKS5_PROTOCOL_PREFIX = "socks5://"; - - private Boolean trustCerts; - private Boolean disableHostnameVerification; - private String masterUrl; - private String apiVersion; - private String namespace; - private Boolean defaultNamespace; - private String caCertFile; - private String caCertData; - private String clientCertFile; - private String clientCertData; - private String clientKeyFile; - private String clientKeyData; - private String clientKeyAlgo; - private String clientKeyPassphrase; - private String trustStoreFile; - private String trustStorePassphrase; - private String keyStoreFile; - private String keyStorePassphrase; - private AuthProviderConfig authProvider; - private String username; - private String password; - private volatile String oauthToken; - @JsonIgnore - private volatile String autoOAuthToken; - private OAuthTokenProvider oauthTokenProvider; - private Long websocketPingInterval; - private Integer connectionTimeout; - private Integer maxConcurrentRequests; - private Integer maxConcurrentRequestsPerHost; - - private final RequestConfig requestConfig; - - private List contexts; - private NamedContext currentContext = null; - private List kubeConfigFiles = new ArrayList<>(); - - /** - * fields not used but needed for builder generation. - */ - private Integer watchReconnectInterval; - private Integer watchReconnectLimit; - private Integer uploadRequestTimeout; - private Integer requestRetryBackoffLimit; - private Integer requestRetryBackoffInterval; - private Integer requestTimeout; - private Long scaleTimeout; - private Integer loggingInterval; - private String impersonateUsername; - - /** - * @deprecated use impersonateGroups instead - */ - @Deprecated - private String impersonateGroup; - private String[] impersonateGroups; - private Map> impersonateExtras; - /** - * end of fields not used but needed for builder generation. - */ - - private Boolean http2Disable; - private String httpProxy; - private String httpsProxy; - private String proxyUsername; - private String proxyPassword; - private String[] noProxy; - private String userAgent; - private TlsVersion[] tlsVersions; - - private Boolean onlyHttpWatches; - - @Deprecated - private Map errorMessages = new HashMap<>(); - - /** - * custom headers - */ - private Map customHeaders = null; - - private Boolean autoConfigure; - - @JsonIgnore - protected Map additionalProperties = new HashMap<>(); - - /** - * @deprecated use {@link #autoConfigure(String)} or {@link ConfigBuilder} instead - */ - @Deprecated - public Config() { - this(!disableAutoConfig()); - } - - static boolean disableAutoConfig() { - return Utils.getSystemPropertyOrEnvVar(KUBERNETES_DISABLE_AUTO_CONFIG_SYSTEM_PROPERTY, false); - } - - private Config(boolean autoConfigure) { - this(null, null, null, null, null, - null, null, null, null, null, - null, null, null, null, null, - null, null, null, null, null, - null, null, null, null, - null, - null, null, null, null, - null, null, null, - null, - null, null, null, null, null, - null, null, null, - null, null, null, - null, null, null, null, - null, autoConfigure, true, null); - } - - /** - * Create an empty {@link Config} class without any automatic configuration - * (i.e. reading system properties/environment variables to load defaults.) - * You can also reuse this object to build your own {@link Config} object - * without any auto configuration like this: - * - * - *
{@code
-   * Config configFromBuilder = new ConfigBuilder(Config.empty())
-   *                                // ...
-   *                               .build();
-   * }
- * - * @return a Config object without any automatic configuration - */ - public static Config empty() { - return new Config(false); - } - - /** - * Does auto detection with some opinionated defaults. - * - * @param context if null will use current-context - * @return Config object - */ - public static Config autoConfigure(String context) { - Config config = new Config(false); - return autoConfigure(config, context); - } - - private static Config autoConfigure(Config config, String context) { - if (!tryKubeConfig(config, context)) { - tryServiceAccount(config); - tryNamespaceFromPath(config); - } - postAutoConfigure(config); - config.autoConfigure = true; - return config; - } - - private static void postAutoConfigure(Config config) { - configFromSysPropsOrEnvVars(config); - - config.masterUrl = ensureHttps(config.masterUrl, config); - config.masterUrl = ensureEndsWithSlash(config.masterUrl); - } - - private static String ensureEndsWithSlash(String masterUrl) { - if (!masterUrl.endsWith("/")) { - masterUrl = masterUrl + "/"; - } - return masterUrl; - } - - private static String ensureHttps(String masterUrl, Config config) { - if (!masterUrl.toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL_PREFIX) - && !masterUrl.toLowerCase(Locale.ROOT).startsWith(HTTPS_PROTOCOL_PREFIX)) { - masterUrl = (SSLUtils.isHttpsAvailable(config) ? HTTPS_PROTOCOL_PREFIX : HTTP_PROTOCOL_PREFIX) + masterUrl; - } - return masterUrl; - } - - @Deprecated - public Config(String masterUrl, String apiVersion, String namespace, Boolean trustCerts, boolean disableHostnameVerification, - String caCertFile, String caCertData, String clientCertFile, String clientCertData, String clientKeyFile, - String clientKeyData, String clientKeyAlgo, String clientKeyPassphrase, String username, String password, - String oauthToken, String autoOAuthToken, int watchReconnectInterval, int watchReconnectLimit, int connectionTimeout, - int requestTimeout, - long rollingTimeout, long scaleTimeout, int loggingInterval, int maxConcurrentRequests, int maxConcurrentRequestsPerHost, - String httpProxy, String httpsProxy, String[] noProxy, Map errorMessages, String userAgent, - TlsVersion[] tlsVersions, long websocketPingInterval, String proxyUsername, String proxyPassword, - String trustStoreFile, String trustStorePassphrase, String keyStoreFile, String keyStorePassphrase, - String impersonateUsername, String[] impersonateGroups, Map> impersonateExtras) { - this(masterUrl, apiVersion, namespace, trustCerts, disableHostnameVerification, caCertFile, caCertData, clientCertFile, - clientCertData, clientKeyFile, clientKeyData, clientKeyAlgo, clientKeyPassphrase, username, password, oauthToken, - autoOAuthToken, - watchReconnectInterval, watchReconnectLimit, connectionTimeout, requestTimeout, scaleTimeout, - loggingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, false, httpProxy, httpsProxy, noProxy, - userAgent, tlsVersions, websocketPingInterval, proxyUsername, proxyPassword, - trustStoreFile, trustStorePassphrase, keyStoreFile, keyStorePassphrase, impersonateUsername, impersonateGroups, - impersonateExtras, null, null, DEFAULT_REQUEST_RETRY_BACKOFFLIMIT, DEFAULT_REQUEST_RETRY_BACKOFFINTERVAL, - DEFAULT_UPLOAD_REQUEST_TIMEOUT, false, null, Collections.emptyList()); - } - - @Deprecated - public Config(String masterUrl, String apiVersion, String namespace, Boolean trustCerts, boolean disableHostnameVerification, - String caCertFile, String caCertData, String clientCertFile, String clientCertData, String clientKeyFile, - String clientKeyData, String clientKeyAlgo, String clientKeyPassphrase, String username, String password, - String oauthToken, String autoOAuthToken, int watchReconnectInterval, int watchReconnectLimit, int connectionTimeout, - int requestTimeout, - long scaleTimeout, int loggingInterval, int maxConcurrentRequests, int maxConcurrentRequestsPerHost, - boolean http2Disable, String httpProxy, String httpsProxy, String[] noProxy, - String userAgent, TlsVersion[] tlsVersions, long websocketPingInterval, String proxyUsername, - String proxyPassword, String trustStoreFile, String trustStorePassphrase, String keyStoreFile, String keyStorePassphrase, - String impersonateUsername, String[] impersonateGroups, Map> impersonateExtras, - OAuthTokenProvider oauthTokenProvider, Map customHeaders, int requestRetryBackoffLimit, - int requestRetryBackoffInterval, int uploadRequestTimeout, boolean onlyHttpWatches, NamedContext currentContext, - List contexts) { - this(masterUrl, apiVersion, namespace, trustCerts, disableHostnameVerification, caCertFile, caCertData, - clientCertFile, clientCertData, clientKeyFile, clientKeyData, clientKeyAlgo, clientKeyPassphrase, username, - password, oauthToken, autoOAuthToken, watchReconnectInterval, watchReconnectLimit, connectionTimeout, requestTimeout, - scaleTimeout, loggingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, http2Disable, - httpProxy, httpsProxy, noProxy, userAgent, tlsVersions, websocketPingInterval, proxyUsername, proxyPassword, - trustStoreFile, trustStorePassphrase, keyStoreFile, keyStorePassphrase, impersonateUsername, impersonateGroups, - impersonateExtras, oauthTokenProvider, customHeaders, requestRetryBackoffLimit, requestRetryBackoffInterval, - uploadRequestTimeout, onlyHttpWatches, currentContext, contexts, false, true, null); - } - - public Config(String masterUrl, String apiVersion, String namespace, Boolean trustCerts, Boolean disableHostnameVerification, - String caCertFile, String caCertData, String clientCertFile, String clientCertData, String clientKeyFile, - String clientKeyData, String clientKeyAlgo, String clientKeyPassphrase, String username, String password, - String oauthToken, String autoOAuthToken, Integer watchReconnectInterval, Integer watchReconnectLimit, - Integer connectionTimeout, - Integer requestTimeout, - Long scaleTimeout, Integer loggingInterval, Integer maxConcurrentRequests, Integer maxConcurrentRequestsPerHost, - Boolean http2Disable, String httpProxy, String httpsProxy, String[] noProxy, - String userAgent, TlsVersion[] tlsVersions, Long websocketPingInterval, String proxyUsername, - String proxyPassword, String trustStoreFile, String trustStorePassphrase, String keyStoreFile, String keyStorePassphrase, - String impersonateUsername, String[] impersonateGroups, Map> impersonateExtras, - OAuthTokenProvider oauthTokenProvider, Map customHeaders, Integer requestRetryBackoffLimit, - Integer requestRetryBackoffInterval, Integer uploadRequestTimeout, Boolean onlyHttpWatches, NamedContext currentContext, - List contexts, Boolean autoConfigure) { - this(masterUrl, apiVersion, namespace, trustCerts, disableHostnameVerification, caCertFile, caCertData, - clientCertFile, clientCertData, clientKeyFile, clientKeyData, clientKeyAlgo, clientKeyPassphrase, username, - password, oauthToken, autoOAuthToken, watchReconnectInterval, watchReconnectLimit, connectionTimeout, requestTimeout, - scaleTimeout, loggingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, http2Disable, - httpProxy, httpsProxy, noProxy, userAgent, tlsVersions, websocketPingInterval, proxyUsername, proxyPassword, - trustStoreFile, trustStorePassphrase, keyStoreFile, keyStorePassphrase, impersonateUsername, impersonateGroups, - impersonateExtras, oauthTokenProvider, customHeaders, requestRetryBackoffLimit, requestRetryBackoffInterval, - uploadRequestTimeout, onlyHttpWatches, currentContext, contexts, autoConfigure, true, null); - } - - /** - * introduced for backward compatibility with {@link io.fabric8.openshift.client.OpenShiftConfig} 6.12.0 - **/ - protected Config(String masterUrl, String apiVersion, String namespace, boolean trustCerts, boolean disableHostnameVerification, - String caCertFile, String caCertData, String clientCertFile, String clientCertData, String clientKeyFile, - String clientKeyData, String clientKeyAlgo, String clientKeyPassphrase, String username, String password, - String oauthToken, String autoOAuthToken, int watchReconnectInterval, int watchReconnectLimit, int connectionTimeout, - int requestTimeout, - long scaleTimeout, int loggingInterval, int maxConcurrentRequests, int maxConcurrentRequestsPerHost, - boolean http2Disable, String httpProxy, String httpsProxy, String[] noProxy, Map errorMessages, - String userAgent, TlsVersion[] tlsVersions, long websocketPingInterval, String proxyUsername, - String proxyPassword, String trustStoreFile, String trustStorePassphrase, String keyStoreFile, String keyStorePassphrase, - String impersonateUsername, String[] impersonateGroups, Map> impersonateExtras, - OAuthTokenProvider oauthTokenProvider, Map customHeaders, int requestRetryBackoffLimit, - int requestRetryBackoffInterval, int uploadRequestTimeout, boolean onlyHttpWatches) { - this(masterUrl, apiVersion, namespace, trustCerts, disableHostnameVerification, - caCertFile, caCertData, clientCertFile, clientCertData, clientKeyFile, - clientKeyData, clientKeyAlgo, clientKeyPassphrase, username, password, - oauthToken, autoOAuthToken, watchReconnectInterval, watchReconnectLimit, - connectionTimeout, - requestTimeout, - scaleTimeout, loggingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, - http2Disable, httpProxy, httpsProxy, noProxy, - // errorMessages, - userAgent, tlsVersions, websocketPingInterval, proxyUsername, - proxyPassword, trustStoreFile, trustStorePassphrase, keyStoreFile, keyStorePassphrase, - impersonateUsername, impersonateGroups, impersonateExtras, - oauthTokenProvider, customHeaders, requestRetryBackoffLimit, - requestRetryBackoffInterval, uploadRequestTimeout, onlyHttpWatches, null, - null, false, true, null); - } - - /* - * The Builder is generated in SundrioConfig, if new fields need to be added here, please make sure to add them there too. - */ - Config(String masterUrl, String apiVersion, String namespace, Boolean trustCerts, Boolean disableHostnameVerification, - String caCertFile, String caCertData, String clientCertFile, String clientCertData, String clientKeyFile, - String clientKeyData, String clientKeyAlgo, String clientKeyPassphrase, String username, String password, - String oauthToken, String autoOAuthToken, Integer watchReconnectInterval, Integer watchReconnectLimit, - Integer connectionTimeout, - Integer requestTimeout, - Long scaleTimeout, Integer loggingInterval, Integer maxConcurrentRequests, Integer maxConcurrentRequestsPerHost, - Boolean http2Disable, String httpProxy, String httpsProxy, String[] noProxy, - String userAgent, TlsVersion[] tlsVersions, Long websocketPingInterval, String proxyUsername, - String proxyPassword, String trustStoreFile, String trustStorePassphrase, String keyStoreFile, String keyStorePassphrase, - String impersonateUsername, String[] impersonateGroups, Map> impersonateExtras, - OAuthTokenProvider oauthTokenProvider, Map customHeaders, Integer requestRetryBackoffLimit, - Integer requestRetryBackoffInterval, Integer uploadRequestTimeout, Boolean onlyHttpWatches, NamedContext currentContext, - List contexts, Boolean autoConfigure, Boolean shouldSetDefaultValues, List files) { - if (Boolean.TRUE.equals(shouldSetDefaultValues)) { - this.masterUrl = DEFAULT_MASTER_URL; - this.apiVersion = "v1"; - this.defaultNamespace = true; - this.trustCerts = false; - this.disableHostnameVerification = false; - this.onlyHttpWatches = false; - this.http2Disable = false; - this.clientKeyAlgo = "RSA"; - this.clientKeyPassphrase = DEFAULT_CLIENT_KEY_PASSPHRASE; - this.websocketPingInterval = DEFAULT_WEBSOCKET_PING_INTERVAL; - this.connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; - this.maxConcurrentRequests = DEFAULT_MAX_CONCURRENT_REQUESTS; - this.maxConcurrentRequestsPerHost = DEFAULT_MAX_CONCURRENT_REQUESTS_PER_HOST; - this.contexts = new ArrayList<>(); - this.watchReconnectInterval = DEFAULT_WATCH_RECONNECT_INTERVAL; - this.watchReconnectLimit = -1; - this.uploadRequestTimeout = DEFAULT_UPLOAD_REQUEST_TIMEOUT; - this.requestRetryBackoffInterval = DEFAULT_REQUEST_RETRY_BACKOFFINTERVAL; - this.requestRetryBackoffLimit = DEFAULT_REQUEST_RETRY_BACKOFFLIMIT; - this.requestTimeout = DEFAULT_REQUEST_TIMEOUT; - this.scaleTimeout = DEFAULT_SCALE_TIMEOUT; - this.loggingInterval = DEFAULT_LOGGING_INTERVAL; - this.userAgent = "fabric8-kubernetes-client/" + Version.clientVersion(); - this.tlsVersions = new TlsVersion[] { TlsVersion.TLS_1_3, TlsVersion.TLS_1_2 }; - this.requestConfig = new RequestConfig(this.watchReconnectLimit, this.watchReconnectInterval, - this.requestTimeout, this.scaleTimeout, this.loggingInterval, - this.requestRetryBackoffLimit, this.requestRetryBackoffInterval, this.uploadRequestTimeout); - } else { - this.requestConfig = new RequestConfig(watchReconnectLimit, watchReconnectInterval, requestTimeout, scaleTimeout, loggingInterval, requestRetryBackoffLimit, requestRetryBackoffInterval, uploadRequestTimeout); - } - - if (Boolean.TRUE.equals(autoConfigure)) { - autoConfigure(this, null); - } - if (Utils.isNotNullOrEmpty(apiVersion)) { - this.apiVersion = apiVersion; - } - if (Utils.isNotNullOrEmpty(masterUrl)) { - this.masterUrl = masterUrl; - } - if (Utils.isNotNullOrEmpty(namespace)) { - this.namespace = namespace; - } - if (Boolean.TRUE.equals(trustCerts)) { - this.trustCerts = true; - } - if (Boolean.TRUE.equals(disableHostnameVerification)) { - this.disableHostnameVerification = true; - } - if (Utils.isNotNullOrEmpty(caCertFile)) { - this.caCertFile = caCertFile; - } - if (Utils.isNotNullOrEmpty(caCertData)) { - this.caCertData = caCertData; - } - if (Utils.isNotNullOrEmpty(clientCertFile)) { - this.clientCertFile = clientCertFile; - } - if (Utils.isNotNullOrEmpty(clientCertData)) { - this.clientCertData = clientCertData; - } - if (Utils.isNotNullOrEmpty(clientKeyFile)) { - this.clientKeyFile = clientKeyFile; - } - if (Utils.isNotNullOrEmpty(clientKeyData)) { - this.clientKeyData = clientKeyData; - } - if (Utils.isNotNullOrEmpty(clientKeyAlgo)) { - this.clientKeyAlgo = clientKeyAlgo; - } - if (Utils.isNotNullOrEmpty(clientKeyPassphrase)) { - this.clientKeyPassphrase = clientKeyPassphrase; - } - if (Utils.isNotNullOrEmpty(username)) { - this.username = username; - } - if (Utils.isNotNullOrEmpty(password)) { - this.password = password; - } - if (Utils.isNotNullOrEmpty(oauthToken)) { - this.oauthToken = oauthToken; - } - if (websocketPingInterval != null) { - this.websocketPingInterval = websocketPingInterval; - } - if (connectionTimeout != null) { - this.connectionTimeout = connectionTimeout; - } - if (watchReconnectLimit != null) { - setWatchReconnectLimit(watchReconnectLimit); - } - if (watchReconnectInterval != null) { - setWatchReconnectInterval(watchReconnectInterval); - } - if (requestTimeout != null) { - setRequestTimeout(requestTimeout); - } - if (scaleTimeout != null) { - setScaleTimeout(scaleTimeout); - } - if (loggingInterval != null) { - setLoggingInterval(loggingInterval); - } - if (requestRetryBackoffLimit != null) { - setRequestRetryBackoffLimit(requestRetryBackoffLimit); - } - if (requestRetryBackoffInterval != null) { - setRequestRetryBackoffInterval(requestRetryBackoffInterval); - } - if (uploadRequestTimeout != null) { - setUploadRequestTimeout(uploadRequestTimeout); - } - if (Utils.isNotNullOrEmpty(impersonateUsername)) { - setImpersonateUsername(impersonateUsername); - } - if (Utils.isNotNullOrEmpty(impersonateGroups)) { - setImpersonateGroups(impersonateGroups); - } - if (Utils.isNotNullOrEmpty(impersonateExtras)) { - setImpersonateExtras(impersonateExtras); - } - if (http2Disable != null) { - this.http2Disable = http2Disable; - } - if (Utils.isNotNullOrEmpty(httpProxy)) { - this.httpProxy = httpProxy; - } - if (Utils.isNotNullOrEmpty(httpsProxy)) { - this.httpsProxy = httpsProxy; - } - if (Utils.isNotNullOrEmpty(noProxy)) { - this.noProxy = noProxy; - } - if (Utils.isNotNullOrEmpty(proxyUsername)) { - this.proxyUsername = proxyUsername; - } - if (Utils.isNotNullOrEmpty(proxyPassword)) { - this.proxyPassword = proxyPassword; - } - if (Utils.isNotNullOrEmpty(userAgent)) { - this.userAgent = userAgent; - } - if (tlsVersions != null && tlsVersions.length > 0) { - this.tlsVersions = tlsVersions; - } - if (Utils.isNotNullOrEmpty(trustStoreFile)) { - this.trustStoreFile = trustStoreFile; - } - if (Utils.isNotNullOrEmpty(trustStorePassphrase)) { - this.trustStorePassphrase = trustStorePassphrase; - } - if (Utils.isNotNullOrEmpty(keyStoreFile)) { - this.keyStoreFile = keyStoreFile; - } - if (Utils.isNotNullOrEmpty(keyStorePassphrase)) { - this.keyStorePassphrase = keyStorePassphrase; - } - if (maxConcurrentRequests != null) { - this.maxConcurrentRequests = maxConcurrentRequests; - } - if (maxConcurrentRequestsPerHost != null) { - this.maxConcurrentRequestsPerHost = maxConcurrentRequestsPerHost; - } - if (Utils.isNotNullOrEmpty(autoOAuthToken)) { - this.autoOAuthToken = autoOAuthToken; - } - if (Utils.isNotNullOrEmpty(contexts)) { - this.contexts = contexts; - } - if (Utils.isNotNull(currentContext)) { - this.currentContext = currentContext; - } - - if (Utils.isNotNullOrEmpty(this.masterUrl)) { - this.masterUrl = ensureEndsWithSlash(ensureHttps(this.masterUrl, this)); - } - this.autoConfigure = autoConfigure; - this.oauthTokenProvider = oauthTokenProvider; - this.customHeaders = customHeaders; - this.onlyHttpWatches = onlyHttpWatches; - if (Utils.isNotNullOrEmpty(files)) { - this.kubeConfigFiles = files; - } - } - - public static void configFromSysPropsOrEnvVars(Config config) { - config.setTrustCerts(Utils.getSystemPropertyOrEnvVar(KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, config.isTrustCerts())); - config.setDisableHostnameVerification(Utils.getSystemPropertyOrEnvVar( - KUBERNETES_DISABLE_HOSTNAME_VERIFICATION_SYSTEM_PROPERTY, config.isDisableHostnameVerification())); - config.setMasterUrl(Utils.getSystemPropertyOrEnvVar(KUBERNETES_MASTER_SYSTEM_PROPERTY, config.getMasterUrl())); - config.setApiVersion(Utils.getSystemPropertyOrEnvVar(KUBERNETES_API_VERSION_SYSTEM_PROPERTY, config.getApiVersion())); - config.setNamespace(Utils.getSystemPropertyOrEnvVar(KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, config.getNamespace())); - config - .setCaCertFile(Utils.getSystemPropertyOrEnvVar(KUBERNETES_CA_CERTIFICATE_FILE_SYSTEM_PROPERTY, config.getCaCertFile())); - config - .setCaCertData(Utils.getSystemPropertyOrEnvVar(KUBERNETES_CA_CERTIFICATE_DATA_SYSTEM_PROPERTY, config.getCaCertData())); - config.setClientCertFile( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_CERTIFICATE_FILE_SYSTEM_PROPERTY, config.getClientCertFile())); - config.setClientCertData( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_CERTIFICATE_DATA_SYSTEM_PROPERTY, config.getClientCertData())); - config.setClientKeyFile( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_KEY_FILE_SYSTEM_PROPERTY, config.getClientKeyFile())); - config.setClientKeyData( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_KEY_DATA_SYSTEM_PROPERTY, config.getClientKeyData())); - config.setClientKeyAlgo(getKeyAlgorithm(config.getClientKeyFile(), config.getClientKeyData())); - config.setClientKeyPassphrase(Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_KEY_PASSPHRASE_SYSTEM_PROPERTY, - new String(config.getClientKeyPassphrase()))); - config.setUserAgent(Utils.getSystemPropertyOrEnvVar(KUBERNETES_USER_AGENT, config.getUserAgent())); - - config.setTrustStorePassphrase( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_TRUSTSTORE_PASSPHRASE_PROPERTY, config.getTrustStorePassphrase())); - config.setTrustStoreFile(Utils.getSystemPropertyOrEnvVar(KUBERNETES_TRUSTSTORE_FILE_PROPERTY, config.getTrustStoreFile())); - config.setKeyStorePassphrase( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_KEYSTORE_PASSPHRASE_PROPERTY, config.getKeyStorePassphrase())); - config.setKeyStoreFile(Utils.getSystemPropertyOrEnvVar(KUBERNETES_KEYSTORE_FILE_PROPERTY, config.getKeyStoreFile())); - - config - .setAutoOAuthToken(Utils.getSystemPropertyOrEnvVar(KUBERNETES_OAUTH_TOKEN_SYSTEM_PROPERTY, config.getAutoOAuthToken())); - config.setUsername(Utils.getSystemPropertyOrEnvVar(KUBERNETES_AUTH_BASIC_USERNAME_SYSTEM_PROPERTY, config.getUsername())); - config.setPassword(Utils.getSystemPropertyOrEnvVar(KUBERNETES_AUTH_BASIC_PASSWORD_SYSTEM_PROPERTY, config.getPassword())); - - config.setImpersonateUsername( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_IMPERSONATE_USERNAME, config.getImpersonateUsername())); - - String configuredImpersonateGroups = Utils.getSystemPropertyOrEnvVar(KUBERNETES_IMPERSONATE_GROUP, Arrays - .stream(Optional.ofNullable(config.getImpersonateGroups()).orElse(new String[0])).collect(Collectors.joining(","))); - if (Utils.isNotNullOrEmpty(configuredImpersonateGroups)) { - config.setImpersonateGroups(configuredImpersonateGroups.split(",")); - } - - String configuredWatchReconnectInterval = Utils - .getSystemPropertyOrEnvVar(KUBERNETES_WATCH_RECONNECT_INTERVAL_SYSTEM_PROPERTY); - if (configuredWatchReconnectInterval != null) { - config.setWatchReconnectInterval(Integer.parseInt(configuredWatchReconnectInterval)); - } - - String configuredWatchReconnectLimit = Utils.getSystemPropertyOrEnvVar(KUBERNETES_WATCH_RECONNECT_LIMIT_SYSTEM_PROPERTY); - if (configuredWatchReconnectLimit != null) { - config.setWatchReconnectLimit(Integer.parseInt(configuredWatchReconnectLimit)); - } - - String configuredScaleTimeout = Utils.getSystemPropertyOrEnvVar(KUBERNETES_SCALE_TIMEOUT_SYSTEM_PROPERTY, - String.valueOf(DEFAULT_SCALE_TIMEOUT)); - if (configuredScaleTimeout != null) { - config.setScaleTimeout(Long.parseLong(configuredScaleTimeout)); - } - - String configuredLoggingInterval = Utils.getSystemPropertyOrEnvVar(KUBERNETES_LOGGING_INTERVAL_SYSTEM_PROPERTY, - String.valueOf(DEFAULT_LOGGING_INTERVAL)); - if (configuredLoggingInterval != null) { - config.setLoggingInterval(Integer.parseInt(configuredLoggingInterval)); - } - - config.setConnectionTimeout( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_CONNECTION_TIMEOUT_SYSTEM_PROPERTY, config.getConnectionTimeout())); - config.setUploadRequestTimeout( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_UPLOAD_REQUEST_TIMEOUT_SYSTEM_PROPERTY, config.getUploadRequestTimeout())); - config.setRequestTimeout( - Utils.getSystemPropertyOrEnvVar(KUBERNETES_REQUEST_TIMEOUT_SYSTEM_PROPERTY, config.getRequestTimeout())); - config.setRequestRetryBackoffLimit(Utils.getSystemPropertyOrEnvVar(KUBERNETES_REQUEST_RETRY_BACKOFFLIMIT_SYSTEM_PROPERTY, - config.getRequestRetryBackoffLimit())); - config.setRequestRetryBackoffInterval(Utils.getSystemPropertyOrEnvVar( - KUBERNETES_REQUEST_RETRY_BACKOFFINTERVAL_SYSTEM_PROPERTY, config.getRequestRetryBackoffInterval())); - - String configuredWebsocketPingInterval = Utils.getSystemPropertyOrEnvVar(KUBERNETES_WEBSOCKET_PING_INTERVAL_SYSTEM_PROPERTY, - String.valueOf(config.getWebsocketPingInterval())); - if (configuredWebsocketPingInterval != null) { - config.setWebsocketPingInterval(Long.parseLong(configuredWebsocketPingInterval)); - } - - String configuredMaxConcurrentRequests = Utils.getSystemPropertyOrEnvVar(KUBERNETES_MAX_CONCURRENT_REQUESTS, - String.valueOf(config.getMaxConcurrentRequests())); - if (configuredMaxConcurrentRequests != null) { - config.setMaxConcurrentRequests(Integer.parseInt(configuredMaxConcurrentRequests)); - } - - String configuredMaxConcurrentReqeustsPerHost = Utils.getSystemPropertyOrEnvVar(KUBERNETES_MAX_CONCURRENT_REQUESTS_PER_HOST, - String.valueOf(config.getMaxConcurrentRequestsPerHost())); - if (configuredMaxConcurrentReqeustsPerHost != null) { - config.setMaxConcurrentRequestsPerHost(Integer.parseInt(configuredMaxConcurrentReqeustsPerHost)); - } - - config.setHttp2Disable(Utils.getSystemPropertyOrEnvVar(KUBERNETES_HTTP2_DISABLE, config.isHttp2Disable())); - - // Only set http(s) proxy fields if they're not set. This is done in order to align behavior of - // KubernetesClient with kubectl / client-go . Please see https://github.com/fabric8io/kubernetes-client/issues/6150 - // Precedence is given to proxy-url read from kubeconfig . - if (Utils.isNullOrEmpty(config.getHttpProxy())) { - config.setHttpProxy(Utils.getSystemPropertyOrEnvVar(KUBERNETES_ALL_PROXY, config.getHttpProxy())); - config.setHttpProxy(Utils.getSystemPropertyOrEnvVar(KUBERNETES_HTTP_PROXY, config.getHttpProxy())); - } - if (Utils.isNullOrEmpty(config.getHttpsProxy())) { - config.setHttpsProxy(Utils.getSystemPropertyOrEnvVar(KUBERNETES_ALL_PROXY, config.getHttpsProxy())); - config.setHttpsProxy(Utils.getSystemPropertyOrEnvVar(KUBERNETES_HTTPS_PROXY, config.getHttpsProxy())); - } - - config.setProxyUsername(Utils.getSystemPropertyOrEnvVar(KUBERNETES_PROXY_USERNAME, config.getProxyUsername())); - config.setProxyPassword(Utils.getSystemPropertyOrEnvVar(KUBERNETES_PROXY_PASSWORD, config.getProxyPassword())); - - String noProxyVar = Utils.getSystemPropertyOrEnvVar(KUBERNETES_NO_PROXY); - if (noProxyVar != null) { - config.setNoProxy(noProxyVar.split(",")); - } - - String tlsVersionsVar = Utils.getSystemPropertyOrEnvVar(KUBERNETES_TLS_VERSIONS); - if (Utils.isNotNullOrEmpty(tlsVersionsVar)) { - String[] tlsVersionsSplit = tlsVersionsVar.split(","); - TlsVersion[] tlsVersions = new TlsVersion[tlsVersionsSplit.length]; - for (int i = 0; i < tlsVersionsSplit.length; i++) { - tlsVersions[i] = TlsVersion.forJavaName(tlsVersionsSplit[i]); - } - config.setTlsVersions(tlsVersions); - } - } - - private static boolean tryServiceAccount(Config config) { - LOGGER.debug("Trying to configure client from service account..."); - String masterHost = Utils.getSystemPropertyOrEnvVar(KUBERNETES_SERVICE_HOST_PROPERTY, (String) null); - String masterPort = Utils.getSystemPropertyOrEnvVar(KUBERNETES_SERVICE_PORT_PROPERTY, (String) null); - String caCertPath = Utils.getSystemPropertyOrEnvVar(KUBERNETES_CA_CERTIFICATE_FILE_SYSTEM_PROPERTY, - KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH); - - if (masterHost != null && masterPort != null) { - String hostPort = joinHostPort(masterHost, masterPort); - LOGGER.debug("Found service account host and port: {}", hostPort); - config.setMasterUrl("https://" + hostPort); - } - if (Utils.getSystemPropertyOrEnvVar(KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, true)) { - boolean serviceAccountCaCertExists = Files.isRegularFile(new File(caCertPath).toPath()); - if (serviceAccountCaCertExists) { - LOGGER.debug("Found service account ca cert at: [{}}].", caCertPath); - config.setCaCertFile(caCertPath); - } else { - LOGGER.debug("Did not find service account ca cert at: [{}}].", caCertPath); - } - - File saTokenPathFile = findServiceAccountTokenFile(); - if (saTokenPathFile != null) { - String saTokenPathLocation = saTokenPathFile.getAbsolutePath(); - try { - String serviceTokenCandidate = new String(Files.readAllBytes(saTokenPathFile.toPath())); - LOGGER.debug("Found service account token at: [{}].", saTokenPathLocation); - config.setAutoOAuthToken(serviceTokenCandidate); - return true; - } catch (IOException e) { - // No service account token available... - LOGGER.warn("Error reading service account token from: [{}]. Ignoring.", saTokenPathLocation); - } - } - } - return false; - } - - private static File findServiceAccountTokenFile() { - File saTokenPathFile; - String saTokenPath = Utils.getSystemPropertyOrEnvVar(KUBERNETES_AUTH_SERVICEACCOUNT_TOKEN_FILE_SYSTEM_PROPERTY); - if (saTokenPath != null) { - // if user has set the service account token system property, we use it. - saTokenPathFile = new File(saTokenPath); - } else { - // otherwise, let's try the default location iif the location exists. - saTokenPathFile = new File(KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH); - if (!saTokenPathFile.exists()) { - saTokenPathFile = null; - LOGGER.debug("Could not find the service account token at the default location: [{}]. Ignoring.", - KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH); - } - } - - return saTokenPathFile; - } - - private static String joinHostPort(String host, String port) { - if (host.indexOf(':') >= 0) { - // Host is an IPv6 - return "[" + host + "]:" + port; - } - return host + ":" + port; - } - - private static String absolutify(File relativeTo, String filename) { - if (filename == null) { - return null; - } - - File file = new File(filename); - if (file.isAbsolute()) { - return file.getAbsolutePath(); - } - - return new File(relativeTo.getParentFile(), filename).getAbsolutePath(); - } - - public static Config fromKubeconfig(String kubeconfigContents) { - return fromKubeconfig(null, kubeconfigContents, null); - } - - // Note: kubeconfigPath is optional (see note on loadFromKubeConfig) - public static Config fromKubeconfig(String context, String kubeconfigContents, String kubeconfigPath) { - // we allow passing context along here, since downstream accepts it - Config config = new Config(false); - if (kubeconfigPath != null) { - config.setFile(new File(kubeconfigPath)); - } - if (!loadFromKubeconfig(config, context, kubeconfigContents)) { - throw new KubernetesClientException("Could not create Config from kubeconfig"); - } - if (!disableAutoConfig()) { - postAutoConfigure(config); - } - return config; - } - - /** - * Refresh the config from file / env sources. - * Any values that the user have programmatically set will be lost. - * - * @return - */ - public Config refresh() { - final String currentContextName = this.getCurrentContext() != null ? this.getCurrentContext().getName() : null; - if (Utils.isNotNullOrEmpty(this.oauthToken)) { - return this; - } - if (this.autoConfigure) { - return Config.autoConfigure(currentContextName); - } - // if files is null there's nothing to refresh - the kubeconfigs were directly supplied - if (!Utils.isNullOrEmpty(kubeConfigFiles)) { - io.fabric8.kubernetes.api.model.Config mergedConfig = mergeKubeConfigs(kubeConfigFiles); - if (mergedConfig != null) { - loadFromKubeconfig(this, mergedConfig.getCurrentContext(), mergedConfig); - } - } - return this; - } - - private static boolean tryKubeConfig(Config config, String context) { - LOGGER.debug("Trying to configure client from Kubernetes config..."); - if (!Utils.getSystemPropertyOrEnvVar(KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, true)) { - return false; - } - String[] kubeConfigFilenames = getKubeconfigFilenames(); - if (Utils.isNullOrEmpty(kubeConfigFilenames)) { - return false; - } - - List files = Arrays.stream(kubeConfigFilenames) - .map(KubeConfigFile::new) - .collect(Collectors.toList()); - config.setKubeConfigFiles(files); - io.fabric8.kubernetes.api.model.Config mergedConfig = mergeKubeConfigs(files); - return loadFromKubeconfig(config, context, mergedConfig); - } - - private static io.fabric8.kubernetes.api.model.Config mergeKubeConfigs(List files) { - if (Utils.isNullOrEmpty(files)) { - return null; - } - return files.stream() - .map(KubeConfigFile::getConfig) - .reduce(null, (merged, additionalConfig) -> { - if (additionalConfig == null) { - return merged; - } else { - return KubeConfigUtils.merge(additionalConfig, merged); - } - }); - } - - /** - * Returns the first filename of all the filenames that are used in this Config. - * - * @return the first config filename that is used in this config. - * - * @deprecated use {@link #getKubeconfigFilenames()} instead - */ - @Deprecated - public static String getKubeconfigFilename() { - String fileName = null; - String[] fileNames = getKubeconfigFilenames(); - if (Utils.isNotNullOrEmpty(fileNames)) { - // if system property/env var contains multiple files take the first one based on the environment - fileName = fileNames[0]; - if (fileNames.length > 1) { - LOGGER.info("Found multiple Kubernetes config files [{}], returning the first one. Use #getKubeconfigFilenames instead", - fileNames[0]); - } - } - return fileName; - } - - /** - * Returns all the filenames that are used in this Config. - * Several config files can be used by setting the {@link Config#KUBERNETES_KUBECONFIG_FILES} env variable. - * Returns the default file at {@link Config#DEFAULT_KUBECONFIG_FILE} otherwise - * - * @return all the config files that are used in this Config - */ - public static String[] getKubeconfigFilenames() { - String[] fileNames = null; - String fileName = Utils.getSystemPropertyOrEnvVar(KUBERNETES_KUBECONFIG_FILES); - if (Utils.isNotNullOrEmpty(fileName)) { - fileNames = fileName.split(File.pathSeparator); - } - if (Utils.isNullOrEmpty(fileNames)) { - fileNames = new String[] { DEFAULT_KUBECONFIG_FILE.toString() }; - } - return fileNames; - } - - // Note: kubeconfigPath is optional - // It is only used to rewrite relative tls asset paths inside kubeconfig when a file is passed, and in the case that - // the kubeconfig references some assets via relative paths. - private static boolean loadFromKubeconfig(Config config, String context, String kubeconfigContents) { - if (Utils.isNotNullOrEmpty(kubeconfigContents)) { - return loadFromKubeconfig(config, context, KubeConfigUtils.parseConfigFromString(kubeconfigContents)); - } else { - return false; - } - } - - private static boolean loadFromKubeconfig(Config config, String context, io.fabric8.kubernetes.api.model.Config kubeConfig) { - try { - if (kubeConfig != null) { - mergeKubeConfigContents(config, context, kubeConfig); - return true; - } - } catch (IOException ioException) { - throw KubernetesClientException.launderThrowable("Failed to parse the kubeconfig.", ioException); - } - - return false; - } - - private static void mergeKubeConfigContents(Config config, String context, io.fabric8.kubernetes.api.model.Config kubeConfig) - throws IOException { - config.setContexts(kubeConfig.getContexts()); - Context currentContext = setCurrentContext(context, config, kubeConfig); - Cluster currentCluster = KubeConfigUtils.getCluster(kubeConfig, currentContext); - if (currentContext != null) { - config.setNamespace(currentContext.getNamespace()); - } - if (currentCluster != null) { - config.setMasterUrl(currentCluster.getServer()); - config.setTrustCerts(currentCluster.getInsecureSkipTlsVerify() != null && currentCluster.getInsecureSkipTlsVerify()); - config.setDisableHostnameVerification( - currentCluster.getInsecureSkipTlsVerify() != null && currentCluster.getInsecureSkipTlsVerify()); - config.setCaCertData(currentCluster.getCertificateAuthorityData()); - if (currentContext != null) { - NamedAuthInfo currentAuthInfo = KubeConfigUtils.getAuthInfo(kubeConfig, currentContext.getUser()); - mergeKubeConfigAuthInfo(config, currentCluster, currentAuthInfo); - mergeProxyUrl(config, currentCluster.getProxyUrl()); - } - } - } - - private static void mergeProxyUrl(Config config, String proxyUrl) { - if (Utils.isNullOrEmpty(proxyUrl)) { - return; - } - if (proxyUrl.startsWith(SOCKS5_PROTOCOL_PREFIX) && config.getMasterUrl().startsWith(HTTPS_PROTOCOL_PREFIX)) { - config.setHttpsProxy(proxyUrl); - } else if (proxyUrl.startsWith(SOCKS5_PROTOCOL_PREFIX)) { - config.setHttpProxy(proxyUrl); - } else if (proxyUrl.startsWith(HTTP_PROTOCOL_PREFIX)) { - config.setHttpProxy(proxyUrl); - } else if (proxyUrl.startsWith(HTTPS_PROTOCOL_PREFIX)) { - config.setHttpsProxy(proxyUrl); - } - } - - private static void mergeKubeConfigAuthInfo(Config config, Cluster currentCluster, NamedAuthInfo currentAuthInfo) - throws IOException { - if (currentAuthInfo == null) { - return; - } - AuthInfo user = currentAuthInfo.getUser(); - KubeConfigFile kubeConfigFile = config.getFileWithAuthInfo(currentAuthInfo.getName()); - File file = (kubeConfigFile != null) ? kubeConfigFile.getFile() : null; - mergeCertFiles(config, file, currentCluster, user); - config.setClientCertData(user.getClientCertificateData()); - config.setClientKeyData(user.getClientKeyData()); - config.setClientKeyAlgo(getKeyAlgorithm(config.getClientKeyFile(), config.getClientKeyData())); - config.setAutoOAuthToken(user.getToken()); - config.setUsername(user.getUsername()); - config.setPassword(user.getPassword()); - - if (Utils.isNullOrEmpty(config.getAutoOAuthToken()) && user.getAuthProvider() != null) { - mergeKubeConfigAuthProviderConfig(config, user); - } else if (config.getOauthTokenProvider() == null) { // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins - mergeKubeConfigExecCredential(config, user.getExec(), file); - } - } - - private static void mergeCertFiles(Config config, File file, Cluster currentCluster, AuthInfo currentAuthInfo) { - if (config == null - || currentCluster == null - || currentAuthInfo == null) { - return; - } - String caCertFile = currentCluster.getCertificateAuthority(); - String clientCertFile = currentAuthInfo.getClientCertificate(); - String clientKeyFile = currentAuthInfo.getClientKey(); - if (file != null) { - // rewrite tls asset paths if needed - caCertFile = absolutify(file, currentCluster.getCertificateAuthority()); - clientCertFile = absolutify(file, currentAuthInfo.getClientCertificate()); - clientKeyFile = absolutify(file, currentAuthInfo.getClientKey()); - } - config.setCaCertFile(caCertFile); - config.setClientCertFile(clientCertFile); - config.setClientKeyFile(clientKeyFile); - } - - private static void mergeKubeConfigAuthProviderConfig(Config config, AuthInfo currentAuthInfo) { - if (currentAuthInfo.getAuthProvider().getConfig() != null) { - config.setAuthProvider(currentAuthInfo.getAuthProvider()); - if (!Utils.isNullOrEmpty(currentAuthInfo.getAuthProvider().getConfig().get(ACCESS_TOKEN))) { - // GKE token - config.setAutoOAuthToken(currentAuthInfo.getAuthProvider().getConfig().get(ACCESS_TOKEN)); - } else if (!Utils.isNullOrEmpty(currentAuthInfo.getAuthProvider().getConfig().get(ID_TOKEN))) { - // OpenID Connect token - config.setAutoOAuthToken(currentAuthInfo.getAuthProvider().getConfig().get(ID_TOKEN)); - } - } - } - - private static void mergeKubeConfigExecCredential(Config config, ExecConfig exec, File configFile) - throws IOException { - if (exec != null) { - try { - ExecCredential ec = getExecCredentialFromExecConfig(exec, configFile); - if (ec != null && ec.status != null) { - if (ec.status.token != null) { - config.setAutoOAuthToken(ec.status.token); - } else if (Utils.isNotNullOrEmpty(ec.status.clientCertificateData) - && Utils.isNotNullOrEmpty(ec.status.clientKeyData)) { - config.setClientCertData(ec.status.clientCertificateData); - config.setClientKeyData(ec.status.clientKeyData); - } else { - LOGGER.warn("No token or certificate returned"); - } - } - } catch (InterruptedException interruptedException) { - Thread.currentThread().interrupt(); - throw KubernetesClientException.launderThrowable("failure while running exec credential ", interruptedException); - } - } - } - - protected static ExecCredential getExecCredentialFromExecConfig(ExecConfig exec, File configFile) - throws IOException, InterruptedException { - String apiVersion = exec.getApiVersion(); - List env = exec.getEnv(); - // TODO check behavior of tty & stdin - ProcessBuilder pb = new ProcessBuilder( - getAuthenticatorCommandFromExecConfig(exec, configFile, Utils.getSystemPathVariable())); - pb.redirectErrorStream(true); - if (env != null) { - Map environment = pb.environment(); - env.forEach(var -> environment.put(var.getName(), var.getValue())); - } - Process p = pb.start(); - String output; - try (InputStream is = p.getInputStream()) { - output = IOHelpers.readFully(is); - } - if (p.waitFor() != 0) { - LOGGER.warn(output); - } - try { - ExecCredential ec = Serialization.unmarshal(output, ExecCredential.class); - if (!apiVersion.equals(ec.apiVersion)) { - LOGGER.warn("Wrong apiVersion {} vs. {}", ec.apiVersion, apiVersion); - } else { - return ec; - } - } catch (Exception ex) { - LOGGER.warn("Error unmarshalling ExecCredential", ex); - } - return null; - } - - protected static List getAuthenticatorCommandFromExecConfig(ExecConfig exec, File configFile, - String systemPathValue) { - String command = exec.getCommand(); - if (command.contains(File.separator) && !command.startsWith(File.separator) && configFile != null) { - // Appears to be a relative path; normalize. Spec is vague about how to detect this situation. - command = Paths.get(configFile.getAbsolutePath()).resolveSibling(command).normalize().toString(); - } - List argv = new ArrayList<>(Utils.getCommandPlatformPrefix()); - command = getCommandWithFullyQualifiedPath(command, systemPathValue); - - command = shellQuote(command); - - List args = exec.getArgs(); - if (Utils.isNotNullOrEmpty(args)) { - command += " " + args - .stream() - .map(Config::shellQuote) - .collect(Collectors.joining(" ")); - } - argv.add(command); - return argv; - } - - private static String shellQuote(String value) { - if (value.contains(" ") || value.contains("\"") || value.contains("'")) { - return "\"" + value.replace("\"", "\\\"") + "\""; - } - return value; - } - - protected static String getCommandWithFullyQualifiedPath(String command, String pathValue) { - String[] pathParts = pathValue.split(File.pathSeparator); - - // Iterate through path in order to find executable file - for (String pathPart : pathParts) { - File commandFile = new File(pathPart + File.separator + command); - if (commandFile.exists()) { - return commandFile.getAbsolutePath(); - } - } - - return command; - } - - private static Context setCurrentContext(String context, Config config, io.fabric8.kubernetes.api.model.Config kubeConfig) { - if (context != null) { - // override existing current-context - kubeConfig.setCurrentContext(context); - } - Context currentContext = null; - NamedContext currentNamedContext = KubeConfigUtils.getCurrentContext(kubeConfig); - if (currentNamedContext != null) { - config.setCurrentContext(currentNamedContext); - currentContext = currentNamedContext.getContext(); - } - return currentContext; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class ExecCredential { - public String kind; - public String apiVersion; - public ExecCredentialSpec spec; - public ExecCredentialStatus status; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class ExecCredentialSpec { - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class ExecCredentialStatus { - public String token; - public String clientCertificateData; - public String clientKeyData; - // TODO expirationTimestamp - } - - private static boolean tryNamespaceFromPath(Config config) { - LOGGER.debug("Trying to configure client namespace from Kubernetes service account namespace path..."); - if (Utils.getSystemPropertyOrEnvVar(KUBERNETES_TRYNAMESPACE_PATH_SYSTEM_PROPERTY, true)) { - String serviceAccountNamespace = Utils.getSystemPropertyOrEnvVar(KUBERNETES_NAMESPACE_FILE, KUBERNETES_NAMESPACE_PATH); - boolean serviceAccountNamespaceExists = Files.isRegularFile(new File(serviceAccountNamespace).toPath()); - if (serviceAccountNamespaceExists) { - LOGGER.debug("Found service account namespace at: [{}].", serviceAccountNamespace); - try { - String namespace = new String(Files.readAllBytes(new File(serviceAccountNamespace).toPath())); - config.setNamespace(namespace.replace(System.lineSeparator(), "")); - return true; - } catch (IOException e) { - LOGGER.error("Error reading service account namespace from: [" + serviceAccountNamespace + "].", e); - } - } else { - LOGGER.debug("Did not find service account namespace at: [{}]. Ignoring.", serviceAccountNamespace); - } - } - return false; - } - - protected static String getHomeDir(Predicate directoryExists, UnaryOperator getEnvVar) { - String home = getEnvVar.apply("HOME"); - if (Utils.isNotNullOrEmpty(home) && directoryExists.test(home)) { - return home; - } - String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT); - if (osName.startsWith("win")) { - String homeDrive = getEnvVar.apply("HOMEDRIVE"); - String homePath = getEnvVar.apply("HOMEPATH"); - if (homeDrive != null && !homeDrive.isEmpty() && homePath != null && !homePath.isEmpty()) { - String homeDir = homeDrive + homePath; - if (directoryExists.test(homeDir)) { - return homeDir; - } - } - String userProfile = getEnvVar.apply("USERPROFILE"); - if (Utils.isNotNullOrEmpty(userProfile) && directoryExists.test(userProfile)) { - return userProfile; - } - } - - // Fall back to user.home should never really get here - return System.getProperty("user.home", "."); - } - - public static String getKeyAlgorithm(InputStream inputStream) throws IOException { - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { - String line, algorithm = null; - - while ((line = bufferedReader.readLine()) != null) { - if (line.contains("BEGIN EC PRIVATE KEY")) - algorithm = "EC"; - else if (line.contains("BEGIN RSA PRIVATE KEY")) { - algorithm = "RSA"; - } - } - return algorithm; - } - } - - public static String getKeyAlgorithm(String clientKeyFile, String clientKeyData) { - // Check if any system property is set - if (Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_KEY_ALGO_SYSTEM_PROPERTY) != null) { - return Utils.getSystemPropertyOrEnvVar(KUBERNETES_CLIENT_KEY_ALGO_SYSTEM_PROPERTY); - } - - // Detect algorithm - try { - if (clientKeyData != null || clientKeyFile != null) { - ByteArrayInputStream keyInputStream = CertUtils.getInputStreamFromDataOrFile(clientKeyData, clientKeyFile); - return getKeyAlgorithm(keyInputStream); - } - } catch (IOException exception) { - LOGGER.debug("Failure in determining private key algorithm type, defaulting to RSA {}", exception.getMessage()); - } - return null; - } - - @JsonProperty("oauthToken") - public String getOauthToken() { - return oauthToken; - } - - public void setOauthToken(String oauthToken) { - this.oauthToken = oauthToken; - } - - @JsonProperty("password") - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @JsonProperty("username") - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - @JsonProperty("impersonateUsername") - public String getImpersonateUsername() { - return getRequestConfig().getImpersonateUsername(); - } - - public void setImpersonateUsername(String impersonateUsername) { - this.requestConfig.setImpersonateUsername(impersonateUsername); - } - - @JsonProperty("impersonateGroups") - public String[] getImpersonateGroups() { - return getRequestConfig().getImpersonateGroups(); - } - - public void setImpersonateGroups(String... impersonateGroup) { - this.requestConfig.setImpersonateGroups(impersonateGroup); - } - - @JsonProperty("impersonateExtras") - public Map> getImpersonateExtras() { - return getRequestConfig().getImpersonateExtras(); - } - - public void setImpersonateExtras(Map> impersonateExtras) { - this.requestConfig.setImpersonateExtras(impersonateExtras); - } - - @JsonProperty("clientKeyPassphrase") - public String getClientKeyPassphrase() { - return clientKeyPassphrase; - } - - public void setClientKeyPassphrase(String clientKeyPassphrase) { - this.clientKeyPassphrase = clientKeyPassphrase; - } - - @JsonProperty("clientKeyAlgo") - public String getClientKeyAlgo() { - return clientKeyAlgo; - } - - public void setClientKeyAlgo(String clientKeyAlgo) { - this.clientKeyAlgo = clientKeyAlgo; - } - - @JsonProperty("clientKeyData") - public String getClientKeyData() { - return clientKeyData; - } - - public void setClientKeyData(String clientKeyData) { - this.clientKeyData = clientKeyData; - } - - @JsonProperty("clientKeyFile") - public String getClientKeyFile() { - return clientKeyFile; - } - - public void setClientKeyFile(String clientKeyFile) { - this.clientKeyFile = clientKeyFile; - } - - @JsonProperty("clientCertData") - public String getClientCertData() { - return clientCertData; - } - - public void setClientCertData(String clientCertData) { - this.clientCertData = clientCertData; - } - - @JsonProperty("clientCertFile") - public String getClientCertFile() { - return clientCertFile; - } - - public void setClientCertFile(String clientCertFile) { - this.clientCertFile = clientCertFile; - } - - @JsonProperty("caCertData") - public String getCaCertData() { - return caCertData; - } - - public void setCaCertData(String caCertData) { - this.caCertData = caCertData; - } - - @JsonProperty("caCertFile") - public String getCaCertFile() { - return caCertFile; - } - - public void setCaCertFile(String caCertFile) { - this.caCertFile = caCertFile; - } - - @JsonProperty("apiVersion") - public String getApiVersion() { - return apiVersion; - } - - public void setApiVersion(String apiVersion) { - this.apiVersion = apiVersion; - } - - @JsonProperty("masterUrl") - public String getMasterUrl() { - return masterUrl; - } - - public void setMasterUrl(String masterUrl) { - //We set the masterUrl because it's needed by ensureHttps - this.masterUrl = masterUrl; - this.masterUrl = ensureEndsWithSlash(ensureHttps(masterUrl, this)); - } - - @JsonProperty("trustCerts") - public boolean isTrustCerts() { - return Optional.ofNullable(trustCerts).orElse(false); - } - - Boolean getTrustCerts() { - return trustCerts; - } - - public void setTrustCerts(boolean trustCerts) { - this.trustCerts = trustCerts; - } - - @JsonProperty("disableHostnameVerification") - public boolean isDisableHostnameVerification() { - return Optional.ofNullable(disableHostnameVerification).orElse(false); - } - - boolean getDisableHostnameVerification() { - return disableHostnameVerification; - } - - public void setDisableHostnameVerification(boolean disableHostnameVerification) { - this.disableHostnameVerification = disableHostnameVerification; - } - - @JsonProperty("watchReconnectInterval") - public int getWatchReconnectInterval() { - return requestConfig.getWatchReconnectInterval(); - } - - public void setWatchReconnectInterval(int watchReconnectInterval) { - this.requestConfig.setWatchReconnectInterval(watchReconnectInterval); - } - - @JsonProperty("watchReconnectLimit") - public int getWatchReconnectLimit() { - return getRequestConfig().getWatchReconnectLimit(); - } - - public void setWatchReconnectLimit(int watchReconnectLimit) { - this.requestConfig.setWatchReconnectLimit(watchReconnectLimit); - } - - public Map getErrorMessages() { - return errorMessages; - } - - public void setErrorMessages(Map errorMessages) { - this.errorMessages = errorMessages; - } - - public static ConfigBuilder builder() { - return new ConfigBuilder(); - } - - @JsonProperty("connectionTimeout") - public int getConnectionTimeout() { - return connectionTimeout; - } - - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - @JsonProperty("uploadRequestTimeout") - public int getUploadRequestTimeout() { - return getRequestConfig().getUploadRequestTimeout(); - } - - public void setUploadRequestTimeout(int requestTimeout) { - this.requestConfig.setUploadRequestTimeout(requestTimeout); - } - - @JsonProperty("requestTimeout") - public int getRequestTimeout() { - return getRequestConfig().getRequestTimeout(); - } - - public void setRequestTimeout(int requestTimeout) { - this.requestConfig.setRequestTimeout(requestTimeout); - } - - @JsonProperty("requestRetryBackoffLimit") - public int getRequestRetryBackoffLimit() { - return getRequestConfig().getRequestRetryBackoffLimit(); - } - - public void setRequestRetryBackoffLimit(int requestRetryBackoffLimit) { - requestConfig.setRequestRetryBackoffLimit(requestRetryBackoffLimit); - } - - @JsonProperty("requestRetryBackoffInterval") - public int getRequestRetryBackoffInterval() { - return getRequestConfig().getRequestRetryBackoffInterval(); - } - - public void setRequestRetryBackoffInterval(int requestRetryBackoffInterval) { - requestConfig.setRequestRetryBackoffInterval(requestRetryBackoffInterval); - } - - @JsonProperty("scaleTimeout") - public long getScaleTimeout() { - return getRequestConfig().getScaleTimeout(); - } - - public void setScaleTimeout(long scaleTimeout) { - this.requestConfig.setScaleTimeout(scaleTimeout); - } - - @JsonProperty("loggingInterval") - public int getLoggingInterval() { - return getRequestConfig().getLoggingInterval(); - } - - public void setLoggingInterval(int loggingInterval) { - this.requestConfig.setLoggingInterval(loggingInterval); - } - - @JsonProperty("http2Disable") - public boolean isHttp2Disable() { - return Optional.ofNullable(http2Disable).orElse(false); - } - - Boolean getHttp2Disable() { - return http2Disable; - } - - public void setHttp2Disable(Boolean http2Disable) { - this.http2Disable = http2Disable; - } - - public void setHttpProxy(String httpProxy) { - this.httpProxy = httpProxy; - } - - @JsonProperty("httpProxy") - public String getHttpProxy() { - return httpProxy; - } - - public void setHttpsProxy(String httpsProxy) { - this.httpsProxy = httpsProxy; - } - - @JsonProperty("httpsProxy") - public String getHttpsProxy() { - return httpsProxy; - } - - public void setNoProxy(String[] noProxy) { - this.noProxy = noProxy; - } - - @JsonProperty("noProxy") - public String[] getNoProxy() { - return noProxy; - } - - @JsonProperty("namespace") - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - @JsonProperty("defaultNamespace") - public boolean isDefaultNamespace() { - return Optional.ofNullable(defaultNamespace).orElse(true); - } - - public void setDefaultNamespace(boolean defaultNamespace) { - this.defaultNamespace = defaultNamespace; - } - - @JsonProperty("userAgent") - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - @JsonProperty("tlsVersions") - public TlsVersion[] getTlsVersions() { - return tlsVersions; - } - - public void setTlsVersions(TlsVersion[] tlsVersions) { - this.tlsVersions = tlsVersions; - } - - @JsonProperty("websocketPingInterval") - public long getWebsocketPingInterval() { - return websocketPingInterval; - } - - public void setWebsocketPingInterval(Long websocketPingInterval) { - this.websocketPingInterval = websocketPingInterval; - } - - public int getMaxConcurrentRequests() { - return maxConcurrentRequests; - } - - public void setMaxConcurrentRequests(int maxConcurrentRequests) { - this.maxConcurrentRequests = maxConcurrentRequests; - } - - public int getMaxConcurrentRequestsPerHost() { - return maxConcurrentRequestsPerHost; - } - - public void setMaxConcurrentRequestsPerHost(int maxConcurrentRequestsPerHost) { - this.maxConcurrentRequestsPerHost = maxConcurrentRequestsPerHost; - } - - @JsonProperty("proxyUsername") - public String getProxyUsername() { - return proxyUsername; - } - - public void setProxyUsername(String proxyUsername) { - this.proxyUsername = proxyUsername; - } - - @JsonProperty("proxyPassword") - public String getProxyPassword() { - return proxyPassword; - } - - public void setProxyPassword(String proxyPassword) { - this.proxyPassword = proxyPassword; - } - - public RequestConfig getRequestConfig() { - return this.requestConfig; - } - - public void setTrustStorePassphrase(String trustStorePassphrase) { - this.trustStorePassphrase = trustStorePassphrase; - } - - @JsonProperty("trustStorePassphrase") - public String getTrustStorePassphrase() { - return trustStorePassphrase; - } - - public void setKeyStorePassphrase(String keyStorePassphrase) { - this.keyStorePassphrase = keyStorePassphrase; - } - - @JsonProperty("keyStorePassphrase") - public String getKeyStorePassphrase() { - return keyStorePassphrase; - } - - public void setTrustStoreFile(String trustStoreFile) { - this.trustStoreFile = trustStoreFile; - } - - @JsonProperty("trustStoreFile") - public String getTrustStoreFile() { - return trustStoreFile; - } - - public void setKeyStoreFile(String keyStoreFile) { - this.keyStoreFile = keyStoreFile; - } - - @JsonProperty("keyStoreFile") - public String getKeyStoreFile() { - return keyStoreFile; - } - - @JsonIgnore - public OAuthTokenProvider getOauthTokenProvider() { - return this.oauthTokenProvider; - } - - public void setOauthTokenProvider(OAuthTokenProvider oauthTokenProvider) { - this.oauthTokenProvider = oauthTokenProvider; - } - - @JsonProperty("customHeaders") - public Map getCustomHeaders() { - return customHeaders; - } - - public void setCustomHeaders(Map customHeaders) { - this.customHeaders = customHeaders; - } - - public boolean getAutoConfigure() { - return autoConfigure; - } - - /** - * Returns all the {@link NamedContext}s that exist in the kube config - * - * @return all the contexts - * - * @see NamedContext - */ - public List getContexts() { - return contexts; - } - - public void setContexts(List contexts) { - this.contexts = contexts; - } - - /** - * Returns the current context that's defined in the kube config. Returns {@code null} if there's none - * - * @return the current context - * - * @see NamedContext - */ - public NamedContext getCurrentContext() { - return currentContext; - } - - public void setCurrentContext(NamedContext context) { - this.currentContext = context; - } - - /** - * - * Returns the path to the file that this configuration was loaded from. Returns {@code null} if no file was used. - * - * @return the kubeConfig file - */ - public File getFile() { - if (Utils.isNotNullOrEmpty(kubeConfigFiles)) { - return kubeConfigFiles.get(0).getFile(); - } else { - return null; - } - } - - public KubeConfigFile getFileWithAuthInfo(String name) { - if (Utils.isNullOrEmpty(name)) { - return null; - } - return getFirstKubeConfigFileMatching(config -> KubeConfigUtils.hasAuthInfoNamed(config, name)); - } - - public KubeConfigFile getFileWithContext(String name) { - if (Utils.isNullOrEmpty(name)) { - return null; - } - return getFirstKubeConfigFileMatching(config -> KubeConfigUtils.getContext(config, name) != null); - } - - public KubeConfigFile getFileWithCurrentContext() { - return getFirstKubeConfigFileMatching(config -> Utils.isNotNullOrEmpty(config.getCurrentContext())); - } - - private KubeConfigFile getFirstKubeConfigFileMatching(Predicate predicate) { - if (Utils.isNullOrEmpty(kubeConfigFiles)) { - return null; - } - return kubeConfigFiles.stream() - .filter(KubeConfigFile::isReadable) - .filter(entry -> predicate.test(entry.getConfig())) - .findFirst() - .orElse(null); - } - - @JsonIgnore - public Readiness getReadiness() { - return Readiness.getInstance(); - } - - public void setAuthProvider(AuthProviderConfig authProvider) { - this.authProvider = authProvider; - } - - public AuthProviderConfig getAuthProvider() { - return authProvider; - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @JsonAnySetter - public void setAdditionalProperty(String name, Object value) { - this.additionalProperties.put(name, value); - } - - public void setFile(File file) { - setFiles(Collections.singletonList(file)); - } - - public void setFiles(List files) { - if (Utils.isNullOrEmpty(files)) { - this.kubeConfigFiles = Collections.emptyList(); - } else { - this.kubeConfigFiles = files.stream() - .map(KubeConfigFile::new) - .collect(Collectors.toList()); - } - } - - public void setKubeConfigFiles(List files) { - if (Utils.isNullOrEmpty(files)) { - this.kubeConfigFiles = Collections.emptyList(); - } else { - this.kubeConfigFiles = files; - } - } - - /** - * Returns the kube config files that are used to configure this client. - * Returns the files that are listed in the KUBERNETES_KUBECONFIG_FILES env or system variables. - * Returns the default kube config file if it's not set'. - * - * @return - */ - public List getFiles() { - if (this.kubeConfigFiles == null) { - return Collections.emptyList(); - } else { - return kubeConfigFiles.stream() - .map(KubeConfigFile::getFile) - .collect(Collectors.toList()); - } - } - - public void setAutoConfigure(boolean autoConfigure) { - this.autoConfigure = autoConfigure; - } - - public String getAutoOAuthToken() { - return autoOAuthToken; - } - - public void setAutoOAuthToken(String autoOAuthToken) { - this.autoOAuthToken = autoOAuthToken; - } - - public boolean isOnlyHttpWatches() { - return Optional.ofNullable(onlyHttpWatches).orElse(false); - } - - public void setOnlyHttpWatches(boolean onlyHttpWatches) { - this.onlyHttpWatches = onlyHttpWatches; - } - -} diff --git a/src/main/java/io/fabric8/kubernetes/client/KubeConfigFile.java b/src/main/java/io/fabric8/kubernetes/client/KubeConfigFile.java deleted file mode 100644 index 9c223e9ed..000000000 --- a/src/main/java/io/fabric8/kubernetes/client/KubeConfigFile.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client; - -import io.fabric8.kubernetes.api.model.Config; -import io.fabric8.kubernetes.client.internal.KubeConfigUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; - -public class KubeConfigFile { - - private static final Logger LOGGER = LoggerFactory.getLogger(KubeConfigFile.class); - - private final File file; - private boolean parsed = false; - private Config config; - - public KubeConfigFile(String file) { - this(new File(file), null); - } - - public KubeConfigFile(File file) { - this(file, null); - } - - private KubeConfigFile(File file, Config config) { - this.file = file; - this.config = config; - } - - public File getFile() { - return file; - } - - public Config getConfig() { - if (!parsed) { - this.config = createConfig(file); - this.parsed = true; - } - return config; - } - - private Config createConfig(File file) { - Config config = null; - try { - if (isReadable(file)) { - LOGGER.debug("Found for Kubernetes config at: [{}].", file.getPath()); - config = KubeConfigUtils.parseConfig(file); - } - } catch (IOException e) { - LOGGER.debug("Kubernetes file at [{}] is not a valid config. Ignoring.", file.getPath(), e); - } - return config; - } - - public boolean isReadable() { - return isReadable(file); - } - - private boolean isReadable(File file) { - try { - return file != null - && file.isFile(); - } catch (SecurityException e) { - return false; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java b/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java deleted file mode 100644 index 4f017dd1a..000000000 --- a/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client.internal; - -import io.fabric8.kubernetes.api.model.AuthInfo; -import io.fabric8.kubernetes.api.model.Cluster; -import io.fabric8.kubernetes.api.model.Config; -import io.fabric8.kubernetes.api.model.ConfigBuilder; -import io.fabric8.kubernetes.api.model.Context; -import io.fabric8.kubernetes.api.model.NamedAuthInfo; -import io.fabric8.kubernetes.api.model.NamedCluster; -import io.fabric8.kubernetes.api.model.NamedContext; -import io.fabric8.kubernetes.api.model.NamedExtension; -import io.fabric8.kubernetes.api.model.PreferencesBuilder; -import io.fabric8.kubernetes.client.utils.Serialization; -import io.fabric8.kubernetes.client.utils.Utils; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; - -/** - * Helper class for working with the YAML config file thats located in - * ~/.kube/config which is updated when you use commands - * like osc login and osc project myproject - */ -public class KubeConfigUtils { - private KubeConfigUtils() { - } - - public static Config parseConfig(File file) throws IOException { - return Serialization.unmarshal(Files.newInputStream(file.toPath()), Config.class); - } - - public static Config parseConfigFromString(String contents) { - return Serialization.unmarshal(contents, Config.class); - } - - /** - * Returns the current context in the given config - * - * @param config Config object - * @return returns context in config if found, otherwise null - */ - public static NamedContext getCurrentContext(Config config) { - String contextName = config.getCurrentContext(); - if (contextName != null) { - return getContext(config, contextName); - } - return null; - } - - /** - * Returns the {@link NamedContext} with the given name. - * Returns {@code null} otherwise - * - * @param config the config to search - * @param name the context name to match - * @return the context with the the given name - */ - public static NamedContext getContext(Config config, String name) { - NamedContext context = null; - if (config != null && name != null) { - List contexts = config.getContexts(); - if (contexts != null) { - context = contexts.stream() - .filter(toInspect -> name.equals(toInspect.getName())) - .findAny() - .orElse(null); - } - } - return context; - } - - /** - * Returns the current user token for the config and current context - * - * @param config Config object - * @param context Context object - * @return returns current user based upon provided parameters. - */ - public static String getUserToken(Config config, Context context) { - AuthInfo authInfo = getUserAuthInfo(config, context); - if (authInfo != null) { - return authInfo.getToken(); - } - return null; - } - - /** - * Returns the current {@link AuthInfo} for the current context and user - * - * @param config Config object - * @param context Context object - * @return {@link AuthInfo} for current context - */ - public static AuthInfo getUserAuthInfo(Config config, Context context) { - NamedAuthInfo namedAuthInfo = getAuthInfo(config, context.getUser()); - return (namedAuthInfo != null) ? namedAuthInfo.getUser() : null; - } - - /** - * Returns the {@link NamedAuthInfo} with the given name. - * Returns {@code null} otherwise - * - * @param config the config to search - * @param name - * @return - */ - public static NamedAuthInfo getAuthInfo(Config config, String name) { - NamedAuthInfo authInfo = null; - if (config != null && name != null) { - List users = config.getUsers(); - if (users != null) { - authInfo = users.stream() - .filter(toInspect -> name.equals(toInspect.getName())) - .findAny() - .orElse(null); - } - } - return authInfo; - } - - /** - * Returns {@code true} if the given {@link Config} has a {@link NamedAuthInfo} with the given name. - * Returns {@code false} otherwise. - * - * @param name the name of the NamedAuthInfo that we are looking for - * @param config the Config to search - * @return true if it contains a NamedAuthInfo with the given name - */ - public static boolean hasAuthInfoNamed(Config config, String name) { - if (Utils.isNullOrEmpty(name) - || config == null - || config.getUsers() == null) { - return false; - } - return getAuthInfo(config, name) != null; - } - - /** - * Returns the current {@link Cluster} for the current context - * - * @param config {@link Config} config object - * @param context {@link Context} context object - * @return current {@link Cluster} for current context - */ - public static Cluster getCluster(Config config, Context context) { - Cluster cluster = null; - if (config != null && context != null) { - String clusterName = context.getCluster(); - if (clusterName != null) { - List clusters = config.getClusters(); - if (clusters != null) { - cluster = clusters.stream() - .filter(c -> c.getName().equals(clusterName)) - .findAny() - .map(NamedCluster::getCluster) - .orElse(null); - } - } - } - return cluster; - } - - /** - * Get User index from Config object - * - * @param config {@link io.fabric8.kubernetes.api.model.Config} Kube Config - * @param userName username inside Config - * @return index of user in users array - */ - public static int getNamedUserIndexFromConfig(Config config, String userName) { - for (int i = 0; i < config.getUsers().size(); i++) { - if (config.getUsers().get(i).getName().equals(userName)) { - return i; - } - } - return -1; - } - - /** - * Modify KUBECONFIG file - * - * @param kubeConfig modified {@link io.fabric8.kubernetes.api.model.Config} object - * @param kubeConfigPath path to KUBECONFIG - * @throws IOException in case of failure while writing to file - */ - public static void persistKubeConfigIntoFile(Config kubeConfig, String kubeConfigPath) throws IOException { - try (FileWriter writer = new FileWriter(kubeConfigPath)) { - writer.write(Serialization.asYaml(kubeConfig)); - } - } - - public static Config merge(Config thisConfig, Config thatConfig) { - if (thisConfig == null) { - return thatConfig; - } - ConfigBuilder builder = new ConfigBuilder(thatConfig); - if (thisConfig.getClusters() != null) { - builder.addAllToClusters(thisConfig.getClusters()); - } - if (thisConfig.getContexts() != null) { - builder.addAllToContexts(thisConfig.getContexts()); - } - if (thisConfig.getUsers() != null) { - builder.addAllToUsers(thisConfig.getUsers()); - } - if (thisConfig.getExtensions() != null) { - builder.addAllToExtensions(thisConfig.getExtensions()); - } - if (!builder.hasCurrentContext() - && Utils.isNotNullOrEmpty(thisConfig.getCurrentContext())) { - builder.withCurrentContext(thisConfig.getCurrentContext()); - } - Config merged = builder.build(); - mergePreferences(thisConfig, merged); - return merged; - } - - public static void mergePreferences(io.fabric8.kubernetes.api.model.Config source, - io.fabric8.kubernetes.api.model.Config destination) { - if (source.getPreferences() != null) { - PreferencesBuilder builder = new PreferencesBuilder(destination.getPreferences()); - builder.addToExtensions(source.getExtensions().toArray(new NamedExtension[] {})); - destination.setPreferences(builder.build()); - } - } -} diff --git a/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java b/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java deleted file mode 100644 index 030ace7eb..000000000 --- a/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client.utils; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.fabric8.kubernetes.api.model.AuthInfo; -import io.fabric8.kubernetes.api.model.AuthProviderConfig; -import io.fabric8.kubernetes.api.model.NamedAuthInfo; -import io.fabric8.kubernetes.api.model.NamedAuthInfoBuilder; -import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.KubeConfigFile; -import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.kubernetes.client.http.HttpClient; -import io.fabric8.kubernetes.client.http.HttpRequest; -import io.fabric8.kubernetes.client.internal.KubeConfigUtils; -import io.fabric8.kubernetes.client.internal.SSLUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.TrustManager; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.security.spec.InvalidKeySpecException; -import java.time.Instant; -import java.util.Base64; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -/** - * Utility class for OpenID token refresh. - */ -public class OpenIDConnectionUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(OpenIDConnectionUtils.class); - - public static final String ID_TOKEN_KUBECONFIG = "id-token"; - public static final String ISSUER_KUBECONFIG = "idp-issuer-url"; - public static final String REFRESH_TOKEN_KUBECONFIG = "refresh-token"; - private static final String REFRESH_TOKEN_PARAM = "refresh_token"; - public static final String GRANT_TYPE_PARAM = "grant_type"; - public static final String CLIENT_ID_PARAM = "client_id"; - public static final String CLIENT_SECRET_PARAM = "client_secret"; - public static final String CLIENT_ID_KUBECONFIG = "client-id"; - public static final String CLIENT_SECRET_KUBECONFIG = "client-secret"; - private static final String IDP_CERT_DATA = "idp-certificate-authority-data"; - private static final String WELL_KNOWN_OPENID_CONFIGURATION = ".well-known/openid-configuration"; - private static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; - private static final String JWT_TOKEN_EXPIRY_TIMESTAMP_KEY = "exp"; - private static final String JWT_PARTS_DELIMITER_REGEX = "\\."; - private static final int TOKEN_EXPIRY_DELTA = 10; - - private OpenIDConnectionUtils() { - } - - /** - * Fetch OpenID Connect token from Kubeconfig, check whether it's still valid or not; If expired handle - * token refresh with OpenID Connection provider APIs - * - * @param currentAuthProviderConfig current AuthInfo's AuthProvider config as a map - * @return access token for interacting with Kubernetes API - */ - public static CompletableFuture resolveOIDCTokenFromAuthConfig( - Config currentConfig, Map currentAuthProviderConfig, HttpClient.Builder clientBuilder) { - String originalToken = currentAuthProviderConfig.get(ID_TOKEN_KUBECONFIG); - String idpCert = currentAuthProviderConfig.getOrDefault(IDP_CERT_DATA, getClientCertDataFromConfig(currentConfig)); - if (isTokenRefreshSupported(currentAuthProviderConfig)) { - final HttpClient httpClient = initHttpClientWithPemCert(idpCert, clientBuilder); - final CompletableFuture result = getOpenIdConfiguration(httpClient, currentAuthProviderConfig) - .thenCompose(openIdConfiguration -> refreshOpenIdToken(httpClient, currentAuthProviderConfig, openIdConfiguration)) - .thenApply(oAuthToken -> persistOAuthToken(currentConfig, oAuthToken, null)) - .thenApply(oAuthToken -> { - if (oAuthToken == null || Utils.isNullOrEmpty(oAuthToken.idToken)) { - LOGGER.warn("token response did not contain an id_token, either the scope \\\"openid\\\" wasn't " + - "requested upon login, or the provider doesn't support id_tokens as part of the refresh response."); - return originalToken; - } - return oAuthToken.idToken; - }); - result.whenComplete((s, t) -> httpClient.close()); - return result; - } - return CompletableFuture.completedFuture(originalToken); - } - - /** - * Whether we should try to do token refresh or not, checks whether refresh-token key is set in - * HashMap or not - * - * @param currentAuthProviderConfig a HashMap of current AuthProvider configuration - * @return boolean value whether refresh-token is present or not. - */ - static boolean isTokenRefreshSupported(Map currentAuthProviderConfig) { - return Utils.isNotNull(currentAuthProviderConfig.get(REFRESH_TOKEN_KUBECONFIG)); - } - - /** - * OpenID providers publish their metadata at a well-known URL which looks like this: - * https://[base-server-url]/.well-known/openid-configuration - * - * This method performs an HTTP GET at this public URL and fetches response as a Map. - * - * @param client HttpClient for doing HTTP Get to well known URL of OpenID provider - * @param authProviderConfig OpenID Connect provider information - * @return the OpenID Configuration as returned by the OpenID provider - */ - private static CompletableFuture getOpenIdConfiguration(HttpClient client, - Map authProviderConfig) { - final HttpRequest request = client.newHttpRequestBuilder() - .uri(resolveWellKnownUrlForOpenIDIssuer(authProviderConfig)).build(); - return client.sendAsync(request, String.class).thenApply(response -> { - try { - if (response.isSuccessful() && response.body() != null) { - return Serialization.unmarshal(response.body(), OpenIdConfiguration.class); - } else { - // Don't produce an error that's too huge (e.g. if we get HTML back for some reason). - String responseBody = response.body(); - LOGGER.warn("oidc: failed to query metadata endpoint: {} {}", response.code(), responseBody); - } - } catch (Exception e) { - LOGGER.warn("Could not refresh OIDC token, failure in getting refresh URL", e); - } - return null; - }); - } - - /** - * Issue Token Refresh HTTP Request to OIDC Provider - */ - private static CompletableFuture refreshOpenIdToken( - HttpClient httpClient, Map authProviderConfig, OpenIdConfiguration openIdConfiguration) { - if (openIdConfiguration == null || Utils.isNullOrEmpty(openIdConfiguration.tokenEndpoint)) { - LOGGER.warn("oidc: discovery object doesn't contain a valid token endpoint: {}", openIdConfiguration); - return CompletableFuture.completedFuture(null); - } - final HttpRequest request = initTokenRefreshHttpRequest(httpClient, authProviderConfig, - openIdConfiguration.tokenEndpoint); - return httpClient.sendAsync(request, String.class).thenApply(r -> { - String body = r.body(); - if (body != null) { - // Get response body as string - if (r.isSuccessful()) { - // Deserialize response body into a Map and return - try { - return Serialization.unmarshal(body, OAuthToken.class); - } catch (Exception e) { - LOGGER.warn("Failure in fetching refresh token: ", e); - } - } else { - // Log error response body - LOGGER.warn("Response: {}", body); - } - } - return null; - }); - } - - /** - * Save Updated Access and Refresh token in local KubeConfig file and in-memory Config object. - * - * @param currentConfig current Config object. - * @param oAuthToken OAuth token information as received from OpenID provider. - * @param token new token to be persisted in KubeConfig (if not null). - * @return the oAuthToken for chaining and further processing. - */ - public static OAuthToken persistOAuthToken(Config currentConfig, OAuthToken oAuthToken, String token) { - final Map authProviderConfig = new HashMap<>(); - if (oAuthToken != null) { - authProviderConfig.put(ID_TOKEN_KUBECONFIG, oAuthToken.idToken); - authProviderConfig.put(REFRESH_TOKEN_KUBECONFIG, oAuthToken.refreshToken); - persistOAuthTokenToFile(currentConfig.getAuthProvider(), authProviderConfig); - } - persistOAuthTokenToFile(currentConfig, token, authProviderConfig); - - return oAuthToken; - } - - private static void persistOAuthTokenToFile(Config currentConfig, String token, Map authProviderConfig) { - if (currentConfig.getCurrentContext() != null - && currentConfig.getCurrentContext().getContext() != null) { - try { - final String userName = currentConfig.getCurrentContext().getContext().getUser(); - KubeConfigFile kubeConfigFile = currentConfig.getFileWithAuthInfo(userName); - if (kubeConfigFile == null - || kubeConfigFile.getConfig() == null) { - LOGGER.warn("oidc: failure while persisting new tokens into KUBECONFIG: file for user {} not found", userName); - return; - } - final NamedAuthInfo namedAuthInfo = getOrCreateNamedAuthInfo(userName, kubeConfigFile.getConfig()); - setAuthProviderAndToken(token, authProviderConfig, namedAuthInfo); - - KubeConfigUtils.persistKubeConfigIntoFile(kubeConfigFile.getConfig(), kubeConfigFile.getFile().getAbsolutePath()); - } catch (IOException ex) { - LOGGER.warn("oidc: failure while persisting new tokens into KUBECONFIG", ex); - } - } - } - - private static void setAuthProviderAndToken(String token, Map authProviderConfig, - NamedAuthInfo namedAuthInfo) { - if (namedAuthInfo.getUser() == null) { - namedAuthInfo.setUser(new AuthInfo()); - } - if (namedAuthInfo.getUser().getAuthProvider() == null) { - namedAuthInfo.getUser().setAuthProvider(new AuthProviderConfig()); - } - namedAuthInfo.getUser().getAuthProvider().getConfig().putAll(authProviderConfig); - if (Utils.isNotNullOrEmpty(token)) { - namedAuthInfo.getUser().setToken(token); - } - } - - private static NamedAuthInfo getOrCreateNamedAuthInfo(String name, io.fabric8.kubernetes.api.model.Config kubeConfig) { - return kubeConfig.getUsers().stream() - .filter(n -> n.getName().equals(name)) - .findFirst() - .orElseGet(() -> { - NamedAuthInfo authInfo = new NamedAuthInfoBuilder() - .withName(name) - .withNewUser() - .endUser() - .build(); - kubeConfig.getUsers().add(authInfo); - return authInfo; - }); - } - - private static void persistOAuthTokenToFile(AuthProviderConfig config, Map authProviderConfig) { - if (config == null) { - return; - } - Optional.of(config) - .map(AuthProviderConfig::getConfig) - .ifPresent(c -> c.putAll(authProviderConfig)); - } - - /** - * Well known URL for getting OpenID Connect metadata. - * https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig - * - * @param authProviderConfig containing the issuing authority URL - * @return well known URL for corresponding OpenID provider - */ - private static String resolveWellKnownUrlForOpenIDIssuer(Map authProviderConfig) { - return URLUtils.join(authProviderConfig.get(ISSUER_KUBECONFIG), "/", WELL_KNOWN_OPENID_CONFIGURATION); - } - - private static HttpClient initHttpClientWithPemCert(String idpCert, HttpClient.Builder clientBuilder) { - // fist, lets get the pem - String pemCert = new String(java.util.Base64.getDecoder().decode(idpCert)); - try { - final TrustManager[] trustManagers = SSLUtils.trustManagers(pemCert, null, false, null, null); - final KeyManager[] keyManagers = SSLUtils.keyManagers(pemCert, null, null, null, null, null, null, null); - - clientBuilder.sslContext(keyManagers, trustManagers); - return clientBuilder.build(); - } catch (KeyStoreException | InvalidKeySpecException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException - | CertificateException e) { - throw KubernetesClientException.launderThrowable("Could not import idp certificate", e); - } - } - - private static HttpRequest initTokenRefreshHttpRequest( - HttpClient client, Map authProviderConfig, String tokenRefreshUrl) { - - final String clientId = authProviderConfig.get(CLIENT_ID_KUBECONFIG); - final String clientSecret = authProviderConfig.getOrDefault(CLIENT_SECRET_KUBECONFIG, ""); - final HttpRequest.Builder httpRequestBuilder = client.newHttpRequestBuilder().uri(tokenRefreshUrl); - final String credentials = java.util.Base64.getEncoder().encodeToString((clientId + ':' + clientSecret) - .getBytes(StandardCharsets.UTF_8)); - httpRequestBuilder.header("Authorization", "Basic " + credentials); - - final Map requestBody = new LinkedHashMap<>(); - requestBody.put(REFRESH_TOKEN_PARAM, authProviderConfig.get(REFRESH_TOKEN_KUBECONFIG)); - requestBody.put(GRANT_TYPE_PARAM, GRANT_TYPE_REFRESH_TOKEN); - requestBody.put(CLIENT_ID_PARAM, clientId); - requestBody.put(CLIENT_SECRET_PARAM, clientSecret); - - httpRequestBuilder.post(requestBody); - return httpRequestBuilder.build(); - } - - public static boolean idTokenExpired(Config config) { - if (config.getAuthProvider() != null && config.getAuthProvider().getConfig() != null) { - Map authProviderConfig = config.getAuthProvider().getConfig(); - String accessToken = authProviderConfig.get(ID_TOKEN_KUBECONFIG); - if (isValidJwt(accessToken)) { - try { - String[] jwtParts = accessToken.split(JWT_PARTS_DELIMITER_REGEX); - String jwtPayload = jwtParts[1]; - String jwtPayloadDecoded = new String(Base64.getDecoder().decode(jwtPayload)); - Map jwtPayloadMap = Serialization.unmarshal(jwtPayloadDecoded, Map.class); - int expiryTimestampInSeconds = (Integer) jwtPayloadMap.get(JWT_TOKEN_EXPIRY_TIMESTAMP_KEY); - return Instant.ofEpochSecond(expiryTimestampInSeconds) - .minusSeconds(TOKEN_EXPIRY_DELTA) - .isBefore(Instant.now()); - } catch (Exception e) { - return true; - } - } - } - return true; - } - - private static boolean isValidJwt(String token) { - if (token != null && !token.isEmpty()) { - String[] jwtParts = token.split(JWT_PARTS_DELIMITER_REGEX); - return jwtParts.length == 3; - } - return false; - } - - private static String getClientCertDataFromConfig(Config config) { - if (config.getCaCertData() != null && !config.getCaCertData().isEmpty()) { - return config.getCaCertData(); - } - try { - if (config.getCaCertFile() != null) { - return java.util.Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get(config.getCaCertFile()))); - } - } catch (IOException e) { - LOGGER.debug("Failure in reading certificate data from {}", config.getCaCertFile()); - } - return null; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class OpenIdConfiguration { - @JsonProperty("token_endpoint") - private String tokenEndpoint; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class OAuthToken { - @JsonProperty("id_token") - private String idToken; - @JsonProperty("refresh_token") - private String refreshToken; - } -} diff --git a/src/main/java/io/fabric8/kubernetes/client/utils/Utils.java b/src/main/java/io/fabric8/kubernetes/client/utils/Utils.java deleted file mode 100644 index 8db382b5c..000000000 --- a/src/main/java/io/fabric8/kubernetes/client/utils/Utils.java +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client.utils; - -import io.fabric8.kubernetes.api.Pluralize; -import io.fabric8.kubernetes.api.model.Namespaced; -import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.kubernetes.model.annotation.Group; -import io.fabric8.kubernetes.model.annotation.Version; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.io.Flushable; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.annotation.Annotation; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.function.LongSupplier; -import java.util.function.Supplier; -import java.util.stream.Stream; - -public class Utils { - - private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); - private static final String ALL_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"; - public static final String WINDOWS = "win"; - public static final String OS_NAME = "os.name"; - public static final String PATH_WINDOWS = "Path"; - public static final String PATH_UNIX = "PATH"; - private static final Random random = new Random(); - private static final AtomicLong leastSigBits = new AtomicLong(); - - private static final CachedSingleThreadScheduler SHARED_SCHEDULER = new CachedSingleThreadScheduler(); - - private Utils() { - } - - public static T checkNotNull(T ref, String message) { - if (ref == null) { - throw new NullPointerException(message); - } - return ref; - } - - public static String getSystemPropertyOrEnvVar(String systemPropertyName, String envVarName, String defaultValue) { - String answer = System.getProperty(systemPropertyName); - if (isNotNullOrEmpty(answer)) { - return answer; - } - - answer = System.getenv(envVarName); - if (isNotNullOrEmpty(answer)) { - return answer; - } - - return defaultValue; - } - - public static String convertSystemPropertyNameToEnvVar(String systemPropertyName) { - return systemPropertyName.toUpperCase(Locale.ROOT).replaceAll("[.-]", "_"); - } - - public static String getEnvVar(String envVarName, String defaultValue) { - String answer = System.getenv(envVarName); - return isNotNullOrEmpty(answer) ? answer : defaultValue; - } - - public static String getSystemPropertyOrEnvVar(String systemPropertyName, String defaultValue) { - return getSystemPropertyOrEnvVar(systemPropertyName, convertSystemPropertyNameToEnvVar(systemPropertyName), defaultValue); - } - - public static String getSystemPropertyOrEnvVar(String systemPropertyName) { - return getSystemPropertyOrEnvVar(systemPropertyName, (String) null); - } - - public static boolean getSystemPropertyOrEnvVar(String systemPropertyName, Boolean defaultValue) { - String result = getSystemPropertyOrEnvVar(systemPropertyName, defaultValue.toString()); - return Boolean.parseBoolean(result); - } - - public static int getSystemPropertyOrEnvVar(String systemPropertyName, int defaultValue) { - String result = getSystemPropertyOrEnvVar(systemPropertyName, Integer.toString(defaultValue)); - return Integer.parseInt(result); - } - - public static String join(final Object[] array) { - return join(array, ','); - } - - public static String join(final Object[] array, final char separator) { - if (array == null) { - return null; - } - if (array.length == 0) { - return ""; - } - final StringBuilder buf = new StringBuilder(); - for (int i = 0; i < array.length; i++) { - if (i > 0) { - buf.append(separator); - } - if (array[i] != null) { - buf.append(array[i]); - } - } - return buf.toString(); - } - - /** - * Wait until another thread signals the completion of a task. - * If an exception is passed, it will be propagated to the caller. - * - * @param future The communication channel. - * @param amount The amount of time to wait. If less than 0, wait indefinitely - * @param timeUnit The time unit. - * - * @return a boolean value indicating resource is ready or not. - */ - public static boolean waitUntilReady(Future future, long amount, TimeUnit timeUnit) { - try { - if (amount < 0) { - future.get(); - } else { - future.get(amount, timeUnit); - } - return true; - } catch (TimeoutException e) { - return false; - } catch (ExecutionException e) { - Throwable t = e; - if (e.getCause() != null) { - t = e.getCause(); - } - t.addSuppressed(new Throwable("waiting here")); - throw KubernetesClientException.launderThrowable(t); - } catch (Exception e) { - throw KubernetesClientException.launderThrowable(e); - } - } - - /** - * Similar to {@link #waitUntilReady(Future, long, TimeUnit)}, but will always throw an exception if not ready - */ - public static void waitUntilReadyOrFail(Future future, long amount, TimeUnit timeUnit) { - if (!waitUntilReady(future, amount, timeUnit)) { - throw KubernetesClientException.launderThrowable(new TimeoutException("not ready after " + amount + " " + timeUnit)); - } - } - - /** - * Closes and flushes the specified {@link Closeable} items. - * - * @param closeables An {@link Iterable} of {@link Closeable} items. - */ - public static void closeQuietly(Iterable closeables) { - for (Closeable c : closeables) { - try { - //Check if we also need to flush - if (c instanceof Flushable) { - ((Flushable) c).flush(); - } - - if (c != null) { - c.close(); - } - } catch (IOException e) { - LOGGER.debug("Error closing: {}", c); - } - } - } - - /** - * Closes and flushes the specified {@link Closeable} items. - * - * @param closeables An array of {@link Closeable} items. - */ - public static void closeQuietly(Closeable... closeables) { - closeQuietly(Arrays.asList(closeables)); - } - - public static String coalesce(String... items) { - for (String str : items) { - if (str != null) { - return str; - } - } - return null; - } - - /** - * Utility method to generate UUIDs. - * This is taken from Spring Framework's SimpleIdGenerator - * - * @return generated UUID - */ - public static UUID generateId() { - return new UUID(0, leastSigBits.incrementAndGet()); - } - - public static String randomString(int length) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - int index = random.nextInt(ALL_CHARS.length()); - sb.append(ALL_CHARS.charAt(index)); - } - return sb.toString(); - } - - public static String filePath(URL path) { - if (path == null) { - throw new KubernetesClientException("Path is required"); - } - try { - return Paths.get(path.toURI()).toString(); - } catch (URISyntaxException e) { - throw KubernetesClientException.launderThrowable(e); - } - } - - /** - * Replaces all occurrences of the from text with to text without any regular expressions - * - * @param text text string - * @param from from string - * @param to to string - * @return returns processed string - */ - public static String replaceAllWithoutRegex(String text, String from, String to) { - if (text == null) { - return null; - } - int idx = 0; - while (true) { - idx = text.indexOf(from, idx); - if (idx >= 0) { - text = text.substring(0, idx) + to + text.substring(idx + from.length()); - - // lets start searching after the end of the `to` to avoid possible infinite recursion - idx += to.length(); - } else { - break; - } - } - return text; - } - - public static boolean isNullOrEmpty(String str) { - return str == null || str.isEmpty(); - } - - public static boolean isNotNullOrEmpty(Map map) { - return !isNullOrEmpty(map); - } - - public static boolean isNullOrEmpty(Map map) { - return map == null || map.isEmpty(); - } - - public static boolean isNotNullOrEmpty(Collection collection) { - return !isNullOrEmpty(collection); - } - - public static boolean isNullOrEmpty(Collection collection) { - return collection == null || collection.isEmpty(); - } - - public static boolean isNotNullOrEmpty(String str) { - return !isNullOrEmpty(str); - } - - public static boolean isNotNullOrEmpty(String[] array) { - return !isNullOrEmpty(array); - } - - public static boolean isNullOrEmpty(String[] array) { - return array == null || array.length == 0; - } - - public static boolean isNotNull(T... refList) { - return Optional.ofNullable(refList) - .map(refs -> Stream.of(refs).allMatch(Objects::nonNull)) - .orElse(false); - } - - public static T getNonNullOrElse(T obj, T defaultObj) { - return obj != null ? obj : defaultObj; - } - - public static String getProperty(Map properties, String propertyName, String defaultValue) { - String answer = (String) properties.get(propertyName); - if (!isNullOrEmpty(answer)) { - return answer; - } - - return getSystemPropertyOrEnvVar(propertyName, defaultValue); - } - - public static String getProperty(Map properties, String propertyName) { - return getProperty(properties, propertyName, null); - } - - /** - * Converts string to URL encoded string. - * - * @param str Url as string - * @return returns encoded string - */ - public static String toUrlEncoded(String str) { - try { - return URLEncoder.encode(str, StandardCharsets.UTF_8.displayName()); - } catch (UnsupportedEncodingException exception) { - // Ignore - } - return null; - } - - /** - * - * @param kind - * @return - * @deprecated use {@link io.fabric8.kubernetes.api.model.HasMetadata#getPlural(Class)} - */ - @Deprecated - public static String getPluralFromKind(String kind) { - return Pluralize.toPlural(kind.toLowerCase(Locale.ROOT)); - } - - /** - * Reads @Namespaced annotation in resource class to check whether - * resource is namespaced or not - * - * @param kubernetesResourceType class for resource - * @return boolean value indicating it's namespaced or not - */ - public static boolean isResourceNamespaced(Class kubernetesResourceType) { - return Namespaced.class.isAssignableFrom(kubernetesResourceType); - } - - public static String getAnnotationValue(Class kubernetesResourceType, Class annotationClass) { - Annotation annotation = kubernetesResourceType.getAnnotation(annotationClass); - if (annotation instanceof Group) { - return ((Group) annotation).value(); - } else if (annotation instanceof Version) { - return ((Version) annotation).value(); - } - return null; - } - - /** - * Interpolates a String containing variable placeholders with the values provided in the valuesMap. - * - *

- * This method is intended to interpolate templates loaded from YAML and JSON files. - * - *

- * Placeholders are indicated by the dollar sign and curly braces ({@code ${VARIABLE_KEY}}). - * - *

- * Placeholders can also be indicated by the dollar sign and double curly braces ({@code ${{VARIABLE_KEY}}}), - * when this notation is used, the resulting value will be unquoted (if applicable), expected values should be JSON - * compatible. - * - * @see OpenShift - * Templates - * @param valuesMap to interpolate in the String - * @param templateInput raw input containing a String with placeholders ready to be interpolated - * @return the interpolated String - */ - public static String interpolateString(String templateInput, Map valuesMap) { - return Optional.ofNullable(valuesMap).orElse(Collections.emptyMap()).entrySet().stream() - .filter(entry -> entry.getKey() != null) - .filter(entry -> entry.getValue() != null) - .flatMap(entry -> { - final String key = entry.getKey(); - final String value = entry.getValue(); - return Stream.of( - new AbstractMap.SimpleEntry<>("${" + key + "}", value), - new AbstractMap.SimpleEntry<>("\"${{" + key + "}}\"", value), - new AbstractMap.SimpleEntry<>("${{" + key + "}}", value)); - }) - .map(explodedParam -> (Function) s -> s.replace(explodedParam.getKey(), explodedParam.getValue())) - .reduce(Function.identity(), Function::andThen) - .apply(Objects.requireNonNull(templateInput, "templateInput is required")); - } - - /** - * Check whether platform is windows or not - * - * @return boolean value indicating whether OS is Windows or not. - */ - public static boolean isWindowsOperatingSystem() { - return getOperatingSystemFromSystemProperty().toLowerCase().contains(WINDOWS); - } - - /** - * Get system PATH variable - * - * @return a string containing value of PATH - */ - public static String getSystemPathVariable() { - return System.getenv(isWindowsOperatingSystem() ? PATH_WINDOWS : PATH_UNIX); - } - - /** - * Returns prefixes needed to invoke specified command - * in a subprocess. - * - * @return a list of strings containing prefixes - */ - public static List getCommandPlatformPrefix() { - List platformPrefixParts = new ArrayList<>(); - if (Utils.isWindowsOperatingSystem()) { - platformPrefixParts.add("cmd.exe"); - platformPrefixParts.add("/c"); - } else { - platformPrefixParts.add("sh"); - platformPrefixParts.add("-c"); - } - return platformPrefixParts; - } - - private static String getOperatingSystemFromSystemProperty() { - return System.getProperty(OS_NAME); - } - - /** - * Create a {@link ThreadFactory} with daemon threads and a thread - * name based upon the object passed in. - */ - public static ThreadFactory daemonThreadFactory(Object forObject) { - String name = forObject.getClass().getSimpleName() + "-" + System.identityHashCode(forObject); - return daemonThreadFactory(name); - } - - static ThreadFactory daemonThreadFactory(String name) { - return new ThreadFactory() { - ThreadFactory threadFactory = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(Runnable r) { - Thread ret = threadFactory.newThread(r); - ret.setName(name + "-" + ret.getName()); - ret.setDaemon(true); - return ret; - } - }; - } - - /** - * Schedule a task to run in the given {@link Executor} - which should run the task in a different thread as to not - * hold the scheduling thread - */ - public static CompletableFuture schedule(Executor executor, Runnable command, long delay, TimeUnit unit) { - // to be replaced in java 9+ with CompletableFuture.runAsync(command, CompletableFuture.delayedExecutor(delay, unit, executor)); - CompletableFuture result = new CompletableFuture<>(); - ScheduledFuture scheduledFuture = SHARED_SCHEDULER.schedule(() -> { - try { - executor.execute(command); - result.complete(null); - } catch (Throwable t) { - result.completeExceptionally(t); - } - }, delay, unit); - result.whenComplete((v, t) -> { - scheduledFuture.cancel(true); - }); - return result; - } - - /** - * Schedule a repeated task to run in the given {@link Executor} - which should run the task in a different thread as to not - * hold the scheduling thread. - *

- * Has the same general contract as {@link ScheduledThreadPoolExecutor#scheduleAtFixedRate(Runnable, long, long, TimeUnit)} - */ - public static CompletableFuture scheduleAtFixedRate(Executor executor, Runnable command, long initialDelay, long delay, - TimeUnit unit) { - CompletableFuture completion = new CompletableFuture<>(); - scheduleWithVariableRate(completion, executor, command, initialDelay, () -> delay, unit); - return completion; - } - - /** - * Schedule a repeated task to run in the given {@link Executor} - which should run the task in a different thread as to not - * hold the scheduling thread. - *

- * - * @param nextDelay provides the relative next delay - that is the values are applied cumulatively to the initial start - * time. Supplying a fixed value produces a fixed rate. - */ - public static void scheduleWithVariableRate(CompletableFuture completion, Executor executor, Runnable command, - long initialDelay, - LongSupplier nextDelay, TimeUnit unit) { - AtomicReference> currentScheduledFuture = new AtomicReference<>(); - AtomicLong next = new AtomicLong(unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS) + Math.max(0, initialDelay)); - schedule(() -> CompletableFuture.runAsync(command, executor), initialDelay, unit, completion, nextDelay, next, - currentScheduledFuture); - // remove on cancel is true, so this may proactively clean up - completion.whenComplete((v, t) -> Optional.ofNullable(currentScheduledFuture.get()).ifPresent(s -> s.cancel(true))); - } - - private static void schedule(Supplier> runner, long delay, TimeUnit unit, - CompletableFuture completion, LongSupplier nextDelay, AtomicLong next, - AtomicReference> currentScheduledFuture) { - currentScheduledFuture.set(SHARED_SCHEDULER.schedule(() -> { - if (completion.isDone()) { - return; - } - CompletableFuture runAsync = runner.get(); - runAsync.whenComplete((v, t) -> { - if (t != null) { - completion.completeExceptionally(t); - } else if (!completion.isDone()) { - schedule(runner, next.addAndGet(nextDelay.getAsLong()) - unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS), - unit, completion, nextDelay, next, currentScheduledFuture); - } - }); - }, delay, unit)); - } -} diff --git a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContexts.kt b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContexts.kt index 15e8bbda6..d9add854c 100644 --- a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContexts.kt +++ b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContexts.kt @@ -12,6 +12,7 @@ package com.redhat.devtools.intellij.kubernetes.model import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.logger +import com.redhat.devtools.intellij.common.utils.ConfigHelper import com.redhat.devtools.intellij.common.utils.ConfigWatcher import com.redhat.devtools.intellij.common.utils.ExecHelper import com.redhat.devtools.intellij.kubernetes.model.client.ClientAdapter @@ -26,9 +27,10 @@ import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.NAME_P import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.PROP_IS_OPENSHIFT import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.PROP_KUBERNETES_VERSION import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.PROP_OPENSHIFT_VERSION -import io.fabric8.kubernetes.api.model.Config import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.client.Config import io.fabric8.kubernetes.client.KubernetesClient +import java.nio.file.Paths import java.util.concurrent.CompletionException import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read @@ -84,14 +86,16 @@ open class AllContexts( = { namespace, context -> ClientAdapter.Factory.create(namespace, context) } ) : IAllContexts { + init { + watchKubeConfig() + } + private val lock = ReentrantReadWriteLock() - protected open val client = ResettableLazyProperty { - val client = lock.write { + private val client = ResettableLazyProperty { + lock.write { clientFactory.invoke(null, null) } - watchKubeConfig(client) - client } override val current: IActiveContext? @@ -106,11 +110,10 @@ open class AllContexts( lock.write { if (_all.isEmpty()) { try { - val client = client.get() - val all = createContexts(client, client?.config) + val all = createContexts(client.get(), client.get()?.config) _all.addAll(all) } catch (e: Exception) { - logger().warn("Could not load all contexts.", e) + // } } return _all @@ -193,10 +196,8 @@ open class AllContexts( return config.allContexts .map { if (config.isCurrent(it)) { - logger().debug("Adding active context ${it.name}") createActiveContext(client) ?: Context(it) } else { - logger().debug("Adding inactive context ${it.name}") Context(it) } } @@ -240,28 +241,26 @@ open class AllContexts( } } - protected open fun watchKubeConfig(client: ClientAdapter) { - val files = client.config.files - val paths = files.map { file -> file.toPath() } + protected open fun watchKubeConfig() { + val path = Paths.get(Config.getKubeconfigFilename()) /** * [ConfigWatcher] cannot add/remove listeners nor can it get closed (and stop the [java.nio.file.WatchService]). * We therefore have to create a single instance in here rather than using it in a shielded/private way within * [com.redhat.devtools.intellij.kubernetes.model.client.ClientConfig]. * Closing/Recreating [ConfigWatcher] is needed when used within [com.redhat.devtools.intellij.kubernetes.model.client.ClientConfig]. * The latter gets closed/recreated whenever the context changes in - * [com.redhat.devtools.intellij.kubernetes.model.client.KubeConfigPersistence]. + * [com.redhat.devtools.intellij.kubernetes.model.client.KubeConfigAdapter]. */ - val watcher = ConfigWatcher(paths) { _, config: Config? -> - onKubeConfigChanged(config) - } + val watcher = ConfigWatcher(path) { _, config: io.fabric8.kubernetes.api.model.Config? -> onKubeConfigChanged(config) } runAsync(watcher::run) } - protected open fun onKubeConfigChanged(fileConfig: Config?) { + protected open fun onKubeConfigChanged(fileConfig: io.fabric8.kubernetes.api.model.Config?) { lock.read { fileConfig ?: return val client = client.get() ?: return - if (client.config.isEqual(fileConfig)) { + val clientConfig = client.config.configuration + if (ConfigHelper.areEqual(fileConfig, clientConfig)) { return } this.client.reset() // create new client when accessed diff --git a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfig.kt b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfig.kt index 4f753f5c2..76f871534 100644 --- a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfig.kt +++ b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfig.kt @@ -10,14 +10,13 @@ ******************************************************************************/ package com.redhat.devtools.intellij.kubernetes.model.client -import com.intellij.openapi.diagnostic.logger import com.redhat.devtools.intellij.common.utils.ConfigHelper import com.redhat.devtools.intellij.kubernetes.CompletableFutureUtils.PLATFORM_EXECUTOR +import io.fabric8.kubernetes.api.model.Context import io.fabric8.kubernetes.api.model.NamedContext import io.fabric8.kubernetes.client.Client import io.fabric8.kubernetes.client.Config import io.fabric8.kubernetes.client.internal.KubeConfigUtils -import java.io.File import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor @@ -25,16 +24,15 @@ import java.util.concurrent.Executor * An adapter to access [io.fabric8.kubernetes.client.Config]. * It also saves the kube config [KubeConfigUtils] when it changes the client config. */ -open class ClientConfig( - private val client: Client, - private val executor: Executor = PLATFORM_EXECUTOR, - private val persistence: (io.fabric8.kubernetes.api.model.Config?, String?) -> Unit = KubeConfigUtils::persistKubeConfigIntoFile -) { +open class ClientConfig(private val client: Client, private val executor: Executor = PLATFORM_EXECUTOR) { - open val currentContext: NamedContext? + open var currentContext: NamedContext? get() { return configuration.currentContext } + set(context) { + configuration.currentContext = context + } open val allContexts: List get() { @@ -45,62 +43,73 @@ open class ClientConfig( client.configuration } - val files: List - get() { - return configuration.files - } + protected open val kubeConfig: KubeConfigAdapter by lazy { + KubeConfigAdapter() + } fun save(): CompletableFuture { return CompletableFuture.supplyAsync( { - val toSave = mutableMapOf() - val withCurrentContext = configuration.fileWithCurrentContext - if (withCurrentContext != null - && setCurrentContext(withCurrentContext.config) - ) { - toSave[withCurrentContext.file] = withCurrentContext.config + if (!kubeConfig.exists()) { + return@supplyAsync false } - val withCurrentNamespace = configuration.getFileWithContext(currentContext?.name) - if (withCurrentNamespace != null - && setCurrentNamespace(withCurrentNamespace.config) + val fromFile = kubeConfig.load() ?: return@supplyAsync false + if (setCurrentContext( + currentContext, + KubeConfigUtils.getCurrentContext(fromFile), + fromFile + ).or( // no short-circuit + setCurrentNamespace( + currentContext?.context, + KubeConfigUtils.getCurrentContext(fromFile)?.context + ) + ) ) { - toSave[withCurrentNamespace.file] = withCurrentNamespace.config - } - toSave.forEach { - save(it.value, it.key) + kubeConfig.save(fromFile) + return@supplyAsync true + } else { + return@supplyAsync false } - toSave.isNotEmpty() }, executor ) } - private fun save(kubeConfig: io.fabric8.kubernetes.api.model.Config?, file: File?) { - if (kubeConfig != null - && file?.absolutePath != null) { - logger().debug("Saving ${file.absolutePath}.") - persistence.invoke(kubeConfig, file.absolutePath) - } - } - - private fun setCurrentNamespace(kubeConfig: io.fabric8.kubernetes.api.model.Config?): Boolean { - val currentNamespace = currentContext?.context?.namespace ?: return false - val context = KubeConfigUtils.getContext(kubeConfig, currentContext?.name) - return if (context?.context != null - && context.context.namespace != currentNamespace) { - context.context.namespace = currentNamespace + private fun setCurrentContext( + currentContext: NamedContext?, + kubeConfigCurrentContext: NamedContext?, + kubeConfig: io.fabric8.kubernetes.api.model.Config + ): Boolean { + return if (currentContext != null + && !ConfigHelper.areEqual(currentContext, kubeConfigCurrentContext) + ) { + kubeConfig.currentContext = currentContext.name true } else { false } } - private fun setCurrentContext(kubeConfig: io.fabric8.kubernetes.api.model.Config?): Boolean { - val currentContext = currentContext?.name ?: return false - return if ( - kubeConfig != null - && currentContext != kubeConfig.currentContext) { - kubeConfig.currentContext = currentContext + /** + * Sets the namespace in the given source [Context] to the given target [Context]. + * Does nothing if the target config has no current context + * or if the source config has no current context + * or if setting it would not change it. + * + * @param source Context whose namespace should be copied + * @param target Context whose namespace should be overriden + * @return + */ + private fun setCurrentNamespace( + source: Context?, + target: Context? + ): Boolean { + val sourceNamespace = source?.namespace ?: return false + val targetNamespace = target?.namespace + return if (target != null + && sourceNamespace != targetNamespace + ) { + target.namespace = source.namespace true } else { false @@ -110,8 +119,4 @@ open class ClientConfig( fun isCurrent(context: NamedContext): Boolean { return context == currentContext } - - fun isEqual(config: io.fabric8.kubernetes.api.model.Config): Boolean { - return ConfigHelper.areEqual(config, configuration) - } } \ No newline at end of file diff --git a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/KubeConfigAdapter.kt b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/KubeConfigAdapter.kt new file mode 100644 index 000000000..27bdfdeca --- /dev/null +++ b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/KubeConfigAdapter.kt @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.kubernetes.model.client + +import io.fabric8.kubernetes.api.model.Config +import io.fabric8.kubernetes.client.internal.KubeConfigUtils +import java.io.File + +/** + * A class that allows to access the kube config file that's by default at ~/.kube/config + * (but may be configured to be at a different location). This class respects this by relying on the + * {@link io.fabric8.kubernetes.client.Config} for the location instead of using a hard coded path. + */ +class KubeConfigAdapter { + + private val file: File by lazy { + File(io.fabric8.kubernetes.client.Config.getKubeconfigFilename()) + } + + fun exists(): Boolean { + return file.exists() + } + + fun load(): Config? { + if (!exists()) { + return null + } + return KubeConfigUtils.parseConfig(file) + } + + fun save(config: Config) { + KubeConfigUtils.persistKubeConfigIntoFile(config, file.absolutePath) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/IActiveContext.kt b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/IActiveContext.kt index e362073bf..37abc2621 100644 --- a/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/IActiveContext.kt +++ b/src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/IActiveContext.kt @@ -10,7 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.intellij.kubernetes.model.context -import com.intellij.openapi.diagnostic.logger import com.redhat.devtools.intellij.common.kubernetes.ClusterInfo import com.redhat.devtools.intellij.kubernetes.model.IResourceModelObservable import com.redhat.devtools.intellij.kubernetes.model.client.ClientAdapter @@ -35,14 +34,12 @@ interface IActiveContext: IContext { ): IActiveContext? { val currentContext = client.config.currentContext ?: return null return if (client.isOpenShift()) { - logger().warn("Current context ${currentContext.name} is OpenShift") OpenShiftContext( currentContext, observable, client as OSClientAdapter ) } else { - logger().warn("Current context ${currentContext.name} is Kubernetes") KubernetesContext( currentContext, observable, diff --git a/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContextsTest.kt b/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContextsTest.kt index 388f4919e..df5cbc036 100644 --- a/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContextsTest.kt +++ b/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContextsTest.kt @@ -10,7 +10,17 @@ ******************************************************************************/ package com.redhat.devtools.intellij.kubernetes.model -import com.nhaarman.mockitokotlin2.* +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.anyOrNull +import com.nhaarman.mockitokotlin2.argThat +import com.nhaarman.mockitokotlin2.clearInvocations +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.doThrow +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.never +import com.nhaarman.mockitokotlin2.times +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever import com.redhat.devtools.intellij.kubernetes.model.client.ClientAdapter import com.redhat.devtools.intellij.kubernetes.model.context.IActiveContext import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.NAMESPACE1 @@ -25,9 +35,17 @@ import com.redhat.devtools.intellij.kubernetes.model.mocks.Mocks.clientConfig import com.redhat.devtools.intellij.kubernetes.model.mocks.Mocks.clientFactory import com.redhat.devtools.intellij.kubernetes.model.mocks.Mocks.context import com.redhat.devtools.intellij.kubernetes.model.resource.ResourceKind -import io.fabric8.kubernetes.api.model.* +import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException +import io.fabric8.kubernetes.api.model.Config +import io.fabric8.kubernetes.api.model.ConfigBuilder +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.NamedAuthInfoBuilder +import io.fabric8.kubernetes.api.model.Namespace +import io.fabric8.kubernetes.api.model.NamespaceList +import io.fabric8.kubernetes.api.model.Pod import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.KubernetesClientException import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation import io.fabric8.kubernetes.client.dsl.Resource import org.assertj.core.api.Assertions.assertThat @@ -64,6 +82,14 @@ class AllContextsTest { private val allContexts = TestableAllContexts(modelChange, contextFactory, clientFactory) + @Test + fun `when instantiated, it should watch kube config`() { + // given + // when + // then + assertThat(allContexts.watchStarted).isTrue + } + @Test fun `#refresh() should close existing context`() { // given @@ -359,42 +385,67 @@ class AllContextsTest { } @Test - fun `#client#get should start watching kubeconfigs`() { + fun `#onKubeConfigChanged() should NOT fire if new config is null`() { // given - assertThat(allContexts.watchStarted).isFalse // when - allContexts.client.get() + allContexts.onKubeConfigChanged(null) // then - assertThat(allContexts.watchStarted).isTrue + verify(modelChange, never()).fireAllContextsChanged() } @Test - fun `#onKubeConfigChanged() should NOT fire if new config is null`() { + fun `#onKubeConfigChanged() should NOT fire if existing config and given config are equal`() { // given + val kubeConfig = ConfigBuilder() + .withCurrentContext(clientConfig.currentContext?.name) + .withContexts(clientConfig.allContexts) + .withUsers(NamedAuthInfoBuilder() + .withName(currentContext.context.user) + .withNewUser() + .withToken(clientConfig.configuration.oauthToken) + .endUser() + .build()) + .build() // when - allContexts.onKubeConfigChanged(null) + allContexts.onKubeConfigChanged(kubeConfig) // then verify(modelChange, never()).fireAllContextsChanged() } @Test - fun `#onKubeConfigChanged() should NOT fire if given kubeConfig is equal to current client config`() { + fun `#onKubeConfigChanged() should fire if given config has different current context`() { // given - val kubeConfig = ConfigBuilder().build() - whenever(clientConfig.isEqual(kubeConfig)) - .thenReturn(true) + assertThat(namedContext1).isNotEqualTo(currentContext) + val kubeConfig = ConfigBuilder() + .withCurrentContext(namedContext1.name) + .withContexts(clientConfig.allContexts) + .withUsers(NamedAuthInfoBuilder() + .withName(currentContext.context.user) + .withNewUser() + .withToken(clientConfig.configuration.oauthToken) + .endUser() + .build()) + .build() // when allContexts.onKubeConfigChanged(kubeConfig) // then - verify(modelChange, never()).fireAllContextsChanged() + verify(modelChange).fireAllContextsChanged() } @Test - fun `#onKubeConfigChanged() should fire if given kubeConfig is NOT equal to current client config`() { + fun `#onKubeConfigChanged() should fire if given config has different contexts`() { // given - val kubeConfig = ConfigBuilder().build() - whenever(clientConfig.isEqual(kubeConfig)) - .thenReturn(false) + val contexts = listOf(mock(), *clientConfig.allContexts.toTypedArray()) + val kubeConfig = ConfigBuilder() + .withCurrentContext(clientConfig.currentContext?.name) + .withContexts(contexts) + .withUsers(NamedAuthInfoBuilder() + .withName(currentContext.context.user) + .withNewUser() + .withToken(clientConfig.configuration.oauthToken) + .endUser() + .build()) + .build() // when allContexts.onKubeConfigChanged(kubeConfig) // then @@ -402,11 +453,19 @@ class AllContextsTest { } @Test - fun `#onKubeConfigChanged() should close client if given config is NOT equal to current context`() { + fun `#onKubeConfigChanged() should close client if given config has different current context`() { // given - val kubeConfig = ConfigBuilder().build() - whenever(clientConfig.isEqual(kubeConfig)) - .thenReturn(false) + assertThat(namedContext1).isNotEqualTo(currentContext) + val kubeConfig = ConfigBuilder() + .withCurrentContext(namedContext1.name) + .withContexts(clientConfig.allContexts) + .withUsers(NamedAuthInfoBuilder() + .withName(currentContext.context.user) + .withNewUser() + .withToken(clientConfig.configuration.oauthToken) + .endUser() + .build()) + .build() allContexts.current // when allContexts.onKubeConfigChanged(kubeConfig) @@ -415,11 +474,19 @@ class AllContextsTest { } @Test - fun `#onKubeConfigChanged() should close current context if given config is NOT equal to current context`() { + fun `#onKubeConfigChanged() should close current context if given config has different current context`() { // given - val kubeConfig = ConfigBuilder().build() - whenever(clientConfig.isEqual(kubeConfig)) - .thenReturn(false) + assertThat(namedContext1).isNotEqualTo(currentContext) + val kubeConfig = ConfigBuilder() + .withCurrentContext(namedContext1.name) + .withContexts(clientConfig.allContexts) + .withUsers(NamedAuthInfoBuilder() + .withName(currentContext.context.user) + .withNewUser() + .withToken(clientConfig.configuration.oauthToken) + .endUser() + .build()) + .build() allContexts.current // when allContexts.onKubeConfigChanged(kubeConfig) @@ -450,6 +517,12 @@ class AllContextsTest { } } + private fun client(e: KubernetesClientException): KubernetesClient { + return mock { + on { namespaces() } doThrow e + } + } + private class TestableAllContexts( modelChange: IResourceModelObservable, contextFactory: (ClientAdapter, IResourceModelObservable) -> IActiveContext, @@ -458,8 +531,6 @@ class AllContextsTest { var watchStarted = false - public override val client = super.client - override fun reportTelemetry(context: IActiveContext) { // prevent telemetry reporting } @@ -468,7 +539,7 @@ class AllContextsTest { runnable.invoke() // run directly, not in IDEA pooled threads } - override fun watchKubeConfig(client: ClientAdapter) { + override fun watchKubeConfig() { // don't watch filesystem (override super method) watchStarted = true } diff --git a/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfigTest.kt b/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfigTest.kt index 609b5d499..898c989b5 100644 --- a/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfigTest.kt +++ b/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientConfigTest.kt @@ -10,383 +10,228 @@ ******************************************************************************/ package com.redhat.devtools.intellij.kubernetes.model.client -import com.nhaarman.mockitokotlin2.* +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.argThat +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.never +import com.nhaarman.mockitokotlin2.spy +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks -import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.kubeConfigFile -import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.namedContext -import io.fabric8.kubernetes.api.model.AuthInfo -import io.fabric8.kubernetes.api.model.NamedAuthInfo -import io.fabric8.kubernetes.api.model.NamedAuthInfoBuilder +import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.doReturnCurrentContextAndAllContexts +import io.fabric8.kubernetes.api.model.ConfigBuilder +import io.fabric8.kubernetes.api.model.ContextBuilder import io.fabric8.kubernetes.api.model.NamedContext import io.fabric8.kubernetes.api.model.NamedContextBuilder import io.fabric8.kubernetes.client.Client import io.fabric8.kubernetes.client.Config +import io.fabric8.kubernetes.client.internal.KubeConfigUtils import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import java.io.File class ClientConfigTest { - private val ctx1 = - namedContext("ctx1", "namespace1", "cluster1", "user1") - private val ctx2 = - namedContext("ctx2", "namespace2", "cluster2", "user2") - private val ctx3 = - namedContext("ctx3", "namespace3", "cluster3", "user3") - private val ctx4 = - namedContext("ctx4", "namespace4", "cluster4", "user4") - private val ctx5 = - namedContext("ctx5", "namespace5", "cluster5", "user5") - private val ctx6 = - namedContext("ctx6", "namespace6", "cluster6", "user6") - private val currentContext = ctx2 - private val allContexts = listOf(ctx1, ctx2, ctx3) - - private val user5 = namedAuthInfo("user5", "user5", "token5") - - // kubeConfigFile current context ctx5 - private val ctx5FileWithCurrentContext = mockFile("ctx5FileWithCurrent") - private val ctx5ConfigWithCurrentContext = kubeConfig( - ctx5.name, - null - ) - private val ctx5KubeConfigFileWithCurrentContext = kubeConfigFile( - ctx5FileWithCurrentContext, - ctx5ConfigWithCurrentContext - ) - - // kubeConfigFile context ctx5 - private val ctx5FileWithCurrentNamespace = mockFile("ctx5FileWithContext") - private val ctx5ConfigWithCurrentNamespace = kubeConfig( - null, - listOf(ctx4, ctx5, ctx6), - listOf(user5) - ) - private val ctx5KubeConfigFileWithCurrentNamespace = kubeConfigFile( - ctx5FileWithCurrentNamespace, - ctx5ConfigWithCurrentNamespace - ) - private val config: Config = ClientMocks.config( - currentContext, - allContexts - ) - private val client: Client = createClient(config) - private val persistence: (io.fabric8.kubernetes.api.model.Config?, String?) -> Unit = mock() - private val clientConfig = spy(TestableClientConfig(client, persistence)) - - @Test - fun `#currentContext should return config#currentContext`() { - // given - // when - clientConfig.currentContext - // then - verify(config).currentContext - } - - @Test - fun `#allContexts should return config#allContexts`() { - // given - // when - clientConfig.allContexts - // then - verify(config).contexts - } - - @Test - fun `#isCurrent should return true if context is equal`() { - // given - // when - val isCurrent = clientConfig.isCurrent(currentContext) - // then - assertThat(isCurrent).isTrue() - } - - @Test - fun `#isCurrent should return false if context isn't equal`() { - // given - // when - val isCurrent = clientConfig.isCurrent(ctx3) - // then - assertThat(isCurrent).isFalse() - } - - @Test - fun `#save should NOT save if no file with current context nor with current namespace exists`() { - // given - val config = client.configuration - doReturn(null) - .whenever(config).getFileWithCurrentContext() - doReturn(null) - .whenever(config).getFileWithContext(any()) - // when - clientConfig.save().join() - // then - verify(persistence, never()).invoke(any(), any()) - - } - - @Test - fun `#save should NOT save if files are same in current namespace and current context than client config`() { - // given - val config = client.configuration - // same current context - whenever(config.currentContext) - .thenReturn(ctx5) - whenever(config.getFileWithCurrentContext()) - .thenReturn(this.ctx5KubeConfigFileWithCurrentContext) - // same current namespace - whenever(config.getFileWithContext(ctx5.name)) - .thenReturn(ctx5KubeConfigFileWithCurrentNamespace) - // when - clientConfig.save().join() - // then - verify(persistence, never()).invoke(any(), any()) - } - - @Test - fun `#save should save file with current context if it has different current context than client config`() { - // given - val config = client.configuration - // different current context - whenever(config.currentContext) - .thenReturn(ctx6) - whenever(config.getFileWithCurrentContext()) - .thenReturn(ctx5KubeConfigFileWithCurrentContext) - // same current namespace - whenever(config.getFileWithContext(ctx5.name)) - .thenReturn(ctx5KubeConfigFileWithCurrentNamespace) - // when - clientConfig.save().join() - // then - verify(persistence).invoke(ctx5ConfigWithCurrentContext, ctx5FileWithCurrentContext.absolutePath) - } - - @Test - fun `#save should set current context in KubeConfigFile if it has different current context than client config`() { - // given - val config = client.configuration - // different current context - whenever(config.currentContext) - .thenReturn(ctx6) - whenever(config.getFileWithCurrentContext()) - .thenReturn(ctx5KubeConfigFileWithCurrentContext) - // same current namespace - whenever(config.getFileWithContext(ctx5.name)) - .thenReturn(ctx5KubeConfigFileWithCurrentNamespace) - // when - clientConfig.save().join() - // then - verify(ctx5ConfigWithCurrentContext).currentContext = ctx6.name - } - - @Test - fun `#save should save file with current namespace if it has different current namespace than client config`() { - // given - val config = client.configuration - // same current context - whenever(config.currentContext) - .thenReturn(ctx5) - whenever(config.getFileWithCurrentContext()) - .thenReturn(ctx5KubeConfigFileWithCurrentContext) - // different current namespace - val ctx5WithDifferentNamespace = kubeConfig( - null, - listOf( - namedContext(ctx5.name,"R2-D2") - ) - ) - val kubeConfigFile = kubeConfigFile(ctx5FileWithCurrentNamespace, ctx5WithDifferentNamespace) - whenever(config.getFileWithContext(ctx5.name)) - .thenReturn(kubeConfigFile) - // when - clientConfig.save().join() - // then - verify(persistence).invoke(ctx5WithDifferentNamespace, ctx5FileWithCurrentNamespace.absolutePath) - } - - @Test - fun `#save should set current namespace if kubeConfigFile has different current namespace than client config`() { - // given - val config = client.configuration - // same current context - whenever(config.currentContext) - .thenReturn(ctx5) - whenever(config.getFileWithCurrentContext()) - .thenReturn(ctx5KubeConfigFileWithCurrentContext) - // different current namespace - val ctx5ContextWithDifferentNamespace = namedContext(ctx5.name,"R2-D2") - val ctx5ConfigWithDifferentNamespace = kubeConfig( - null, - listOf( - ctx5ContextWithDifferentNamespace - ) - ) - val kubeConfigFile = kubeConfigFile(ctx5FileWithCurrentNamespace, ctx5ConfigWithDifferentNamespace) - whenever(config.getFileWithContext(ctx5.name)) - .thenReturn(kubeConfigFile) - // when - clientConfig.save().join() - // then - verify(ctx5ContextWithDifferentNamespace.context).namespace = ctx5.context.namespace - } - - @Test - fun `#save should save file with current context and file with current namespace if both differ from client config`() { - // given - val config = client.configuration - // different current context - whenever(config.currentContext) - .thenReturn(ctx2) - whenever(config.getFileWithCurrentContext()) - .thenReturn(ctx5KubeConfigFileWithCurrentContext) - // different current namespace - val ctx2KubeConfigFileWithCurrentNamespaceClone = kubeConfig( - null, - listOf( - namedContext(ctx2.name,"R2-D2") - ) - ) - val ctx2ConfigWithCurrentNamespaceClone = kubeConfigFile(ctx5FileWithCurrentNamespace, ctx2KubeConfigFileWithCurrentNamespaceClone) - whenever(config.getFileWithContext(ctx2.name)) - .thenReturn(ctx2ConfigWithCurrentNamespaceClone) - // when - clientConfig.save().join() - // then - verify(persistence).invoke(ctx5ConfigWithCurrentContext, ctx5FileWithCurrentContext.absolutePath) - verify(persistence).invoke(ctx2KubeConfigFileWithCurrentNamespaceClone, ctx5FileWithCurrentNamespace.absolutePath) - } - - @Test - fun `#isEqual should return true if given config is equal client config`() { - // given - val kubeConfig = kubeConfig( - config.currentContext.name, - config.contexts, - listOf(namedAuthInfo(config.currentContext.context.user)) - ) - // when - val isEqual = clientConfig.isEqual(kubeConfig) - // then - assertThat(isEqual).isTrue() - } - - @Test - fun `#isEqual should return false if given config differs from client config in current context`() { - // given - val kubeConfig = kubeConfig( - "skywalker", - config.contexts, - listOf(namedAuthInfo(config.currentContext.context.user)) - ) - // when - val isEqual = clientConfig.isEqual(kubeConfig) - // then - assertThat(isEqual).isFalse() - } - - @Test - fun `#isEqual should return false if given config differs from client config in current namespace`() { - // given - val differentNamespace = NamedContextBuilder(config.currentContext) - .editContext() - .withNamespace("skywalker") - .endContext() - .build() - val allContexts = replaceByName(differentNamespace, config.contexts) - val kubeConfig = kubeConfig( - config.currentContext.name, - allContexts // contains current context clone with different namespace - ) - // when - val isEqual = clientConfig.isEqual(kubeConfig) - // then - assertThat(isEqual).isFalse() - } - - private fun replaceByName(context: NamedContext, allContexts: List): List { - val newList = allContexts.toMutableList() - newList.replaceAll { - if (it.name == context.name) { - context - } else { - it - } - } - return newList - } - - @Test - fun `#isEqual should return false if given config differs from client config in token`() { - // given - whenever(config.autoOAuthToken) - .doReturn("skywalker") - val equalKubeConfig = kubeConfig( - config.currentContext.name, - allContexts, - listOf( - NamedAuthInfoBuilder() - .withName(config.currentContext.context.user) - .build() - ) - ) - val differentToken = NamedAuthInfoBuilder() - .withName(config.currentContext.context.user) - .withNewUser() - .withToken("iceplanet") - .endUser() - .build() - val unequalKubeConfig = kubeConfig( - config.currentContext.name, - allContexts, - listOf(differentToken) - ) - // when - val notEqual = clientConfig.isEqual(unequalKubeConfig) - val equal = clientConfig.isEqual(equalKubeConfig) - // then - assertThat(equal).isTrue() - assertThat(notEqual).isFalse() - } - - private fun createClient(config: Config): Client { - return mock { - on { configuration } doReturn config - } - } - - private fun kubeConfig( - currentContext: String?, - allContexts: List?, - allUsers: List? = null - ): io.fabric8.kubernetes.api.model.Config { - - return mock { - on { this.currentContext } doReturn currentContext - on { this.contexts } doReturn allContexts - on { this.users } doReturn allUsers - } - } - - private fun namedAuthInfo(name: String, username: String? = null, token: String? = null): NamedAuthInfo { - val authInfo: AuthInfo = mock { - on { this.username } doReturn username - on { this.token } doReturn token - } - return mock { - on { this.name } doReturn name - on { this.user } doReturn authInfo - } - } - - private fun mockFile(absolutePath: String): File { - return mock { - on { this.absolutePath } doReturn absolutePath - } - } - - private class TestableClientConfig( - client: Client, - persistence: (io.fabric8.kubernetes.api.model.Config?, absolutePath: String?) -> Unit - ) : ClientConfig(client, { it.run() }, persistence) - + private val namedContext1 = + context("ctx1", "namespace1", "cluster1", "user1") + private val namedContext2 = + context("ctx2", "namespace2", "cluster2", "user2") + private val namedContext3 = + context("ctx3", "namespace3", "cluster3", "user3") + private val currentContext = namedContext2 + private val allContexts = listOf(namedContext1, namedContext2, namedContext3) + private val clientKubeConfig: Config = ClientMocks.config(currentContext, allContexts) + private val client: Client = createClient(clientKubeConfig) + private val fileKubeConfig: io.fabric8.kubernetes.api.model.Config = apiConfig(currentContext.name, allContexts) + private val kubeConfigAdapter: KubeConfigAdapter = kubeConfig(true, fileKubeConfig) + private val clientConfig = spy(TestableClientConfig(client, kubeConfigAdapter)) + + @Test + fun `#currentContext should return config#currentContext`() { + // given + // when + clientConfig.currentContext + // then + verify(clientKubeConfig).currentContext + } + + @Test + fun `#allContexts should return config#contexts`() { + // given + // when + clientConfig.allContexts + // then + verify(clientKubeConfig).contexts + } + + @Test + fun `#isCurrent should return true if context is equal`() { + // given + // when + val isCurrent = clientConfig.isCurrent(currentContext) + // then + assertThat(isCurrent).isTrue() + } + + @Test + fun `#isCurrent should return false if context isn't equal`() { + // given + // when + val isCurrent = clientConfig.isCurrent(namedContext3) + // then + assertThat(isCurrent).isFalse() + } + + @Test + fun `#save should NOT save if kubeConfig doesnt exist`() { + // given + doReturn(false) + .whenever(kubeConfigAdapter).exists() + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter, never()).save(any()) + } + + @Test + fun `#save should NOT save if kubeConfig has same current context same namespace and same current context as client config`() { + // given + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter, never()).save(any()) + } + + @Test + fun `#save should save if kubeConfig has different current context as client config`() { + // given + clientKubeConfig.currentContext.name = namedContext3.name + assertThat(fileKubeConfig.currentContext).isNotEqualTo(clientKubeConfig.currentContext.name) + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter).save(any()) + } + + @Test + fun `#save should save if kubeConfig has same current context but current namespace that differs from client config`() { + // given + val newCurrentContext = context( + currentContext.name, + "R2-D2", + currentContext.context.cluster, + currentContext.context.user) + val newAllContexts = mutableListOf(*allContexts.toTypedArray()) + newAllContexts.removeIf { it.name == currentContext.name } + newAllContexts.add(newCurrentContext) + fileKubeConfig.contexts = newAllContexts + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter).save(any()) + } + + @Test + fun `#save should update current context in kube config if differs from current context in client config`() { + // given + val newCurrentContext = namedContext3 + doReturn(newCurrentContext) + .whenever(clientKubeConfig).currentContext + assertThat(KubeConfigUtils.getCurrentContext(fileKubeConfig)) + .isNotEqualTo(clientKubeConfig.currentContext) + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter).save(argThat { + this.currentContext == newCurrentContext.name + }) + } + + @Test + fun `#save should leave current namespace in old context untouched when updating current context in kube config`() { + // given + val newCurrentContext = namedContext3 + doReturn(newCurrentContext) + .whenever(clientKubeConfig).currentContext + assertThat(KubeConfigUtils.getCurrentContext(fileKubeConfig)) + .isNotEqualTo(clientKubeConfig.currentContext) + val context = KubeConfigUtils.getCurrentContext(fileKubeConfig) + val currentBeforeSave = context.name + val namespaceBeforeSave = context.context.namespace + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter).save(argThat { + val afterSave = fileKubeConfig.contexts.find { + namedContext -> namedContext.name == currentBeforeSave } + afterSave!!.context.namespace == namespaceBeforeSave + }) + } + + @Test + fun `#save should update current namespace in kube config if only differs from current in client config but not in current context`() { + // given + val newCurrentContext = context(currentContext.name, + "RD-2D", + currentContext.context.cluster, + currentContext.context.user) + val newAllContexts = replaceCurrentContext(newCurrentContext, currentContext.name, allContexts) + doReturnCurrentContextAndAllContexts(newCurrentContext, newAllContexts, clientKubeConfig) + assertThat(KubeConfigUtils.getCurrentContext(fileKubeConfig).context.namespace) + .isNotEqualTo(clientKubeConfig.currentContext.context.namespace) + // when + clientConfig.save().join() + // then + verify(kubeConfigAdapter).save(argThat { + this.currentContext == this@ClientConfigTest.currentContext.name + && KubeConfigUtils.getCurrentContext(this).context.namespace == newCurrentContext.context.namespace + }) + } + + private fun replaceCurrentContext( + newContext: NamedContext, + currentContext: String, + allContexts: List + ): List { + val newAllContexts = mutableListOf(*allContexts.toTypedArray()) + val existingContext = clientKubeConfig.contexts.find { it.name == currentContext } + newAllContexts.remove(existingContext) + newAllContexts.add(newContext) + return newAllContexts + } + + private fun context(name: String, namespace: String, cluster: String, user: String): NamedContext { + val context = ContextBuilder() + .withNamespace(namespace) + .withCluster(cluster) + .withUser(user) + .build() + return NamedContextBuilder() + .withName(name) + .withContext(context) + .build() + } + + private fun createClient(config: Config): Client { + return mock { + on { configuration } doReturn config + } + } + + private fun kubeConfig(exists: Boolean, config: io.fabric8.kubernetes.api.model.Config): com.redhat.devtools.intellij.kubernetes.model.client.KubeConfigAdapter { + return mock { + on { exists() } doReturn exists + on { load() } doReturn config + } + } + + private fun apiConfig(currentContext: String, allContexts: List): io.fabric8.kubernetes.api.model.Config { + return ConfigBuilder() + .withCurrentContext(currentContext) + .withContexts(allContexts) + .build() + } + + private class TestableClientConfig(client: Client, override val kubeConfig: KubeConfigAdapter) + : ClientConfig(client, { it.run() }) } diff --git a/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/mocks/ClientMocks.kt b/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/mocks/ClientMocks.kt index a2f145471..9e39ebf0b 100644 --- a/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/mocks/ClientMocks.kt +++ b/src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/mocks/ClientMocks.kt @@ -43,7 +43,6 @@ import io.fabric8.kubernetes.api.model.batch.v1.Job import io.fabric8.kubernetes.api.model.batch.v1.JobSpec import io.fabric8.kubernetes.client.Client import io.fabric8.kubernetes.client.Config -import io.fabric8.kubernetes.client.KubeConfigFile import io.fabric8.kubernetes.client.KubernetesClient import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.V1ApiextensionAPIGroupDSL @@ -61,7 +60,6 @@ import io.fabric8.kubernetes.client.dsl.PodResource import io.fabric8.kubernetes.client.dsl.Resource import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperation import io.fabric8.kubernetes.client.extension.ExtensibleResource -import java.io.File import java.net.URL @@ -234,7 +232,7 @@ object ClientMocks { .whenever(op).inContainer(name) } - fun namedContext(name: String, namespace: String, cluster: String? = null, user: String? = null): NamedContext { + fun namedContext(name: String, namespace: String, cluster: String, user: String): NamedContext { val context: Context = context(namespace, cluster, user) return namedContext(name, context) } @@ -246,7 +244,7 @@ object ClientMocks { } } - fun context(namespace: String, cluster: String? = null, user: String? = null): Context { + private fun context(namespace: String, cluster: String, user: String): Context { return mock { on { this.namespace } doReturn namespace on { this.cluster } doReturn cluster @@ -258,9 +256,7 @@ object ClientMocks { currentContext: NamedContext?, contexts: List, masterUrl: String = "https://localhost", - apiVersion: String = "v1", - withCurrentConfig: KubeConfigFile? = null, - withContext: KubeConfigFile? = null + apiVersion: String = "v1" ): Config { return mock { on { this.currentContext } doReturn currentContext @@ -268,8 +264,6 @@ object ClientMocks { on { this.masterUrl } doReturn masterUrl on { this.apiVersion } doReturn apiVersion on { this.requestConfig } doReturn mock() - on { this.fileWithCurrentContext } doReturn withCurrentConfig - on { this.getFileWithContext(any()) } doReturn withContext } } @@ -491,11 +485,5 @@ object ClientMocks { return resource } - fun kubeConfigFile(file: File, config: io.fabric8.kubernetes.api.model.Config): KubeConfigFile { - return mock { - on { this.file } doReturn file - on { this.config } doReturn config - } - } }