Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import io.kubernetes.client.openapi.apis.CoreV1Api;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand Down Expand Up @@ -57,13 +62,26 @@
@EnableConfigurationProperties(KubernetesConfigServerProperties.class)
public class KubernetesConfigServerAutoConfiguration {

private static final Logger LOG = Logger.getLogger(KubernetesConfigServerAutoConfiguration.class.getName());

@Bean
@ConditionalOnBean(KubernetesEnvironmentRepository.class)
@ConditionalOnMissingBean
public KubernetesEnvironmentRepositoryFactory kubernetesEnvironmentRepositoryFactory(
ObjectProvider<KubernetesEnvironmentRepository> kubernetesEnvironmentRepositoryProvider) {
LOG.info("Creating KubernetesEnvironmentRepositoryFactory bean...");
return new KubernetesEnvironmentRepositoryFactory(kubernetesEnvironmentRepositoryProvider);
}

@Bean
@Profile("kubernetes")
@ConditionalOnMissingBean
public EnvironmentRepository kubernetesEnvironmentRepository(CoreV1Api coreV1Api,
List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers,
KubernetesNamespaceProvider kubernetesNamespaceProvider) {
List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers,
KubernetesNamespaceProvider kubernetesNamespaceProvider) {
LOG.info("Creating KubernetesEnvironmentRepository bean...");
return new KubernetesEnvironmentRepository(coreV1Api, kubernetesPropertySourceSuppliers,
kubernetesNamespaceProvider.getNamespace());
kubernetesNamespaceProvider.getNamespace());
}

@Bean
Expand Down Expand Up @@ -92,20 +110,28 @@ public KubernetesPropertySourceSupplier configMapPropertySourceSupplier(
@ConditionalOnKubernetesSecretsEnabled
@ConditionalOnProperty("spring.cloud.kubernetes.secrets.enableApi")
public KubernetesPropertySourceSupplier secretsPropertySourceSupplier(KubernetesConfigServerProperties properties) {
LOG.info("Creating secretsPropertySourceSupplier bean...");
return (coreApi, applicationName, namespace, springEnv) -> {
List<String> namespaces = namespaceSplitter(properties.getSecretsNamespaces(), namespace);
List<MapPropertySource> propertySources = new ArrayList<>();

LOG.info("Processing namespaces for secrets: " + namespaces);

namespaces.forEach(space -> {
LOG.info("Fetching secrets for namespace: " + space);
NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false,
ConfigUtils.Prefix.DEFAULT, true, true);
ConfigUtils.Prefix.DEFAULT, true, true);
KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space,
springEnv, false);
propertySources.add(new KubernetesClientSecretsPropertySource(context));
springEnv, false);
MapPropertySource propertySource = new KubernetesClientSecretsPropertySource(context);
propertySources.add(propertySource);
LOG.info("Added property source for namespace: " + space);
});

LOG.info("Total property sources created: " + propertySources.size());
return propertySources;
};
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
package org.springframework.cloud.kubernetes.configserver;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;

/**
* @author Ryan Baxter
*/

