kubernetesClient = new AtomicReference<>();
+
+ /**
+ * Get or create a {@link KubernetesClient}.
+ *
+ * @return Kubernetes client
+ */
+ public static KubernetesClient getKubernetesClient() {
+ if (kubernetesClient.get() == null) {
+ kubernetesClient.compareAndSet(null, KubernetesUtils.newKubernetesClient());
+ }
+ return kubernetesClient.get();
+ }
+
+ /**
+ * Remove the {@link KubernetesClient} instance.
+ */
+ public static void remove() {
+ kubernetesClient.set(null);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-alibaba-kubernetes-commons/src/main/java/com/alibaba/cloud/kubernetes/commons/KubernetesUtils.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-alibaba-kubernetes-commons/src/main/java/com/alibaba/cloud/kubernetes/commons/KubernetesUtils.java
new file mode 100644
index 0000000000..469b07b4f4
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-alibaba-kubernetes-commons/src/main/java/com/alibaba/cloud/kubernetes/commons/KubernetesUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.commons;
+
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+/**
+ * Kubernetes utils.
+ *
+ *
+ * Usually used to get kube config and create {@link KubernetesClient} instance.
+ *
+ * @author Freeman
+ */
+public final class KubernetesUtils {
+
+ private KubernetesUtils() {
+ throw new UnsupportedOperationException("No KubernetesUtil instances for you!");
+ }
+
+ private static final Config config = new ConfigBuilder().build();
+
+ /**
+ * Get the kube config.
+ *
+ *
+ * NOTE: {@link Config} needs to be a singleton, do NOT modify it.
+ *
+ * @return Config
+ */
+ public static Config config() {
+ return config;
+ }
+
+ /**
+ * Get the current namespace.
+ *
+ * If in kubernetes, it will return the namespace of the pod.
+ *
+ * If not in kubernetes, it will return the namespace of the kube config current
+ * context.
+ *
+ * @return namespace
+ */
+ public static String currentNamespace() {
+ return config.getNamespace();
+ }
+
+ /**
+ * Create a KubernetesClient instance.
+ *
+ * @return new KubernetesClient instance
+ */
+ public static KubernetesClient newKubernetesClient() {
+ return new DefaultKubernetesClient(config);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/pom.xml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/pom.xml
new file mode 100644
index 0000000000..e5de64bc8b
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/pom.xml
@@ -0,0 +1,37 @@
+
+ 4.0.0
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-kubernetes
+ ${revision}
+ ../pom.xml
+
+
+ spring-cloud-starter-alibaba-kubernetes-config
+ Spring Cloud Starter Alibaba Kubernetes Config
+ Spring Cloud Starter Alibaba Kubernetes Config
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-kubernetes-commons
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/KubernetesConfigAutoConfiguration.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/KubernetesConfigAutoConfiguration.java
new file mode 100644
index 0000000000..2daf11a79d
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/KubernetesConfigAutoConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesClientConfiguration;
+import com.alibaba.cloud.kubernetes.config.core.KubernetesConfigWatcher;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Spring Cloud Alibaba Kubernetes Config autoconfiguration.
+ *
+ * @author Freeman
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass({ KubernetesClient.class, ConfigMap.class })
+@ConditionalOnProperty(prefix = KubernetesConfigProperties.PREFIX, name = "enabled", matchIfMissing = true)
+@EnableConfigurationProperties(KubernetesConfigProperties.class)
+@Import(KubernetesClientConfiguration.class)
+public class KubernetesConfigAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public KubernetesConfigWatcher kubernetesConfigWatcher(
+ KubernetesConfigProperties properties, KubernetesClient kubernetesClient) {
+ return new KubernetesConfigWatcher(properties, kubernetesClient);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/KubernetesConfigProperties.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/KubernetesConfigProperties.java
new file mode 100644
index 0000000000..28cb5daccd
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/KubernetesConfigProperties.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesUtils;
+import com.alibaba.cloud.kubernetes.config.util.Preference;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * Spring Cloud Alibaba Kubernetes Config properties.
+ *
+ * @author Freeman
+ */
+@ConfigurationProperties(KubernetesConfigProperties.PREFIX)
+public class KubernetesConfigProperties implements InitializingBean {
+ /**
+ * Prefix of {@link KubernetesConfigProperties}.
+ */
+ public static final String PREFIX = "spring.cloud.alibaba-kubernetes.config";
+
+ /**
+ * Whether to enable the kubernetes config feature.
+ */
+ private boolean enabled = true;
+
+ /**
+ * Default namespace for ConfigMaps and Secrets.
+ *
+ * If in Kubernetes environment, use the namespace of the current pod.
+ *
+ * If not in Kubernetes environment, use the namespace of the current context.
+ */
+ private String namespace = determineNamespace();
+
+ /**
+ * Config preference, default is {@link Preference#REMOTE}, means remote
+ * configurations 'win', will override the local configurations.
+ */
+ private Preference preference = Preference.REMOTE;
+
+ /**
+ * Whether to refresh environment when remote resource was deleted, default value is
+ * {@code false}.
+ *
+ * The default value is {@code false} to prevent app arises abnormal situation from
+ * resource being deleted by mistake.
+ */
+ private boolean refreshOnDelete = false;
+
+ /**
+ * Whether to fail when the config (configmap/secret) is missing, default value is
+ * {@code true}.
+ *
+ * The default value is true to prevent unintended problems caused by not
+ * synchronizing the configuration between environments.
+ */
+ private boolean failOnMissingConfig = true;
+
+ /**
+ * Whether to enable the auto refresh feature, default value is {@code false}.
+ */
+ private boolean refreshable = false;
+
+ private List configMaps = new ArrayList<>();
+
+ private List secrets = new ArrayList<>();
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public Preference getPreference() {
+ return preference;
+ }
+
+ public void setPreference(Preference preference) {
+ this.preference = preference;
+ }
+
+ public List getConfigMaps() {
+ return configMaps;
+ }
+
+ public void setConfigMaps(List configMaps) {
+ this.configMaps = configMaps;
+ }
+
+ public List getSecrets() {
+ return secrets;
+ }
+
+ public void setSecrets(List secrets) {
+ this.secrets = secrets;
+ }
+
+ public boolean isRefreshable() {
+ return refreshable;
+ }
+
+ public void setRefreshable(boolean refreshable) {
+ this.refreshable = refreshable;
+ }
+
+ public boolean isRefreshOnDelete() {
+ return refreshOnDelete;
+ }
+
+ public void setRefreshOnDelete(boolean refreshOnDelete) {
+ this.refreshOnDelete = refreshOnDelete;
+ }
+
+ public boolean isFailOnMissingConfig() {
+ return failOnMissingConfig;
+ }
+
+ public void setFailOnMissingConfig(boolean failOnMissingConfig) {
+ this.failOnMissingConfig = failOnMissingConfig;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KubernetesConfigProperties that = (KubernetesConfigProperties) o;
+ return enabled == that.enabled && refreshOnDelete == that.refreshOnDelete
+ && failOnMissingConfig == that.failOnMissingConfig
+ && refreshable == that.refreshable
+ && Objects.equals(namespace, that.namespace)
+ && preference == that.preference
+ && Objects.equals(configMaps, that.configMaps)
+ && Objects.equals(secrets, that.secrets);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(enabled, namespace, preference, refreshOnDelete,
+ failOnMissingConfig, configMaps, secrets, refreshable);
+ }
+
+ @Override
+ public String toString() {
+ return "KubernetesConfigProperties{" + "enabled=" + enabled + ", namespace='"
+ + namespace + '\'' + ", preference=" + preference + ", refreshOnDelete="
+ + refreshOnDelete + ", failOnMissingConfig=" + failOnMissingConfig
+ + ", configMaps=" + configMaps + ", secrets=" + secrets
+ + ", refreshEnabled=" + refreshable + '}';
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ mergeConfigmaps();
+ mergeSecrets();
+ }
+
+ private void mergeConfigmaps() {
+ for (ConfigMap configMap : configMaps) {
+ if (!StringUtils.hasText(configMap.getName())) {
+ throw new IllegalArgumentException("ConfigMap name must not be empty.");
+ }
+ if (configMap.getNamespace() == null) {
+ configMap.setNamespace(namespace);
+ }
+ if (configMap.getRefreshable() == null) {
+ configMap.setRefreshable(refreshable);
+ }
+ if (configMap.getPreference() == null) {
+ configMap.setPreference(preference);
+ }
+ }
+ }
+
+ private void mergeSecrets() {
+ for (Secret secret : secrets) {
+ if (!StringUtils.hasText(secret.getName())) {
+ throw new IllegalArgumentException("Secret name must not be empty.");
+ }
+ if (secret.getNamespace() == null) {
+ secret.setNamespace(namespace);
+ }
+ if (secret.getRefreshable() == null) {
+ secret.setRefreshable(refreshable);
+ }
+ if (secret.getPreference() == null) {
+ secret.setPreference(preference);
+ }
+ }
+ }
+
+ private static String determineNamespace() {
+ String ns = KubernetesUtils.currentNamespace();
+ return StringUtils.hasText(ns) ? ns : "default";
+ }
+
+ public static class ConfigMap {
+ /**
+ * ConfigMap name.
+ */
+ private String name;
+ /**
+ * Namespace, using
+ * {@code spring.cloud.alibaba-kubernetes.config.namespace} if not
+ * set.
+ */
+ private String namespace;
+ /**
+ * Whether to enable the auto refresh on current ConfigMap, using
+ * {@code spring.cloud.alibaba-kubernetes.config.refreshable} if not
+ * set.
+ */
+ private Boolean refreshable;
+ /**
+ * Config preference, using
+ * {@code spring.cloud.alibaba-kubernetes.config.preference} if not
+ * set.
+ */
+ private Preference preference;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public Boolean getRefreshable() {
+ return refreshable;
+ }
+
+ public void setRefreshable(Boolean refreshable) {
+ this.refreshable = refreshable;
+ }
+
+ public Preference getPreference() {
+ return preference;
+ }
+
+ public void setPreference(Preference preference) {
+ this.preference = preference;
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigMap{" + "name='" + name + '\'' + ", namespace='" + namespace
+ + '\'' + ", refreshable=" + refreshable + ", preference=" + preference
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ConfigMap configMap = (ConfigMap) o;
+ return Objects.equals(name, configMap.name)
+ && Objects.equals(namespace, configMap.namespace)
+ && Objects.equals(refreshable, configMap.refreshable)
+ && preference == configMap.preference;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, namespace, refreshable, preference);
+ }
+ }
+
+ public static class Secret {
+ /**
+ * Secret name.
+ */
+ private String name;
+ /**
+ * Namespace, using
+ * {@code spring.cloud.alibaba-kubernetes.config.namespace} if not
+ * set.
+ */
+ private String namespace;
+ /**
+ * Whether to enable the auto refresh on current Secret, default value is
+ * {@code false}.
+ *
+ * Because Secret is usually used to save sensitive information, the auto refresh
+ * function is not enabled by default. Please consider using ConfigMap if there is
+ * an auto refresh requirement.
+ */
+ private Boolean refreshable = false;
+ /**
+ * Config preference, using
+ * {@code spring.cloud.alibaba-kubernetes.config.preference} if not
+ * set.
+ */
+ private Preference preference;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public Boolean getRefreshable() {
+ return refreshable;
+ }
+
+ public void setRefreshable(Boolean refreshable) {
+ this.refreshable = refreshable;
+ }
+
+ public Preference getPreference() {
+ return preference;
+ }
+
+ public void setPreference(Preference preference) {
+ this.preference = preference;
+ }
+
+ @Override
+ public String toString() {
+ return "Secret{" + "name='" + name + '\'' + ", namespace='" + namespace + '\''
+ + ", refreshable=" + refreshable + ", preference=" + preference + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Secret secret = (Secret) o;
+ return Objects.equals(name, secret.name)
+ && Objects.equals(namespace, secret.namespace)
+ && Objects.equals(refreshable, secret.refreshable)
+ && preference == secret.preference;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, namespace, refreshable, preference);
+ }
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/HasMetadataResourceEventHandler.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/HasMetadataResourceEventHandler.java
new file mode 100644
index 0000000000..90a694e619
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/HasMetadataResourceEventHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.core;
+
+import com.alibaba.cloud.kubernetes.config.KubernetesConfigProperties;
+import com.alibaba.cloud.kubernetes.config.util.Converters;
+import com.alibaba.cloud.kubernetes.config.util.RefreshContext;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.cloud.endpoint.event.RefreshEvent;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * {@link HasMetadataResourceEventHandler} process {@link HasMetadata}(ConfigMap/Secret)
+ * events, trigger {@link RefreshEvent} when necessary.
+ *
+ * @author Freeman
+ */
+class HasMetadataResourceEventHandler
+ implements ResourceEventHandler {
+ private static final Logger log = LoggerFactory
+ .getLogger(HasMetadataResourceEventHandler.class);
+
+ private final ApplicationContext context;
+ private final KubernetesConfigProperties properties;
+
+ HasMetadataResourceEventHandler(ApplicationContext context,
+ KubernetesConfigProperties properties) {
+ this.context = context;
+ this.properties = properties;
+ }
+
+ @Override
+ public void onAdd(HasMetadata obj) {
+ if (log.isDebugEnabled()) {
+ log.debug("[Kubernetes Config] {} '{}' added in namespace '{}'",
+ obj.getKind(), obj.getMetadata().getName(),
+ obj.getMetadata().getNamespace());
+ }
+ // When application start up, the informer will trigger an onAdd event, but at
+ // this phase application is not
+ // ready, and it will not trigger a real refresh.
+ // see
+ // org.springframework.cloud.endpoint.event.RefreshEventListener#handle(RefreshEvent)
+ refresh(obj);
+ }
+
+ @Override
+ public void onUpdate(HasMetadata oldObj, HasMetadata newObj) {
+ if (log.isDebugEnabled()) {
+ log.debug("[Kubernetes Config] {} '{}' updated in namespace '{}'",
+ newObj.getKind(), newObj.getMetadata().getName(),
+ newObj.getMetadata().getNamespace());
+ }
+ refresh(newObj);
+ }
+
+ @Override
+ public void onDelete(HasMetadata obj, boolean deletedFinalStateUnknown) {
+ if (log.isDebugEnabled()) {
+ log.debug("[Kubernetes Config] {} '{}' deleted in namespace '{}'",
+ obj.getKind(), obj.getMetadata().getName(),
+ obj.getMetadata().getNamespace());
+ }
+ if (properties.isRefreshOnDelete()) {
+ deletePropertySourceOfResource(obj);
+ refresh(obj);
+ }
+ else {
+ if (log.isInfoEnabled()) {
+ log.info(
+ "[Kubernetes Config] {} '{}' was deleted in namespace '{}', refresh on delete is disabled, ignore the delete event",
+ obj.getKind(), obj.getMetadata().getName(),
+ obj.getMetadata().getNamespace());
+ }
+ }
+ }
+
+ private void deletePropertySourceOfResource(HasMetadata resource) {
+ String propertySourceName = Converters.propertySourceNameForResource(resource);
+ ((ConfigurableEnvironment) context.getEnvironment()).getPropertySources()
+ .remove(propertySourceName);
+ }
+
+ private void refresh(HasMetadata obj) {
+ // Need to handle the case where events are processed asynchronously?
+ RefreshEvent refreshEvent = new RefreshEvent(obj, null,
+ String.format("%s changed", obj.getKind()));
+ RefreshContext.set(new RefreshContext(context, refreshEvent));
+ try {
+ context.publishEvent(refreshEvent);
+ }
+ finally {
+ RefreshContext.remove();
+ }
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/KubernetesConfigEnvironmentPostProcessor.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/KubernetesConfigEnvironmentPostProcessor.java
new file mode 100644
index 0000000000..226b58b0c1
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/KubernetesConfigEnvironmentPostProcessor.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.core;
+
+import java.net.HttpURLConnection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesClientHolder;
+import com.alibaba.cloud.kubernetes.config.KubernetesConfigProperties;
+import com.alibaba.cloud.kubernetes.config.exception.AbstractKubernetesConfigException;
+import com.alibaba.cloud.kubernetes.config.exception.KubernetesConfigMissingException;
+import com.alibaba.cloud.kubernetes.config.exception.KubernetesForbiddenException;
+import com.alibaba.cloud.kubernetes.config.exception.KubernetesUnavailableException;
+import com.alibaba.cloud.kubernetes.config.util.Pair;
+import com.alibaba.cloud.kubernetes.config.util.Preference;
+import com.alibaba.cloud.kubernetes.config.util.RefreshContext;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import org.apache.commons.logging.Log;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.boot.logging.DeferredLogFactory;
+import org.springframework.cloud.endpoint.event.RefreshEvent;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.env.StandardEnvironment;
+
+import static com.alibaba.cloud.kubernetes.config.util.Converters.toPropertySource;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Kubernetes config {@link EnvironmentPostProcessor}.
+ *
+ *
+ * There are two ways to use this post processor:
+ *
+ * - when application starts up
+ * - when application triggers a {@link RefreshEvent}
+ *
+ *
+ *
+ * When application starts up, this processor will load all the ConfigMaps/Secrets.
+ *
+ * When application triggers a {@link RefreshEvent}, Spring will copy a
+ * {@link Environment} and invoke all {@link EnvironmentPostProcessor}s, then replace the
+ * {@link PropertySource} if copied {@link Environment} has the same name
+ * {@link PropertySource}. So this processor only converts the refreshed resource to
+ * {@link PropertySource} and add it to the {@link Environment} when refresh event is
+ * triggered.
+ *
+ * @author Freeman
+ * @see org.springframework.cloud.context.refresh.ContextRefresher
+ * @see org.springframework.cloud.context.refresh.ConfigDataContextRefresher
+ */
+public class KubernetesConfigEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
+ /**
+ * Order of the post processor.
+ */
+ public static final int ORDER = Ordered.LOWEST_PRECEDENCE - 10;
+
+ private final Log log;
+ private final KubernetesClient client;
+
+ public KubernetesConfigEnvironmentPostProcessor(DeferredLogFactory logFactory) {
+ this.log = logFactory.getLog(getClass());
+ this.client = KubernetesClientHolder.getKubernetesClient();
+ }
+
+ @Override
+ public void postProcessEnvironment(ConfigurableEnvironment environment,
+ SpringApplication application) {
+ Boolean enabled = environment.getProperty(
+ KubernetesConfigProperties.PREFIX + ".enabled", Boolean.class, true);
+ if (!enabled) {
+ return;
+ }
+
+ KubernetesConfigProperties properties = getKubernetesConfigProperties(
+ environment);
+
+ if (isRefreshing()) {
+ RefreshEvent event = RefreshContext.get().refreshEvent();
+ Object resource = event.getSource();
+ // Just add it, {@link
+ // org.springframework.cloud.context.refresh.ContextRefresher} will
+ // replace the PropertySource for you.
+ if (resource instanceof ConfigMap) {
+ environment.getPropertySources()
+ .addLast(toPropertySource((ConfigMap) resource));
+ }
+ else if (resource instanceof Secret) {
+ environment.getPropertySources()
+ .addLast(toPropertySource((Secret) resource));
+ }
+ else {
+ log.warn("[Kubernetes Config] Refreshed a unknown resource type: "
+ + resource.getClass());
+ }
+ }
+ else {
+ pullConfigMaps(properties, environment);
+ pullSecrets(properties, environment);
+ }
+ }
+
+ private static KubernetesConfigProperties getKubernetesConfigProperties(
+ ConfigurableEnvironment environment) {
+ return Optional
+ .ofNullable(RefreshContext.get()).map(context -> context
+ .applicationContext().getBean(KubernetesConfigProperties.class))
+ .orElseGet(() -> {
+ KubernetesConfigProperties prop = Binder.get(environment)
+ .bind(KubernetesConfigProperties.PREFIX,
+ KubernetesConfigProperties.class)
+ .orElseGet(KubernetesConfigProperties::new);
+ prop.afterPropertiesSet();
+ return prop;
+ });
+ }
+
+ private void pullConfigMaps(KubernetesConfigProperties properties,
+ ConfigurableEnvironment environment) {
+ properties.getConfigMaps().stream()
+ .map(configmap -> Optional
+ .ofNullable(propertySourceForConfigMap(configmap,
+ properties.isFailOnMissingConfig()))
+ .map(ps -> Pair.of(configmap.getPreference(), ps)).orElse(null))
+ .filter(Objects::nonNull)
+ .collect(groupingBy(Pair::key, mapping(Pair::value, toList())))
+ .forEach((preference, remotePropertySources) -> {
+ addPropertySourcesToEnvironment(environment, preference,
+ remotePropertySources);
+ });
+ }
+
+ private void pullSecrets(KubernetesConfigProperties properties,
+ ConfigurableEnvironment environment) {
+ properties.getSecrets().stream()
+ .map(secret -> Optional
+ .ofNullable(propertySourceForSecret(secret,
+ properties.isFailOnMissingConfig()))
+ .map(ps -> Pair.of(secret.getPreference(), ps)).orElse(null))
+ .filter(Objects::nonNull)
+ .collect(groupingBy(Pair::key, mapping(Pair::value, toList())))
+ .forEach((preference, remotePropertySources) -> {
+ addPropertySourcesToEnvironment(environment, preference,
+ remotePropertySources);
+ });
+ }
+
+ private static void addPropertySourcesToEnvironment(
+ ConfigurableEnvironment environment, Preference preference,
+ List> remotePropertySources) {
+ MutablePropertySources propertySources = environment.getPropertySources();
+ switch (preference) {
+ case LOCAL:
+ // The latter config should win the previous config
+ Collections.reverse(remotePropertySources);
+ remotePropertySources.forEach(propertySources::addLast);
+ break;
+ case REMOTE:
+ // we can't let it override the system environment properties
+ remotePropertySources.forEach(ps -> propertySources.addAfter(
+ StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, ps));
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown config preference: " + preference.name());
+ }
+ }
+
+ private EnumerablePropertySource> propertySourceForConfigMap(
+ KubernetesConfigProperties.ConfigMap cm, boolean isFailOnMissingConfig) {
+ ConfigMap configMap;
+ try {
+ configMap = client.configMaps().inNamespace(cm.getNamespace())
+ .withName(cm.getName()).get();
+ }
+ catch (KubernetesClientException e) {
+ processException(ConfigMap.class, cm.getName(), cm.getNamespace(), e);
+ return null;
+ }
+ if (configMap == null) {
+ failApplicationStartUpIfNecessary(ConfigMap.class, cm.getName(),
+ cm.getNamespace(), isFailOnMissingConfig);
+ return null;
+ }
+ return toPropertySource(configMap);
+ }
+
+ private EnumerablePropertySource> propertySourceForSecret(
+ KubernetesConfigProperties.Secret secret, boolean isFailOnMissingConfig) {
+ Secret secretInK8s;
+ try {
+ secretInK8s = client.secrets().inNamespace(secret.getNamespace())
+ .withName(secret.getName()).get();
+ }
+ catch (KubernetesClientException e) {
+ processException(Secret.class, secret.getName(), secret.getNamespace(), e);
+ return null;
+ }
+ if (secretInK8s == null) {
+ failApplicationStartUpIfNecessary(Secret.class, secret.getName(),
+ secret.getNamespace(), isFailOnMissingConfig);
+ return null;
+ }
+ return toPropertySource(secretInK8s);
+ }
+
+ private void processException(Class> type, String name, String namespace,
+ KubernetesClientException e) {
+ if (!isRefreshing()) {
+ throw kubernetesConfigException(type, name, namespace, e);
+ }
+ log.warn(String.format(
+ "[Kubernetes Config] Kubernetes client exception while refreshing %s, so properties value won't change",
+ type.getSimpleName()), e);
+ }
+
+ private static AbstractKubernetesConfigException kubernetesConfigException(
+ Class> type, String name, String namespace, KubernetesClientException e) {
+ // Usually the Service Account or user does not have enough privileges.
+ if (e.getCode() == HttpURLConnection.HTTP_FORBIDDEN) {
+ return new KubernetesForbiddenException(type, name, namespace, e);
+ }
+ // Can't connect to the kubernetes cluster.
+ return new KubernetesUnavailableException(type, name, namespace, e);
+ }
+
+ /**
+ * Fail the application start up if necessary when the resource is missing.
+ *
+ *
+ * NOTE: do nothing if the application is refreshing
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @param namespace the namespace of the resource
+ * @param isFailOnMissingConfig whether to fail the application start up
+ */
+ private void failApplicationStartUpIfNecessary(Class> type, String name,
+ String namespace, boolean isFailOnMissingConfig) {
+ log.warn(String.format("[Kubernetes Config] %s '%s' not found in namespace '%s'",
+ type.getSimpleName(), name, namespace));
+ if (!isRefreshing() && isFailOnMissingConfig) {
+ throw new KubernetesConfigMissingException(type, name, namespace, null);
+ }
+ }
+
+ private static boolean isRefreshing() {
+ return RefreshContext.get() != null;
+ }
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/KubernetesConfigWatcher.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/KubernetesConfigWatcher.java
new file mode 100644
index 0000000000..2798bc89d5
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/core/KubernetesConfigWatcher.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.core;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.alibaba.cloud.kubernetes.config.KubernetesConfigProperties;
+import com.alibaba.cloud.kubernetes.config.util.ResourceKey;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import static com.alibaba.cloud.kubernetes.config.util.ResourceKeyUtils.resourceKey;
+
+/**
+ * Watcher for config resources change.
+ *
+ * @author Freeman
+ */
+public class KubernetesConfigWatcher
+ implements SmartInitializingSingleton, ApplicationContextAware, DisposableBean {
+ private static final Logger log = LoggerFactory.getLogger(KubernetesConfigWatcher.class);
+
+ private final Map> configmapInformers = new LinkedHashMap<>();
+ private final Map> secretInformers = new LinkedHashMap<>();
+ private final KubernetesConfigProperties properties;
+ private final KubernetesClient client;
+
+ private ApplicationContext context;
+
+ public KubernetesConfigWatcher(KubernetesConfigProperties properties, KubernetesClient client) {
+ this.properties = properties;
+ this.client = client;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext)
+ throws BeansException {
+ this.context = applicationContext;
+ }
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ watchRefreshableResources(properties, client);
+ }
+
+ @Override
+ public void destroy() {
+ configmapInformers.values().forEach(SharedIndexInformer::close);
+ secretInformers.values().forEach(SharedIndexInformer::close);
+ if (log.isInfoEnabled()) {
+ log.info("[Kubernetes Config] ConfigMap and Secret informers closed");
+ }
+ }
+
+ private void watchRefreshableResources(KubernetesConfigProperties properties,
+ KubernetesClient client) {
+ properties.getConfigMaps().stream()
+ .filter(KubernetesConfigProperties.ConfigMap::getRefreshable)
+ .forEach(configmap -> {
+ SharedIndexInformer informer = client.configMaps()
+ .inNamespace(configmap.getNamespace())
+ .withName(configmap.getName())
+ .inform(new HasMetadataResourceEventHandler<>(context,
+ properties));
+ configmapInformers.put(resourceKey(configmap), informer);
+ });
+ log(configmapInformers);
+ properties.getSecrets().stream()
+ .filter(KubernetesConfigProperties.Secret::getRefreshable)
+ .forEach(secret -> {
+ SharedIndexInformer informer = client.secrets()
+ .inNamespace(secret.getNamespace()).withName(secret.getName())
+ .inform(new HasMetadataResourceEventHandler<>(context,
+ properties));
+ secretInformers.put(resourceKey(secret), informer);
+ });
+ log(secretInformers);
+ }
+
+ private static void log(
+ Map> informers) {
+ List names = informers.keySet().stream().map(resourceKey -> String
+ .join(".", resourceKey.name(), resourceKey.namespace()))
+ .collect(Collectors.toList());
+ if (!names.isEmpty() && log.isInfoEnabled()) {
+ log.info("[Kubernetes Config] Start watching {}s: {}",
+ informers.keySet().iterator().next().type(), names);
+ }
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/AbstractKubernetesConfigException.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/AbstractKubernetesConfigException.java
new file mode 100644
index 0000000000..c7b994e78e
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/AbstractKubernetesConfigException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+/**
+ * @author Freeman
+ */
+public abstract class AbstractKubernetesConfigException extends RuntimeException {
+ private final Class> type;
+ private final String name;
+ private final String namespace;
+
+ public AbstractKubernetesConfigException(Class> type, String name, String namespace,
+ Throwable cause) {
+ super(cause);
+ this.type = type;
+ this.name = name;
+ this.namespace = namespace;
+ }
+
+ public Class> getType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesConfigMissingException.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesConfigMissingException.java
new file mode 100644
index 0000000000..928c447337
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesConfigMissingException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesConfigMissingException extends AbstractKubernetesConfigException {
+
+ public KubernetesConfigMissingException(Class> type, String name, String namespace,
+ Throwable cause) {
+ super(type, name, namespace, cause);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesConfigMissingFailureAnalyzer.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesConfigMissingFailureAnalyzer.java
new file mode 100644
index 0000000000..0ace7cafcd
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesConfigMissingFailureAnalyzer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+import com.alibaba.cloud.kubernetes.config.KubernetesConfigProperties;
+
+import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
+import org.springframework.boot.diagnostics.FailureAnalysis;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesConfigMissingFailureAnalyzer
+ extends AbstractFailureAnalyzer {
+ @Override
+ protected FailureAnalysis analyze(Throwable rootFailure,
+ KubernetesConfigMissingException cause) {
+ String description = String.format("%s name '%s' is missing in namespace '%s'",
+ cause.getType().getSimpleName(), cause.getName(), cause.getNamespace());
+ String action = String.format(
+ "You can set '%s.fail-on-missing-config' to 'false' to not prevent the application start up.",
+ KubernetesConfigProperties.PREFIX);
+ return new FailureAnalysis(description, action, cause);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesForbiddenException.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesForbiddenException.java
new file mode 100644
index 0000000000..6275292523
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesForbiddenException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesForbiddenException extends AbstractKubernetesConfigException {
+
+ public KubernetesForbiddenException(Class> type, String name, String namespace,
+ Throwable cause) {
+ super(type, name, namespace, cause);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesForbiddenFailureAnalyzer.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesForbiddenFailureAnalyzer.java
new file mode 100644
index 0000000000..d760497ba1
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesForbiddenFailureAnalyzer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesUtils;
+
+import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
+import org.springframework.boot.diagnostics.FailureAnalysis;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesForbiddenFailureAnalyzer
+ extends AbstractFailureAnalyzer {
+
+ @Override
+ protected FailureAnalysis analyze(Throwable rootFailure,
+ KubernetesForbiddenException cause) {
+ String description = String.format(
+ "It looks like you don't have enough access to the resource (%s '%s.%s') in context '%s'.",
+ cause.getType().getSimpleName(), cause.getName(), cause.getNamespace(),
+ KubernetesUtils.config().getCurrentContext().getName());
+ String action = "Please ask the administrator to add enough permissions for you, or are you just in the wrong context?";
+ return new FailureAnalysis(description, action, cause);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesUnavailableException.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesUnavailableException.java
new file mode 100644
index 0000000000..07fb545a14
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesUnavailableException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesUnavailableException extends AbstractKubernetesConfigException {
+
+ public KubernetesUnavailableException(Class> type, String name, String namespace,
+ Throwable cause) {
+ super(type, name, namespace, cause);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesUnavailableFailureAnalyzer.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesUnavailableFailureAnalyzer.java
new file mode 100644
index 0000000000..5528477183
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/exception/KubernetesUnavailableFailureAnalyzer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.exception;
+
+import java.util.stream.Collectors;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesUtils;
+import io.fabric8.kubernetes.api.model.NamedContext;
+
+import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
+import org.springframework.boot.diagnostics.FailureAnalysis;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesUnavailableFailureAnalyzer
+ extends AbstractFailureAnalyzer {
+ @Override
+ protected FailureAnalysis analyze(Throwable rootFailure,
+ KubernetesUnavailableException cause) {
+ String description = String.format(
+ "Current context '%s' can not connect to Kubernetes cluster.\n\n"
+ + "Are you sure you've set the right context? Available contexts are: %s.\n",
+ KubernetesUtils.config().getCurrentContext().getName(),
+ KubernetesUtils.config().getContexts().stream().map(NamedContext::getName)
+ .collect(Collectors.toList()));
+ String action = "Please check your kube config file and Kubernetes cluster status.";
+ return new FailureAnalysis(description, action, cause);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/FileProcessor.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/FileProcessor.java
new file mode 100644
index 0000000000..924a16c6de
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/FileProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * {@link FileProcessor} use to generate {@link PropertySource} from file content.
+ *
+ * @author Freeman
+ */
+public interface FileProcessor {
+
+ /**
+ * Whether the fileName is supported by the processor.
+ *
+ * @param fileName file name
+ * @return true if hit
+ */
+ boolean hit(String fileName);
+
+ /**
+ * Generate property source from file content.
+ *
+ * @param name property source name
+ * @param content file content
+ * @return property source
+ */
+ EnumerablePropertySource> generate(String name, String content);
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/JsonFileProcessor.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/JsonFileProcessor.java
new file mode 100644
index 0000000000..4be7de082d
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/JsonFileProcessor.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+import org.springframework.boot.env.YamlPropertySourceLoader;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.ByteArrayResource;
+
+/**
+ * Convert JSON string to {@link PropertySource}, support JSON array.
+ *
+ * @author Freeman
+ */
+public class JsonFileProcessor implements FileProcessor {
+ private static final Logger log = LoggerFactory.getLogger(JsonFileProcessor.class);
+
+ private static final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
+
+ private static final Yaml yaml = new Yaml();
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ public boolean hit(String fileName) {
+ return fileName.endsWith(".json");
+ }
+
+ @Override
+ public EnumerablePropertySource> generate(String name, String content) {
+ if (content.trim().startsWith("{")) {
+ // json object
+ return convertJsonObjectStringToPropertySource(name, content);
+ }
+ CompositePropertySource result = new CompositePropertySource(name);
+ try {
+ List> list = objectMapper.readValue(content, List.class);
+ if (list.isEmpty()) {
+ return result;
+ }
+ for (int i = 0; i < list.size(); i++) {
+ Object o = list.get(i);
+ if (o instanceof Map) {
+ // means it's a json object
+ result.addPropertySource(convertJsonObjectStringToPropertySource(
+ String.format("%s[%d]", name, i),
+ objectMapper.writeValueAsString(o)));
+ }
+ }
+ }
+ catch (JsonProcessingException e) {
+ log.warn("Failed to parse json file", e);
+ }
+ return result;
+ }
+
+ private static CompositePropertySource convertJsonObjectStringToPropertySource(
+ String name, String jsonObjectString) {
+ // We don't want to change the Spring default behavior
+ // this is how we convert json to PropertySource
+ // json -> java.util.Map -> yaml -> PropertySource
+ Map, ?> map = new HashMap<>();
+ try {
+ map = objectMapper.readValue(jsonObjectString, Map.class);
+ }
+ catch (JsonProcessingException e) {
+ log.warn("Failed to parse json file", e);
+ }
+ CompositePropertySource propertySource = new CompositePropertySource(name);
+ try {
+ String yamlString = yaml.dump(map);
+ List> pss = loader.load(name + "[part]",
+ new ByteArrayResource(yamlString.getBytes(StandardCharsets.UTF_8)));
+ propertySource.getPropertySources().addAll(pss);
+ }
+ catch (IOException e) {
+ log.warn("Failed to parse yaml file", e);
+ }
+ return propertySource;
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/PropertiesFileProcessor.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/PropertiesFileProcessor.java
new file mode 100644
index 0000000000..b5a9067b7f
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/PropertiesFileProcessor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.boot.env.PropertiesPropertySourceLoader;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.ByteArrayResource;
+
+/**
+ * Convert properties file to {@link PropertySource}.
+ *
+ * @author Freeman
+ */
+public class PropertiesFileProcessor implements FileProcessor {
+ private static final Logger log = LoggerFactory
+ .getLogger(PropertiesFileProcessor.class);
+
+ private static final PropertiesPropertySourceLoader loader = new PropertiesPropertySourceLoader();
+
+ @Override
+ public boolean hit(String fileName) {
+ return Arrays.stream(loader.getFileExtensions()).anyMatch(fileName::endsWith);
+ }
+
+ @Override
+ public EnumerablePropertySource> generate(String name, String content) {
+ CompositePropertySource propertySource = new CompositePropertySource(name);
+ try {
+ List> pss = loader.load(name,
+ new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)));
+ propertySource.getPropertySources().addAll(pss);
+ }
+ catch (IOException e) {
+ log.warn("Failed to parse properties file", e);
+ }
+ return propertySource;
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/YamlFileProcessor.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/YamlFileProcessor.java
new file mode 100644
index 0000000000..59ee71c832
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/processor/YamlFileProcessor.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.boot.env.YamlPropertySourceLoader;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+
+/**
+ * Convert yaml file to {@link PropertySource}, support multi-document yaml file.
+ *
+ * @author Freeman
+ */
+public class YamlFileProcessor implements FileProcessor {
+ private static final Logger log = LoggerFactory.getLogger(YamlFileProcessor.class);
+
+ private static final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
+
+ @Override
+ public boolean hit(String fileName) {
+ return Arrays.stream(loader.getFileExtensions()).anyMatch(fileName::endsWith);
+ }
+
+ @Override
+ public EnumerablePropertySource> generate(String name, String content) {
+ Resource resource = new ByteArrayResource(
+ content.getBytes(StandardCharsets.UTF_8));
+ CompositePropertySource propertySource = new CompositePropertySource(name);
+ try {
+ List> sources = loader.load(name, resource);
+ propertySource.getPropertySources().addAll(sources);
+ }
+ catch (IOException e) {
+ log.warn("Failed to parse yaml file", e);
+ }
+ return propertySource;
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Converters.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Converters.java
new file mode 100644
index 0000000000..675cf1eab8
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Converters.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.alibaba.cloud.kubernetes.config.processor.FileProcessor;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.Secret;
+
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.MapPropertySource;
+
+import static com.alibaba.cloud.kubernetes.config.util.Processors.fileProcessors;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * {@link Converters} use to convert ConfigMap/Secret to {@link EnumerablePropertySource}.
+ *
+ * @author Freeman
+ */
+public final class Converters {
+ private Converters() {
+ throw new UnsupportedOperationException("No Converter instances for you!");
+ }
+
+ private static EnumerablePropertySource> toPropertySource(String propertySourceName,
+ Map data) {
+ CompositePropertySource compositePropertySource = new CompositePropertySource(
+ propertySourceName);
+ List singlePairPropertySources = new ArrayList<>();
+ data.forEach((key, content) -> {
+ EnumerablePropertySource> ps = toPropertySource(key, content,
+ propertySourceName + "[" + key + "]");
+ if (ps instanceof SinglePairPropertySource) {
+ singlePairPropertySources.add((SinglePairPropertySource) ps);
+ }
+ else {
+ compositePropertySource.addPropertySource(ps);
+ }
+ });
+ if (!singlePairPropertySources.isEmpty()) {
+ Map pairProperties = singlePairPropertySources.stream()
+ .map(SinglePairPropertySource::getSinglePair)
+ .collect(Collectors.toMap(Pair::key, Pair::value,
+ (oldValue, newValue) -> newValue, LinkedHashMap::new));
+ compositePropertySource.addPropertySource(
+ new MapPropertySource(propertySourceName + "[pair]", pairProperties));
+ }
+ return compositePropertySource;
+ }
+
+ private static EnumerablePropertySource> toPropertySource(String key,
+ String content, String propertySourceName) {
+ for (FileProcessor fileProcessor : fileProcessors()) {
+ if (fileProcessor.hit(key)) {
+ return fileProcessor.generate(propertySourceName, content);
+ }
+ }
+ // key-value pair
+ return new SinglePairPropertySource(propertySourceName, key, content);
+ }
+
+ /**
+ * Generate a {@link EnumerablePropertySource} from a {@link ConfigMap}.
+ *
+ * @param configMap the config map
+ * @return the property source
+ */
+ public static EnumerablePropertySource> toPropertySource(ConfigMap configMap) {
+ return toPropertySource(propertySourceNameForResource(configMap),
+ configMap.getData());
+ }
+
+ /**
+ * Generate a {@link EnumerablePropertySource} from a {@link Secret}.
+ *
+ * @param secret the secret
+ * @return the property source
+ */
+ public static EnumerablePropertySource> toPropertySource(Secret secret) {
+ // data is base64 encoded
+ Map data = secret.getData();
+ Map encodedValue = new LinkedHashMap<>(data);
+ data.replaceAll((key, value) -> stripTrailing(
+ new String(Base64.getDecoder().decode(value), UTF_8))); // secret will add
+ // newlines
+ // automatically
+ Map decodedValue = new LinkedHashMap<>(data);
+ CompositePropertySource result = new CompositePropertySource(
+ propertySourceNameForResource(secret));
+ result.addPropertySource(toPropertySource(
+ propertySourceNameForResource(secret) + "[decoded]", decodedValue));
+ result.addPropertySource(toPropertySource(
+ propertySourceNameForResource(secret) + "[encoded]", encodedValue));
+ return result;
+ }
+
+ /**
+ * Strip trailing whitespace from a string.
+ *
+ * @param str string
+ * @return string without trailing whitespace
+ */
+ static String stripTrailing(String str) {
+ return str.replaceAll("\\s+$", "");
+ }
+
+ /**
+ * Generate property source name for resource that have metadata.
+ *
+ * @param hasMetadataResource the resource that have metadata
+ * @return the property source name
+ */
+ public static String propertySourceNameForResource(HasMetadata hasMetadataResource) {
+ return String.format("%s:%s.%s", hasMetadataResource.getKind(),
+ hasMetadataResource.getMetadata().getName(),
+ hasMetadataResource.getMetadata().getNamespace());
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Pair.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Pair.java
new file mode 100644
index 0000000000..f7923e7fea
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Pair.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+/**
+ * Utility class for holding a pair of values.
+ *
+ * @author Freeman
+ */
+public final class Pair {
+ private final K key;
+ private final V value;
+
+ private Pair(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public K key() {
+ return key;
+ }
+
+ public V value() {
+ return value;
+ }
+
+ public static Pair of(K key, V value) {
+ return new Pair<>(key, value);
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Preference.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Preference.java
new file mode 100644
index 0000000000..030ca419a0
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Preference.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+/**
+ * Configuration preference.
+ *
+ * @author Freeman
+ */
+public enum Preference {
+ /**
+ * LOCAL means local configuration has higher priority and will override the remote
+ * configuration.
+ */
+ LOCAL,
+ /**
+ * REMOTE means remote configuration has higher priority and will override the local
+ * configuration.
+ */
+ REMOTE
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Processors.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Processors.java
new file mode 100644
index 0000000000..f0c420e8f9
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/Processors.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.alibaba.cloud.kubernetes.config.processor.FileProcessor;
+import com.alibaba.cloud.kubernetes.config.processor.JsonFileProcessor;
+import com.alibaba.cloud.kubernetes.config.processor.PropertiesFileProcessor;
+import com.alibaba.cloud.kubernetes.config.processor.YamlFileProcessor;
+
+/**
+ * {@link Processors} holds all the {@link FileProcessor} instances.
+ *
+ * @author Freeman
+ */
+public final class Processors {
+
+ private Processors() {
+ throw new UnsupportedOperationException("No Processors instances for you!");
+ }
+
+ private static final List processors = Arrays.asList(
+ new YamlFileProcessor(), new PropertiesFileProcessor(),
+ new JsonFileProcessor());
+
+ public static List fileProcessors() {
+ return processors;
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/RefreshContext.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/RefreshContext.java
new file mode 100644
index 0000000000..fd339940b2
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/RefreshContext.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import java.util.Objects;
+
+import org.springframework.cloud.endpoint.event.RefreshEvent;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Helper class to get the Spring ApplicationContext and RefreshEvent when refreshing the
+ * context.
+ *
+ * @author Freeman
+ */
+public final class RefreshContext {
+ private final ApplicationContext applicationContext;
+ private final RefreshEvent refreshEvent;
+
+ public RefreshContext(ApplicationContext applicationContext,
+ RefreshEvent refreshEvent) {
+ this.applicationContext = applicationContext;
+ this.refreshEvent = refreshEvent;
+ }
+
+ public ApplicationContext applicationContext() {
+ return applicationContext;
+ }
+
+ public RefreshEvent refreshEvent() {
+ return refreshEvent;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+ RefreshContext that = (RefreshContext) obj;
+ return Objects.equals(this.applicationContext, that.applicationContext)
+ && Objects.equals(this.refreshEvent, that.refreshEvent);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(applicationContext, refreshEvent);
+ }
+
+ @Override
+ public String toString() {
+ return "RefreshContext[" + "applicationContext=" + applicationContext + ", "
+ + "refreshEvent=" + refreshEvent + ']';
+ }
+
+ private static final ThreadLocal holder = new ThreadLocal<>();
+
+ public static void set(RefreshContext refreshContext) {
+ holder.set(refreshContext);
+ }
+
+ public static RefreshContext get() {
+ return holder.get();
+ }
+
+ public static void remove() {
+ holder.remove();
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/ResourceKey.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/ResourceKey.java
new file mode 100644
index 0000000000..3bd7f7def0
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/ResourceKey.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import java.util.Objects;
+
+/**
+ * {@link ResourceKey} represents identity of a resource.
+ *
+ * @author Freeman
+ */
+public final class ResourceKey {
+ private final String type;
+ private final String name;
+ private final String namespace;
+
+ public ResourceKey(String type, String name, String namespace) {
+ this.type = type;
+ this.name = name;
+ this.namespace = namespace;
+ }
+
+ public String type() {
+ return type;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String namespace() {
+ return namespace;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ResourceKey that = (ResourceKey) o;
+ return Objects.equals(type, that.type) && Objects.equals(name, that.name)
+ && Objects.equals(namespace, that.namespace);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, name, namespace);
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceKey{" + "type='" + type + '\'' + ", name='" + name + '\''
+ + ", namespace='" + namespace + '\'' + '}';
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/ResourceKeyUtils.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/ResourceKeyUtils.java
new file mode 100644
index 0000000000..b126d75a95
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/ResourceKeyUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import com.alibaba.cloud.kubernetes.config.KubernetesConfigProperties;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.Secret;
+
+/**
+ * {@link ResourceKeyUtils} used to generate {@link ResourceKey}.
+ *
+ * @author Freeman
+ */
+public final class ResourceKeyUtils {
+
+ private ResourceKeyUtils() {
+ throw new UnsupportedOperationException("No ResourceKeyUtils instances for you!");
+ }
+
+ /**
+ * Generate a {@link ResourceKey} from {@link KubernetesConfigProperties.ConfigMap}.
+ *
+ * @param configMap {@link KubernetesConfigProperties.ConfigMap}
+ * @return {@link ResourceKey}
+ */
+ public static ResourceKey resourceKey(
+ KubernetesConfigProperties.ConfigMap configMap) {
+ return new ResourceKey(ConfigMap.class.getSimpleName(), configMap.getName(),
+ configMap.getNamespace());
+ }
+
+ /**
+ * Generate a {@link ResourceKey} from {@link KubernetesConfigProperties.Secret}.
+ *
+ * @param secret {@link KubernetesConfigProperties.Secret}
+ * @return {@link ResourceKey}
+ */
+ public static ResourceKey resourceKey(KubernetesConfigProperties.Secret secret) {
+ return new ResourceKey(Secret.class.getSimpleName(), secret.getName(),
+ secret.getNamespace());
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/SinglePairPropertySource.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/SinglePairPropertySource.java
new file mode 100644
index 0000000000..b550fbf20b
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/java/com/alibaba/cloud/kubernetes/config/util/SinglePairPropertySource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * {@link PropertySource} that contains a single key-value pair.
+ *
+ * @author Freeman
+ */
+public class SinglePairPropertySource extends MapPropertySource {
+
+ public SinglePairPropertySource(String propertySourceName, String key, Object value) {
+ super(propertySourceName, Collections.singletonMap(key, value));
+ }
+
+ public Pair getSinglePair() {
+ Map source = getSource();
+ Map.Entry entry = source.entrySet().iterator().next();
+ return Pair.of(entry.getKey(), entry.getValue());
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..760ed5026c
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,10 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.alibaba.cloud.kubernetes.config.KubernetesConfigAutoConfiguration
+
+org.springframework.boot.env.EnvironmentPostProcessor=\
+ com.alibaba.cloud.kubernetes.config.core.KubernetesConfigEnvironmentPostProcessor
+
+org.springframework.boot.diagnostics.FailureAnalyzer=\
+ com.alibaba.cloud.kubernetes.config.exception.KubernetesConfigMissingFailureAnalyzer,\
+ com.alibaba.cloud.kubernetes.config.exception.KubernetesUnavailableFailureAnalyzer,\
+ com.alibaba.cloud.kubernetes.config.exception.KubernetesForbiddenFailureAnalyzer
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/ConfigMapIntegrationTests.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/ConfigMapIntegrationTests.java
new file mode 100644
index 0000000000..2243af2f0a
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/ConfigMapIntegrationTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.it;
+
+import com.alibaba.cloud.kubernetes.config.testsupport.KubernetesAvailable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.ActiveProfiles;
+
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.createOrReplaceConfigMap;
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.deleteConfigMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
+
+/**
+ * @author Freeman
+ */
+@KubernetesAvailable
+@SpringBootTest(classes = Empty.class, webEnvironment = NONE)
+@ActiveProfiles("configmap")
+public class ConfigMapIntegrationTests {
+
+ @BeforeAll
+ static void init() {
+ createOrReplaceConfigMap("configmap/configmap.yaml");
+ }
+
+ @AfterAll
+ static void recover() {
+ deleteConfigMap("configmap/configmap-changed.yaml");
+ }
+
+ @Autowired
+ private Environment env;
+
+ @Test
+ void testNormal() throws InterruptedException {
+ assertThat(env.getProperty("username")).isEqualTo("admin");
+ assertThat(env.getProperty("password")).isEqualTo("666");
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(env.getProperty("hobbies[2]")).isNull();
+
+ // update configmap
+ createOrReplaceConfigMap("configmap/configmap-changed.yaml");
+
+ // context is refreshing
+ Thread.sleep(1000);
+
+ assertThat(env.getProperty("username")).isEqualTo("admin");
+ assertThat(env.getProperty("password")).isEqualTo("888");
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(env.getProperty("hobbies[2]")).isEqualTo("coding");
+
+ // delete configmap, refresh on delete is disabled by default
+ deleteConfigMap("configmap/configmap-changed.yaml");
+
+ // context is refreshing
+ Thread.sleep(1000);
+
+ assertThat(env.getProperty("username")).isEqualTo("admin");
+ assertThat(env.getProperty("password")).isEqualTo("888");
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(env.getProperty("hobbies[2]")).isEqualTo("coding");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/Empty.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/Empty.java
new file mode 100644
index 0000000000..5eafa4da6f
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/Empty.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.it;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Freeman
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableAutoConfiguration
+public class Empty {
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/MissingConfigIntegrationTests.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/MissingConfigIntegrationTests.java
new file mode 100644
index 0000000000..7f2df86028
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/MissingConfigIntegrationTests.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.it;
+
+import com.alibaba.cloud.kubernetes.config.KubernetesConfigProperties;
+import com.alibaba.cloud.kubernetes.config.exception.KubernetesConfigMissingException;
+import com.alibaba.cloud.kubernetes.config.testsupport.KubernetesAvailable;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/**
+ * @author Freeman
+ */
+@KubernetesAvailable
+public class MissingConfigIntegrationTests {
+ private static final String PROFILE = "missing-config";
+
+ @Test
+ void testEnabledFailOnMissingConfig() {
+ assertThatCode(() -> new SpringApplicationBuilder(Empty.class)
+ .web(WebApplicationType.NONE).profiles(PROFILE).run().close())
+ .isInstanceOf(KubernetesConfigMissingException.class);
+ }
+
+ @Test
+ void testDisabledFailOnMissingConfig() {
+ assertThatCode(() -> new SpringApplicationBuilder(Empty.class)
+ .web(WebApplicationType.NONE)
+ .properties(KubernetesConfigProperties.PREFIX
+ + ".fail-on-missing-config=false")
+ .profiles(PROFILE).run().close()).doesNotThrowAnyException();
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/NotRefreshableIntegrationTests.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/NotRefreshableIntegrationTests.java
new file mode 100644
index 0000000000..9ad7702925
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/NotRefreshableIntegrationTests.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.it;
+
+import com.alibaba.cloud.kubernetes.config.testsupport.KubernetesAvailable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.ActiveProfiles;
+
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.createOrReplaceConfigMap;
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.deleteConfigMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
+
+/**
+ * @author Freeman
+ */
+@KubernetesAvailable
+@SpringBootTest(classes = Empty.class, webEnvironment = NONE)
+@ActiveProfiles("not-refreshable")
+public class NotRefreshableIntegrationTests {
+
+ @BeforeAll
+ static void init() {
+ createOrReplaceConfigMap("not_refreshable/configmap-01.yaml");
+ createOrReplaceConfigMap("not_refreshable/configmap-02.yaml");
+ }
+
+ @AfterAll
+ static void recover() {
+ deleteConfigMap("not_refreshable/configmap-01-changed.yaml");
+ deleteConfigMap("not_refreshable/configmap-02-changed.yaml");
+ }
+
+ @Autowired
+ private Environment env;
+
+ @Test
+ void testNotRefreshable() throws InterruptedException {
+ assertThat(env.getProperty("username")).isEqualTo("admin");
+ assertThat(env.getProperty("password")).isEqualTo("666");
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(env.getProperty("hobbies[2]")).isNull();
+
+ // update configmap-01
+ createOrReplaceConfigMap("not_refreshable/configmap-01-changed.yaml");
+
+ // context is refreshing
+ Thread.sleep(1000);
+
+ assertThat(env.getProperty("username")).isEqualTo("admin");
+ assertThat(env.getProperty("password")).isEqualTo("888");
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(env.getProperty("hobbies[2]")).isNull();
+
+ // update configmap-02
+ createOrReplaceConfigMap("not_refreshable/configmap-02-changed.yaml");
+
+ // context is refreshing
+ Thread.sleep(1000);
+
+ assertThat(env.getProperty("username")).isEqualTo("admin");
+ assertThat(env.getProperty("password")).isEqualTo("888");
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isNotEqualTo("singing");
+ assertThat(env.getProperty("hobbies[2]")).isNull();
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/SecretIntegrationTests.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/SecretIntegrationTests.java
new file mode 100644
index 0000000000..672ae6df19
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/SecretIntegrationTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.it;
+
+import com.alibaba.cloud.kubernetes.config.testsupport.KubernetesAvailable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.ActiveProfiles;
+
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.createOrReplaceConfigMap;
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.createOrReplaceSecret;
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.deleteConfigMap;
+import static com.alibaba.cloud.kubernetes.config.testsupport.KubernetesTestUtil.deleteSecret;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
+
+/**
+ * @author Freeman
+ */
+@KubernetesAvailable
+@SpringBootTest(classes = Empty.class, webEnvironment = NONE)
+@ActiveProfiles("secret")
+public class SecretIntegrationTests {
+
+ @BeforeAll
+ static void init() {
+ createOrReplaceConfigMap("secret/configmap.yaml");
+ createOrReplaceSecret("secret/secret.yaml");
+ createOrReplaceSecret("secret/secret-refreshable.yaml");
+ }
+
+ @AfterAll
+ static void recover() {
+ deleteConfigMap("secret/configmap.yaml");
+ deleteSecret("secret/secret.yaml");
+ deleteSecret("secret/secret-refreshable.yaml");
+ }
+
+ @Autowired
+ private Environment env;
+
+ @Test
+ void testSecret() throws InterruptedException {
+ // secret win, configmap lose
+ assertThat(env.getProperty("username")).isNotEqualTo("admin");
+ assertThat(env.getProperty("username")).isNotEqualTo("cm9vdAo=");
+ assertThat(env.getProperty("username")).isEqualTo("root");
+ assertThat(env.getProperty("password")).isNotEqualTo("666");
+ assertThat(env.getProperty("password")).isNotEqualTo("MTEyMzIyMwo=");
+ assertThat(env.getProperty("password")).isEqualTo("1123223"); // MTEyMzIyMwo=
+
+ assertThat(env.getProperty("price")).isEqualTo("100");
+
+ assertThat(env.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(env.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(env.getProperty("hobbies[2]")).isNull();
+
+ createOrReplaceSecret("secret/secret-changed.yaml");
+ createOrReplaceSecret("secret/secret-refreshable-changed.yaml");
+
+ // make sure context is refreshed
+ Thread.sleep(1000);
+
+ // secret refresh is disabled by default
+ assertThat(env.getProperty("username")).isNotEqualTo("root2");
+ assertThat(env.getProperty("username")).isEqualTo("root");
+
+ // test refreshable secret
+ assertThat(env.getProperty("price")).isEqualTo("200");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/package-info.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/package-info.java
new file mode 100644
index 0000000000..2592dbe178
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/it/package-info.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 `it` is shorthand for integration testing.
+ *
+ *
+ * The tests under this package will detect whether there is a Kubernetes environment, if
+ * there is, the tests will be executed, and if not, the tests will be skipped.
+ *
+ *
+ * How to run integration tests:
+ *
+ * You must have a Kubernetes cluster, and the current-context in ~/.kube/config has
+ * access rights to ConfigMap and Secret. Then you can run the following command:
+ *
+ * ./mvnw clean test -pl com.alibaba.cloud:spring-cloud-starter-alibaba-kubernetes-config
+ * -am
+ *
+ */
+package com.alibaba.cloud.kubernetes.config.it;
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/JsonFileProcessorTest.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/JsonFileProcessorTest.java
new file mode 100644
index 0000000000..7f6288ef17
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/JsonFileProcessorTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.env.EnumerablePropertySource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link JsonFileProcessor} tester.
+ */
+class JsonFileProcessorTest {
+
+ /**
+ * {@link JsonFileProcessor#generate(String, String)}.
+ */
+ @Test
+ void generate_whenJsonObject() {
+ // @checkstyle:off
+ String json = "{\n" +
+ " \"username\": \"admin\",\n" +
+ " \"password\": \"666\",\n" +
+ " \"hobbies\": [\n" +
+ " \"reading\",\n" +
+ " \"writing\"\n" +
+ " ]\n" +
+ "}";
+ // @checkstyle:on
+
+ EnumerablePropertySource> ps = new JsonFileProcessor().generate("test_generate",
+ json);
+
+ assertThat(ps.getPropertyNames()).hasSize(4);
+ assertThat(ps.getProperty("username")).isEqualTo("admin");
+ assertThat(ps.getProperty("password")).isEqualTo("666");
+ assertThat(ps.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(ps.getProperty("hobbies[1]")).isEqualTo("writing");
+ }
+
+ /**
+ * {@link JsonFileProcessor#generate(String, String)}.
+ */
+ @Test
+ void generate_whenJsonArray() {
+ // @checkstyle:off
+ String jsonArray = "[\n" +
+ " {\n" +
+ " \"username\": \"admin\",\n" +
+ " \"password\": \"666\",\n" +
+ " \"hobbies\": [\n" +
+ " \"reading\",\n" +
+ " \"writing\"\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"username\": \"root\",\n" +
+ " \"password\": \"888\",\n" +
+ " \"hobbies\": [\n" +
+ " \"reading\",\n" +
+ " \"writing\",\n" +
+ " \"coding\"\n" +
+ " ]\n" +
+ " }\n" +
+ "]";
+ // @checkstyle:on
+
+ EnumerablePropertySource> ps = new JsonFileProcessor().generate("test_generate",
+ jsonArray);
+
+ // previous config wins
+ assertThat(ps.getPropertyNames()).hasSize(5);
+ assertThat(ps.getProperty("username")).isEqualTo("admin");
+ assertThat(ps.getProperty("password")).isEqualTo("666");
+ assertThat(ps.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(ps.getProperty("hobbies[1]")).isEqualTo("writing");
+ assertThat(ps.getProperty("hobbies[2]")).isEqualTo("coding");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/PropertiesFileProcessorTest.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/PropertiesFileProcessorTest.java
new file mode 100644
index 0000000000..08b42ac66d
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/PropertiesFileProcessorTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.env.EnumerablePropertySource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link PropertiesFileProcessor} tester.
+ */
+class PropertiesFileProcessorTest {
+
+ /**
+ * {@link PropertiesFileProcessor#generate(String, String)}.
+ */
+ @Test
+ void generate() {
+ // @checkstyle:off
+ String properties = "username=admin\n" +
+ "password=666\n" +
+ "hobbies[0]=reading\n" +
+ "hobbies[1]=writing\n";
+ // @checkstyle:on
+
+ EnumerablePropertySource> ps = new PropertiesFileProcessor()
+ .generate("test_generate", properties);
+
+ assertThat(ps.getPropertyNames()).hasSize(4);
+ assertThat(ps.getProperty("username")).isEqualTo("admin");
+ assertThat(ps.getProperty("password")).isEqualTo("666");
+ assertThat(ps.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(ps.getProperty("hobbies[1]")).isEqualTo("writing");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/YamlFileProcessorTest.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/YamlFileProcessorTest.java
new file mode 100644
index 0000000000..4bc03b09bf
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/processor/YamlFileProcessorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.processor;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.env.EnumerablePropertySource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link YamlFileProcessor} tester.
+ */
+class YamlFileProcessorTest {
+
+ /**
+ * {@link YamlFileProcessor#generate(String, String)}.
+ */
+ @Test
+ void generate_whenSingleDocument() {
+ // @checkstyle:off
+ String yaml = "username: admin\n" +
+ "password: \"666\"\n" +
+ "hobbies:\n" +
+ " - reading\n" +
+ " - writing\n";
+ // @checkstyle:on
+
+ EnumerablePropertySource> ps = new YamlFileProcessor().generate("test_generate",
+ yaml);
+ assertThat(ps.getPropertyNames()).hasSize(4);
+ assertThat(ps.getProperty("username")).isEqualTo("admin");
+ assertThat(ps.getProperty("password")).isEqualTo("666");
+ assertThat(ps.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(ps.getProperty("hobbies[1]")).isEqualTo("writing");
+ }
+
+ /**
+ * {@link YamlFileProcessor#generate(String, String)}.
+ */
+ @Test
+ void generate_whenMultipleDocuments() {
+ // @checkstyle:off
+ String yaml = "username: admin\n" +
+ "password: \"666\"\n" +
+ "hobbies:\n" +
+ " - reading\n" +
+ " - writing\n" +
+ "---\n" +
+ "username: adminn\n" +
+ "password: \"6666\"\n" +
+ "hobbies:\n" +
+ " - readingg\n" +
+ " - writingg\n";
+ // @checkstyle:on
+
+ EnumerablePropertySource> ps = new YamlFileProcessor().generate("test_generate",
+ yaml);
+ assertThat(ps.getPropertyNames()).hasSize(4);
+ // first document 'win'
+ assertThat(ps.getProperty("username")).isEqualTo("admin");
+ assertThat(ps.getProperty("password")).isEqualTo("666");
+ assertThat(ps.getProperty("hobbies[0]")).isEqualTo("reading");
+ assertThat(ps.getProperty("hobbies[1]")).isEqualTo("writing");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesAvailable.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesAvailable.java
new file mode 100644
index 0000000000..c611e0a75a
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesAvailable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.testsupport;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Disables test execution if Kubernetes is unavailable.
+ *
+ * @author Freeman
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ExtendWith(KubernetesAvailableCondition.class)
+public @interface KubernetesAvailable {
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesAvailableCondition.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesAvailableCondition.java
new file mode 100644
index 0000000000..5fafd44e54
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesAvailableCondition.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.testsupport;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesUtils;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * @author Freeman
+ */
+public class KubernetesAvailableCondition implements ExecutionCondition {
+
+ @Override
+ public ConditionEvaluationResult evaluateExecutionCondition(
+ ExtensionContext extensionContext) {
+ try {
+ Config config = KubernetesUtils.config();
+ Assert.notEmpty(config.getContexts(),
+ "No contexts found in kubernetes config");
+ try (KubernetesClient client = KubernetesUtils.newKubernetesClient()) {
+ client.configMaps().inNamespace(KubernetesUtils.currentNamespace())
+ .list();
+ }
+ }
+ catch (Throwable e) {
+ return ConditionEvaluationResult.disabled("Kubernetes unavailable");
+ }
+ return ConditionEvaluationResult.enabled("Kubernetes available");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesTestUtil.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesTestUtil.java
new file mode 100644
index 0000000000..b4462b9ce9
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/testsupport/KubernetesTestUtil.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.testsupport;
+
+import java.io.IOException;
+
+import com.alibaba.cloud.kubernetes.commons.KubernetesUtils;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+import org.springframework.core.io.ClassPathResource;
+
+/**
+ * @author Freeman
+ */
+public final class KubernetesTestUtil {
+
+ private KubernetesTestUtil() {
+ throw new UnsupportedOperationException(
+ "No KubernetesTestUtil instances for you!");
+ }
+
+ static KubernetesClient cli = KubernetesUtils.newKubernetesClient();
+
+ public static ConfigMap configMap(String classpathFile) {
+ try {
+ return cli.configMaps().load(new ClassPathResource(classpathFile).getURL())
+ .get();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Secret secret(String classpathFile) {
+ try {
+ return cli.secrets().load(new ClassPathResource(classpathFile).getURL())
+ .get();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ConfigMap createOrReplaceConfigMap(String classpathFile) {
+ return cli.resource(configMap(classpathFile)).createOrReplace();
+ }
+
+ public static void deleteConfigMap(String classpathFile) {
+ cli.resource(configMap(classpathFile)).delete();
+ }
+
+ public static Secret createOrReplaceSecret(String classpathFile) {
+ return cli.resource(secret(classpathFile)).createOrReplace();
+ }
+
+ public static void deleteSecret(String classpathFile) {
+ cli.resource(secret(classpathFile)).delete();
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/util/ConvertersTest.java b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/util/ConvertersTest.java
new file mode 100644
index 0000000000..f915b18f1d
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/java/com/alibaba/cloud/kubernetes/config/util/ConvertersTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013-2023 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alibaba.cloud.kubernetes.config.util;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link Converters} tester.
+ */
+class ConvertersTest {
+
+ /**
+ * {@link Converters#stripTrailing(String)}.
+ */
+ @Test
+ void stripTrailing() {
+ assertThat(Converters.stripTrailing("abc \n")).isEqualTo("abc");
+ assertThat(Converters.stripTrailing("abc \r\t\f")).isEqualTo("abc");
+ assertThat(Converters.stripTrailing(" abc ")).isEqualTo(" abc");
+ }
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-configmap.yml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-configmap.yml
new file mode 100644
index 0000000000..5a82eebeed
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-configmap.yml
@@ -0,0 +1,17 @@
+spring:
+ cloud:
+ alibaba-kubernetes:
+ config:
+ namespace: default
+ configmaps:
+ - name: my-configmap
+ refreshable: true
+ application:
+ name: configmap
+logging:
+ level:
+ com.alibaba.kubernetes.config: debug
+
+username: freeman
+hobbies:
+ - coding
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-missing-config.yml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-missing-config.yml
new file mode 100644
index 0000000000..61f7b44025
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-missing-config.yml
@@ -0,0 +1,12 @@
+spring:
+ application:
+ name: missing-config
+ cloud:
+ alibaba-kubernetes:
+ config:
+ namespace: default
+ configmaps:
+ - name: missing-configmap
+logging:
+ level:
+ com.alibaba.kubernetes.config: debug
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-not-refreshable.yml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-not-refreshable.yml
new file mode 100644
index 0000000000..1b47931ec1
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-not-refreshable.yml
@@ -0,0 +1,15 @@
+spring:
+ cloud:
+ alibaba-kubernetes:
+ config:
+ namespace: default
+ configmaps:
+ - name: configmap-01
+ refreshable: true
+ - name: configmap-02
+ refreshable: false
+ application:
+ name: not-refreshable
+logging:
+ level:
+ com.alibaba.kubernetes.config: debug
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-secret.yml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-secret.yml
new file mode 100644
index 0000000000..47fdf70bb4
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/application-secret.yml
@@ -0,0 +1,16 @@
+spring:
+ cloud:
+ alibaba-kubernetes:
+ config:
+ config-maps:
+ - name: secret-configmap-01
+ secrets:
+ - name: secret-secret-01
+ refreshable: false
+ - name: secret-secret-02
+ refreshable: true
+ application:
+ name: secret
+logging:
+ level:
+ com.alibaba.kubernetes.config: debug
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/configmap/configmap-changed.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/configmap/configmap-changed.yaml
new file mode 100644
index 0000000000..5598db9d98
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/configmap/configmap-changed.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: my-configmap
+ namespace: default
+data:
+ application-default.yml: |
+ username: admin
+ password: 888
+ hobbies:
+ - reading
+ - writing
+ - coding
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/configmap/configmap.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/configmap/configmap.yaml
new file mode 100644
index 0000000000..312f4bb2d8
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/configmap/configmap.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: my-configmap
+ namespace: default
+data:
+ application-default.yml: |
+ username: admin
+ password: 666
+ hobbies:
+ - reading
+ - writing
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-01-changed.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-01-changed.yaml
new file mode 100644
index 0000000000..f88047c646
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-01-changed.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: configmap-01
+ namespace: default
+data:
+ application-default.yml: |
+ username: admin
+ password: 888
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-01.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-01.yaml
new file mode 100644
index 0000000000..052e59b181
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-01.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: configmap-01
+ namespace: default
+data:
+ application-default.yml: |
+ username: admin
+ password: 666
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-02-changed.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-02-changed.yaml
new file mode 100644
index 0000000000..730b1ded87
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-02-changed.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: configmap-02
+ namespace: default
+data:
+ application-default.yml: |
+ hobbies:
+ - reading
+ - singing
+ - coding
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-02.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-02.yaml
new file mode 100644
index 0000000000..9756694cf7
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/not_refreshable/configmap-02.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: configmap-02
+ namespace: default
+data:
+ application-default.yml: |
+ hobbies:
+ - reading
+ - writing
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/configmap.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/configmap.yaml
new file mode 100644
index 0000000000..01c1d48ef2
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/configmap.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: secret-configmap-01
+ namespace: default
+data:
+ application-default.yml: |
+ username: admin
+ password: 666
+ hobbies:
+ - reading
+ - writing
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-changed.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-changed.yaml
new file mode 100644
index 0000000000..7841b70dcc
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-changed.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: secret-secret-01
+ namespace: default
+data:
+ username: cm9vdDIK # root2
+ password: MTEyMzIyMwo=
+type: Opaque
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-refreshable-changed.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-refreshable-changed.yaml
new file mode 100644
index 0000000000..d4f7717429
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-refreshable-changed.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: secret-secret-02
+ namespace: default
+data:
+ price: MjAwCg== # 200
+type: Opaque
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-refreshable.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-refreshable.yaml
new file mode 100644
index 0000000000..55b8ea251e
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret-refreshable.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: secret-secret-02
+ namespace: default
+data:
+ price: MTAwCg== # 100
+type: Opaque
\ No newline at end of file
diff --git a/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret.yaml b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret.yaml
new file mode 100644
index 0000000000..85c6076b7e
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-alibaba-kubernetes/spring-cloud-starter-alibaba-kubernetes-config/src/test/resources/secret/secret.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: secret-secret-01
+ namespace: default
+data:
+ username: cm9vdAo= # root
+ password: MTEyMzIyMwo=
+type: Opaque
\ No newline at end of file