@ConfigurationProperties("spring.cloud.kubernetes.configserver")
public class KubernetesConfigServerProperties {
public class KubernetesConfigServerProperties implements EnvironmentRepositoryProperties {

private int order = DEFAULT_ORDER;

private String configMapNamespaces = "";

Expand All @@ -45,4 +48,12 @@ public void setSecretsNamespaces(String secretsNamespaces) {
this.secretsNamespaces = secretsNamespaces;
}

public int getOrder() {
return this.order;
}

public void setOrder(int order) {
this.order = order;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@

package org.springframework.cloud.kubernetes.configserver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;

import io.kubernetes.client.openapi.apis.CoreV1Api;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
Expand All @@ -46,11 +44,14 @@ public class KubernetesEnvironmentRepository implements EnvironmentRepository {

private final String namespace;


public KubernetesEnvironmentRepository(CoreV1Api coreApi,
List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers, String namespace) {
List<KubernetesPropertySourceSupplier> kubernetesPropertySourceSuppliers,
String namespace) {
this.coreApi = coreApi;
this.kubernetesPropertySourceSuppliers = kubernetesPropertySourceSuppliers;
this.namespace = namespace;
LOG.info("Initialized KubernetesEnvironmentRepository with namespace: " + namespace);
}

@Override
Expand All @@ -60,62 +61,77 @@ public Environment findOne(String application, String profile, String label) {

@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
LOG.info("Finding environment for application: " + application + ", profile: " + profile + ", label: " + label);

if (!StringUtils.hasText(profile)) {
profile = "default";
LOG.debug("No profile provided, using default profile");
}
List<String> profiles = new ArrayList<>(List.of(StringUtils.commaDelimitedListToStringArray(profile)));

List<String> profiles = new ArrayList<>(List.of(StringUtils.commaDelimitedListToStringArray(profile)));
Collections.reverse(profiles);

if (!profiles.contains("default")) {
profiles.add("default");
LOG.debug("Added 'default' profile to the list of active profiles");
}
Environment environment = new Environment(application, profiles.toArray(profiles.toArray(new String[0])), label,
null, null);
LOG.info("Profiles: " + profile);
LOG.info("Application: " + application);
LOG.info("Label: " + label);

Environment environment = new Environment(application, profiles.toArray(new String[0]), label, null, null);
LOG.info("Created Environment with profiles: " + StringUtils.collectionToCommaDelimitedString(profiles));

for (String activeProfile : profiles) {
try {
// This is needed so that when we get the application name in
// SourceDataProcessor.sorted that it actually
// exists in the Environment
StandardEnvironment springEnv = new KubernetesConfigServerEnvironment(
createPropertySources(application));
LOG.debug("Processing profile: " + activeProfile);
StandardEnvironment springEnv = new KubernetesConfigServerEnvironment(createPropertySources(application));
springEnv.setActiveProfiles(activeProfile);
LOG.debug("Set active profile in Spring Environment: " + activeProfile);

if (!"application".equalsIgnoreCase(application)) {
LOG.debug("Adding application configuration for: " + application);
addApplicationConfiguration(environment, springEnv, application);
}
}
catch (Exception e) {
LOG.warn(e);
} catch (Exception e) {
LOG.warn("Error processing profile: " + activeProfile, e);
}
}

LOG.debug("Adding default application configuration for 'application'");
StandardEnvironment springEnv = new KubernetesConfigServerEnvironment(createPropertySources("application"));
addApplicationConfiguration(environment, springEnv, "application");

LOG.info("Final Environment: " + environment);
return environment;
}

private MutablePropertySources createPropertySources(String application) {
LOG.debug("Creating property sources for application: " + application);
Map<String, Object> applicationProperties = Map.of("spring.application.name", application);
MapPropertySource propertySource = new MapPropertySource("kubernetes-config-server", applicationProperties);
MutablePropertySources mutablePropertySources = new MutablePropertySources();
mutablePropertySources.addFirst(propertySource);
LOG.debug("Added property source: " + propertySource.getName());
return mutablePropertySources;
}

private void addApplicationConfiguration(Environment environment, StandardEnvironment springEnv,
String applicationName) {
String applicationName) {
LOG.debug("Adding application configuration for: " + applicationName);

kubernetesPropertySourceSuppliers.forEach(supplier -> {
LOG.debug("Processing property source supplier: " + supplier.getClass().getSimpleName());
List<MapPropertySource> propertySources = supplier.get(coreApi, applicationName, namespace, springEnv);
LOG.debug("Found " + propertySources.size() + " property sources for application: " + applicationName);

propertySources.forEach(propertySource -> {
if (propertySource.getPropertyNames().length > 0) {
LOG.debug("Adding PropertySource " + propertySource.getName());
LOG.debug("Adding PropertySource: " + propertySource.getName());
LOG.debug("PropertySource Names: "
+ StringUtils.arrayToCommaDelimitedString(propertySource.getPropertyNames()));
+ StringUtils.arrayToCommaDelimitedString(propertySource.getPropertyNames()));
environment.add(new PropertySource(propertySource.getName(), propertySource.getSource()));
} else {
LOG.debug("Skipping empty PropertySource: " + propertySource.getName());
}
});
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2013-2021 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 org.springframework.cloud.kubernetes.configserver;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.config.server.environment.EnvironmentRepositoryFactory;

/**
* Factory class for creating instances of {@link KubernetesEnvironmentRepository}.
*/
public class KubernetesEnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<KubernetesEnvironmentRepository, KubernetesConfigServerProperties> {

private final ObjectProvider<KubernetesEnvironmentRepository> kubernetesEnvironmentRepositoryProvider;

public KubernetesEnvironmentRepositoryFactory(
ObjectProvider<KubernetesEnvironmentRepository> kubernetesEnvironmentRepositoryProvider) {
this.kubernetesEnvironmentRepositoryProvider = kubernetesEnvironmentRepositoryProvider;
}

@Override
public KubernetesEnvironmentRepository build(KubernetesConfigServerProperties environmentProperties) {
return kubernetesEnvironmentRepositoryProvider.getIfAvailable();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2013-2022 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 org.springframework.cloud.kubernetes.configserver;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.config.server.environment.JGitEnvironmentRepository;
import org.springframework.context.ConfigurableApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class },
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.profiles.include=kubernetes",
"spring.cloud.kubernetes.client.namespace=default", "spring.profiles.active=composite",
"spring.cloud.config.server.composite[0].type=git",
"spring.cloud.config.server.composite[0].uri=https://github.com/spring-cloud-samples/config-repo",
"spring.cloud.config.server.composite[1].type=kubernetes",
"spring.cloud.config.server.composite[1].config-map-namespace=default",
"spring.cloud.config.server.composite[1].secrets-namespace=default" })
class CompositeProfileWithGitAndKubernetesConfigSourcesTests {

@Autowired
private ConfigurableApplicationContext context;

@Test
void kubernetesEnvironmentRepositoryIsLoaded() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(2);
}

@Test
void kubernetesPropertySourceSuppliersAreLoaded() {
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).isNotEmpty();
}

@Test
void gitEnvironmentRepositoryIsLoaded() {
assertThat(context.getBeanNamesForType(JGitEnvironmentRepository.class)).hasSize(1);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2013-2022 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 org.springframework.cloud.kubernetes.configserver;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { KubernetesConfigServerApplication.class },
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.profiles.include=kubernetes",
"spring.cloud.kubernetes.client.namespace=default", "spring.profiles.active=composite",
"spring.cloud.config.server.composite[0].type=kubernetes",
"spring.cloud.config.server.composite[0].config-map-namespace=default",
"spring.cloud.config.server.composite[0].secrets-namespace=default",
"spring.cloud.config.server.composite[1].type=kubernetes",
"spring.cloud.config.server.composite[1].config-map-namespace=another-namespace",
"spring.cloud.config.server.composite[1].secrets-namespace=another-namespace" })
class CompositeProfileWithMultipleKubernetesConfigSourcesTests {

@Autowired
private ConfigurableApplicationContext context;

@Test
void kubernetesEnvironmentRepositoriesAreLoaded() {
assertThat(context.getBeanNamesForType(KubernetesEnvironmentRepository.class)).hasSize(3);
}

@Test
void kubernetesPropertySourceSuppliersAreLoaded() {
assertThat(context.getBeanNamesForType(KubernetesPropertySourceSupplier.class)).isNotEmpty();
}

}
Loading