Initial commit

This commit is contained in:
Marcin Grzejszczak
2023-09-08 16:55:47 +02:00
parent d89786c54f
commit d00a374ca5
835 changed files with 1764 additions and 58548 deletions

View File

@@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>4.0.0-SNAPSHOT</version>
<name>spring-cloud-config-server</name>
<description>spring-cloud-config-server</description>
<url>https://projects.spring.io/spring-cloud/spring-cloud-config-server/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<comments>Copyright 2014-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.</comments>
</license>
</licenses>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>sgibb</id>
<name>Spencer Gibb</name>
<email>sgibb at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>mgrzejszczak</id>
<name>Marcin Grzejszczak</name>
<email>mgrzejszczak at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>rbaxter</id>
<name>Ryan Baxter</name>
<email>rbaxter at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>omaciaszeksharma</id>
<name>Olga Maciaszek-Sharma</name>
<email>omaciaszeksharma at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-config.git/spring-cloud-config-server</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-config.git/spring-cloud-config-server</developerConnection>
<url>https://github.com/spring-cloud/spring-cloud-config/spring-cloud-config-server</url>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
<version>4.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>1.10.0-RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>6.0.0-RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<version>1.0.11.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
<version>3.0.0-RC1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.credhub</groupId>
<artifactId>spring-credhub-core</artifactId>
<version>2.1.1.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>6.2.0.202206071550-r</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.apache</artifactId>
<version>6.2.0.202206071550-r</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ssh.apache</artifactId>
<version>6.2.0.202206071550-r</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.14.0-rc2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.10.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>auth</artifactId>
<version>2.17.195</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.17.195</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-core</artifactId>
<version>2.17.195</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>secretsmanager</artifactId>
<version>2.17.195</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ssm</artifactId>
<version>2.17.195</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-iam</artifactId>
<version>v1-rev20201112-1.30.10</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-secretmanager</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-cloudresourcemanager</artifactId>
<version>v1-rev20200210-1.30.9</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.22.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -1,264 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-config-server</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-config-server</name>
<description>spring-cloud-config-server</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.credhub</groupId>
<artifactId>spring-credhub-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.apache</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ssh.apache</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>auth</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>secretsmanager</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ssm</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-iam</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-secretmanager</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-cloudresourcemanager</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.junit.http</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<scope>test</scope>
</dependency>
<!-- AWS SDK v1 is required by testcontainers-localstack -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<version>1.12.287</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-tests</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-urlconnection</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-s3</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>org.springframework.cloud.config.server.ConfigServerApplication
</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2018-2019 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.config.server;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
/**
* Configuration server application.
*
* @author Dave Syer
* @deprecated Will be removed in 4.0.0.
*/
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableConfigServer
@Deprecated
public class ConfigServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApplication.class).properties("spring.config.name=configserver")
.run(args);
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2013-2019 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.config.server;
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.springframework.cloud.config.server.config.ConfigServerConfiguration;
import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2014-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.config.server.bootstrap;
import java.util.Collections;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
/**
* Normally you don't want the config server to be a config client itself, so this
* listener disables the config client unless <code>spring.cloud.config.enabled</code> is
* explicitly "true". It has to be "true" at the time this listener is fired, which means
* <b>before</b> the <code>bootstrap.yml</code> is parsed, which in turn means to you need
* to launch the application with an existing primed {@link Environment} (e.g. via System
* properties or a {@link SpringApplicationBuilder}). This is the same rule of precedence
* as for anything else affecting the bootstrap process itself, e.g. setting
* <code>spring.cloud.bootstrap.name</code> to something other than "bootstrap".
* <p>
* N.B. a config server can always be an "embedded" config client (using its own config
* repository as a property source) if you set
* <code>spring.cloud.config.server.bootstrap=true</code> in <code>bootstrap.yml</code>.
* This listener is only to prevent it from using HTTP to contact itself.
*
* @author Dave Syer
* @author Ryan Baxter
*
*/
// TODO We changed this class from an ApplicationListener to an EnvironmentPostProcessor
// but did not change the class name to avoid breaking anyone in a minor release.
// In the next major it will be safe to change the class name and remove the
// ApplicationListener interface.
public class ConfigServerBootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, EnvironmentPostProcessor, Ordered {
/**
* Default order of the bootstrap application listener.
*/
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 4;
private int order = DEFAULT_ORDER;
private PropertySource<?> propertySource = new MapPropertySource("configServerClient",
Collections.<String, Object>singletonMap("spring.cloud.config.enabled", "false"));
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (!environment.resolvePlaceholders("${spring.cloud.config.enabled:false}").equalsIgnoreCase("true")) {
if (!environment.getPropertySources().contains(this.propertySource.getName())) {
environment.getPropertySources().addLast(this.propertySource);
}
}
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.bootstrap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.config.server.config.EnvironmentRepositoryConfiguration;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentRepositoryPropertySourceLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
/**
* Bootstrap configuration to fetch external configuration from a (possibly remote)
* {@link EnvironmentRepository}. Off by default because it can delay startup, but can be
* enabled with <code>spring.cloud.config.server.bootstrap=true</code>. This would be
* useful, for example, if the config server were embedded in another app that wanted to
* be configured from the same repository as all the other clients.
*
* @author Dave Syer
* @author Roy Clarkson
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("spring.cloud.config.server.bootstrap")
public class ConfigServerBootstrapConfiguration {
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class })
protected static class LocalPropertySourceLocatorConfiguration {
@Autowired
private EnvironmentRepository repository;
@Autowired
private ConfigClientProperties client;
@Autowired
private ConfigServerProperties server;
@Bean
public EnvironmentRepositoryPropertySourceLocator environmentRepositoryPropertySourceLocator() {
return new EnvironmentRepositoryPropertySourceLocator(this.repository, this.client.getName(),
this.client.getProfile(), getDefaultLabel());
}
private String getDefaultLabel() {
if (StringUtils.hasText(this.client.getLabel())) {
return this.client.getLabel();
}
else if (StringUtils.hasText(this.server.getDefaultLabel())) {
return this.server.getDefaultLabel();
}
return null;
}
}
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright 2018-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.config.server.bootstrap;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.config.server.environment.EnvironmentController;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("spring.cloud.config.server.bootstrap")
public class ConfigServerBootstrapOverridesAutoConfiguration {
@Bean
ConfigServerBootstrapOverridesLifecycle configServerBootstrapOverridesLifecycle(ApplicationContext context) {
return new ConfigServerBootstrapOverridesLifecycle(context);
}
/**
* SmartLifecycle that refreshes and rebinds beans needed for
* spring.cloud.config.server.overrides to work.
*/
private static class ConfigServerBootstrapOverridesLifecycle implements SmartLifecycle {
private final ApplicationContext context;
private volatile boolean running;
ConfigServerBootstrapOverridesLifecycle(ApplicationContext context) {
this.context = context;
}
@Override
public void start() {
this.running = true;
ConfigurationPropertiesRebinder rebinder = context.getBean(ConfigurationPropertiesRebinder.class);
rebinder.rebind(ConfigServerProperties.class);
RefreshScope refreshScope = context.getBean(RefreshScope.class);
refreshScope.refresh(EnvironmentController.class);
}
@Override
public void stop() {
this.running = false;
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int getPhase() {
// Run before WebServerStartStopLifecycle
return WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_PHASE - 3072;
}
}
}

View File

@@ -1,84 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.composite;
import java.lang.reflect.Type;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.env.Environment;
/**
* A {@link BeanFactoryPostProcessor} to register {@link EnvironmentRepository}
* {@link BeanDefinition}s based on the composite list configuration.
*
* @author Dylan Roberts
*/
public class CompositeEnvironmentBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private Environment environment;
public CompositeEnvironmentBeanFactoryPostProcessor(Environment environment) {
this.environment = environment;
}
@SuppressWarnings("unchecked")
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<String> typePropertyList = CompositeUtils.getCompositeTypeList(this.environment);
for (int i = 0; i < typePropertyList.size(); i++) {
String type = typePropertyList.get(i);
String factoryName = CompositeUtils.getFactoryName(type, beanFactory);
Type[] factoryTypes = CompositeUtils.getEnvironmentRepositoryFactoryTypeParams(beanFactory, factoryName);
Class<? extends EnvironmentRepositoryProperties> propertiesClass;
propertiesClass = (Class<? extends EnvironmentRepositoryProperties>) factoryTypes[1];
EnvironmentRepositoryProperties properties = bindProperties(i, propertiesClass, this.environment);
AbstractBeanDefinition propertiesDefinition = BeanDefinitionBuilder
.genericBeanDefinition(EnvironmentRepositoryProperties.class, () -> properties).getBeanDefinition();
String propertiesBeanName = String.format("%s-env-repo-properties%d", type, i);
registry.registerBeanDefinition(propertiesBeanName, propertiesDefinition);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(EnvironmentRepository.class).setFactoryMethodOnBean("build", factoryName)
.addConstructorArgValue(properties).getBeanDefinition();
String beanName = String.format("%s-env-repo%d", type, i);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
private <P extends EnvironmentRepositoryProperties> P bindProperties(int index, Class<P> propertiesClass,
Environment environment) {
Binder binder = Binder.get(environment);
String environmentConfigurationPropertyName = String.format("spring.cloud.config.server.composite[%d]", index);
P properties = binder.bindOrCreate(environmentConfigurationPropertyName, propertiesClass);
properties.setOrder(index + 1);
return properties;
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.composite;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.config.server.environment.EnvironmentRepositoryFactory;
import org.springframework.core.env.Environment;
import org.springframework.core.type.MethodMetadata;
/**
* @author Dylan Roberts
*/
public final class CompositeUtils {
private CompositeUtils() {
throw new IllegalStateException("Can't instantiate a utility class");
}
/**
* Returns list of values of the `type` field from the
* `spring.cloud.config.server.composite` collection.
* @param environment Spring Environment
* @return list of matching types
*/
public static List<String> getCompositeTypeList(Environment environment) {
return Binder.get(environment).bind("spring.cloud.config.server", CompositeConfig.class).get().getComposite()
.stream().map(map -> (String) map.get("type")).collect(Collectors.toList());
}
/**
* Given a type of EnvironmentRepository (git, svn, native, etc...) returns the name
* of the factory bean. See {@link #getCompositeTypeList(Environment)}
* @param type type of a repository
* @param beanFactory Spring Bean Factory
* @return name of the factory bean
*/
public static String getFactoryName(String type, ConfigurableListableBeanFactory beanFactory) {
String[] factoryNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory,
EnvironmentRepositoryFactory.class, true, false);
return Arrays.stream(factoryNames).filter(n -> n.startsWith(type)).findFirst().orElse(null);
}
/**
* Given a Factory Name return the generic type parameters of the factory (The actual
* repository class, and its properties class).
* @param beanFactory Spring Bean Factory
* @param factoryName name of the factory
* @return generic type params of the factory
*/
public static Type[] getEnvironmentRepositoryFactoryTypeParams(ConfigurableListableBeanFactory beanFactory,
String factoryName) {
MethodMetadata methodMetadata = (MethodMetadata) beanFactory.getBeanDefinition(factoryName).getSource();
Class<?> factoryClass = null;
try {
factoryClass = Class.forName(methodMetadata.getReturnTypeName());
}
catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
Optional<AnnotatedType> annotatedFactoryType = Arrays.stream(factoryClass.getAnnotatedInterfaces())
.filter(i -> {
ParameterizedType parameterizedType = (ParameterizedType) i.getType();
return parameterizedType.getRawType().equals(EnvironmentRepositoryFactory.class);
}).findFirst();
ParameterizedType factoryParameterizedType = (ParameterizedType) annotatedFactoryType
.orElse(factoryClass.getAnnotatedSuperclass()).getType();
return factoryParameterizedType.getActualTypeArguments();
}
static class CompositeConfig {
List<Map<String, Object>> composite;
public List<Map<String, Object>> getComposite() {
return this.composite;
}
public void setComposite(List<Map<String, Object>> composite) {
this.composite = composite;
}
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.composite;
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.springframework.context.annotation.Conditional;
/**
* @author Dylan Roberts
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSearchPathLocatorPresent.class)
public @interface ConditionalOnMissingSearchPathLocator {
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.composite;
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.springframework.context.annotation.Conditional;
/**
* @author Dylan Roberts
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSearchPathLocatorPresent.class)
public @interface ConditionalOnSearchPathLocator {
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.composite;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.SearchPathLocator;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author Dylan Roberts
*/
public class OnSearchPathLocatorPresent extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
List<String> types = CompositeUtils.getCompositeTypeList(context.getEnvironment());
// get EnvironmentRepository types from registered factories
List<Class<? extends EnvironmentRepository>> repositoryTypes = new ArrayList<>();
for (String type : types) {
String factoryName = CompositeUtils.getFactoryName(type, beanFactory);
Type[] actualTypeArguments = CompositeUtils.getEnvironmentRepositoryFactoryTypeParams(beanFactory,
factoryName);
Class<? extends EnvironmentRepository> repositoryType;
repositoryType = (Class<? extends EnvironmentRepository>) actualTypeArguments[0];
repositoryTypes.add(repositoryType);
}
boolean required = metadata.isAnnotated(ConditionalOnSearchPathLocator.class.getName());
boolean foundSearchPathLocator = repositoryTypes.stream().anyMatch(SearchPathLocator.class::isAssignableFrom);
if (required && !foundSearchPathLocator) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSearchPathLocator.class)
.notAvailable(SearchPathLocator.class.getTypeName()));
}
if (!required && foundSearchPathLocator) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingSearchPathLocator.class)
.available(SearchPathLocator.class.getTypeName()));
}
return ConditionOutcome.match();
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.config;
import java.util.ArrayList;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.config.server.environment.CompositeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.SearchPathCompositeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.SearchPathLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author Ryan Baxter
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CompositeEnvironmentRepository.class)
public class CompositeConfiguration {
@Bean
@Primary
@ConditionalOnBean(SearchPathLocator.class)
public SearchPathCompositeEnvironmentRepository searchPathCompositeEnvironmentRepository(
@Autowired(required = false) List<EnvironmentRepository> environmentRepos,
ConfigServerProperties properties, ObjectProvider<ObservationRegistry> observationRegistry) {
environmentRepos = environmentRepos != null ? environmentRepos : new ArrayList<>();
return new SearchPathCompositeEnvironmentRepository(environmentRepos,
observationRegistry.getIfAvailable(() -> ObservationRegistry.NOOP),
properties.isFailOnCompositeError());
}
@Bean
@Primary
@ConditionalOnMissingBean(SearchPathLocator.class)
public CompositeEnvironmentRepository compositeEnvironmentRepository(
@Autowired(required = false) List<EnvironmentRepository> environmentRepos,
ConfigServerProperties properties, ObjectProvider<ObservationRegistry> observationRegistry) {
environmentRepos = environmentRepos != null ? environmentRepos : new ArrayList<>();
return new CompositeEnvironmentRepository(environmentRepos,
observationRegistry.getIfAvailable(() -> ObservationRegistry.NOOP),
properties.isFailOnCompositeError());
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Spencer Gibb
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@ConditionalOnProperty(name = ConfigServerProperties.PREFIX + ".enabled", matchIfMissing = true)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class,
ResourceEncryptorConfiguration.class })
public class ConfigServerAutoConfiguration {
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Spencer Gibb
*/
@Configuration(proxyBeanMethods = false)
public class ConfigServerConfiguration {
@Bean
public Marker enableConfigServerMarker() {
return new Marker();
}
class Marker {
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.server.encryption.EncryptionController;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
@Configuration(proxyBeanMethods = false)
public class ConfigServerEncryptionConfiguration {
@Autowired(required = false)
private TextEncryptorLocator encryptor;
@Autowired
private ConfigServerProperties properties;
@Bean
public EncryptionController encryptionController() {
EncryptionController controller = new EncryptionController(this.encryptor);
controller.setDefaultApplicationName(this.properties.getDefaultApplicationName());
controller.setDefaultProfile(this.properties.getDefaultProfile());
return controller;
}
}

View File

@@ -1,166 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jakarta.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.util.CollectionUtils;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("spring.cloud.config.server.health")
public class ConfigServerHealthIndicator extends AbstractHealthIndicator {
private static Log logger = LogFactory.getLog(ConfigServerHealthIndicator.class);
private EnvironmentRepository environmentRepository;
private Map<String, Repository> repositories = new LinkedHashMap<>();
private String downHealthStatus = Status.DOWN.getCode();
// autowired required or boot constructor binding produces an error
@Autowired
public ConfigServerHealthIndicator(EnvironmentRepository environmentRepository) {
this.environmentRepository = environmentRepository;
}
@PostConstruct
public void init() {
if (this.repositories.isEmpty()) {
this.repositories.put("app", new Repository());
}
}
@Override
protected void doHealthCheck(Health.Builder builder) {
builder.up();
List<Map<String, Object>> details = new ArrayList<>();
for (String name : this.repositories.keySet()) {
Repository repository = this.repositories.get(name);
String application = (repository.getName() == null) ? name : repository.getName();
String profiles = repository.getProfiles();
try {
Environment environment = this.environmentRepository.findOne(application, profiles,
repository.getLabel(), false);
HashMap<String, Object> detail = new HashMap<>();
detail.put("name", environment.getName());
detail.put("label", environment.getLabel());
if (environment.getProfiles() != null && environment.getProfiles().length > 0) {
detail.put("profiles", Arrays.asList(environment.getProfiles()));
}
if (!CollectionUtils.isEmpty(environment.getPropertySources())) {
List<String> sources = new ArrayList<>();
for (PropertySource source : environment.getPropertySources()) {
sources.add(source.getName());
}
detail.put("sources", sources);
}
details.add(detail);
}
catch (Exception e) {
logger.debug("Could not read repository: " + application, e);
HashMap<String, String> map = new HashMap<>();
map.put("application", application);
map.put("profiles", profiles);
builder.withDetail("repository", map);
builder.status(this.downHealthStatus).withException(e);
return;
}
}
builder.withDetail("repositories", details);
}
public Map<String, Repository> getRepositories() {
return this.repositories;
}
public void setRepositories(Map<String, Repository> repositories) {
this.repositories = repositories;
}
public String getDownHealthStatus() {
return downHealthStatus;
}
public void setDownHealthStatus(String downHealthStatus) {
this.downHealthStatus = downHealthStatus;
}
/**
* Repository properties.
*/
public static class Repository {
private String name;
private String profiles = "default";
private String label;
public Repository() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getProfiles() {
return this.profiles;
}
public void setProfiles(String profiles) {
this.profiles = profiles;
}
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
}
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.config;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
import org.springframework.cloud.config.server.environment.EnvironmentController;
import org.springframework.cloud.config.server.environment.EnvironmentEncryptorEnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.resource.ResourceController;
import org.springframework.cloud.config.server.resource.ResourceControllerAdvice;
import org.springframework.cloud.config.server.resource.ResourceRepository;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author Dave Syer
* @author Roy Clarkson
* @author Tim Ysewyn
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
public class ConfigServerMvcConfiguration implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("properties", MediaType.valueOf("text/plain"));
configurer.mediaType("yml", MediaType.valueOf("text/yaml"));
configurer.mediaType("yaml", MediaType.valueOf("text/yaml"));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(org.springframework.cloud.context.scope.refresh.RefreshScope.class)
static class EnvironmentControllerConfiguration {
@Autowired(required = false)
private List<EnvironmentEncryptor> environmentEncryptors;
@Autowired(required = false)
private Map<String, ResourceEncryptor> resourceEncryptorMap = new HashMap<>();
@Autowired(required = false)
private ObjectMapper objectMapper = new ObjectMapper();
@Autowired(required = false)
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
@Bean
public EnvironmentController environmentController(EnvironmentRepository envRepository,
ConfigServerProperties server) {
return delegateController(envRepository, server);
}
protected EnvironmentController delegateController(EnvironmentRepository envRepository,
ConfigServerProperties server) {
EnvironmentController controller = new EnvironmentController(encrypted(envRepository, server),
this.objectMapper);
controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml());
controller.setAcceptEmpty(server.isAcceptEmpty());
return controller;
}
@Bean
@ConditionalOnBean(ResourceRepository.class)
public ResourceController resourceController(ResourceRepository repository, EnvironmentRepository envRepository,
ConfigServerProperties server) {
ResourceController controller = new ResourceController(repository, encrypted(envRepository, server),
this.resourceEncryptorMap);
controller.setEncryptEnabled(server.getEncrypt().isEnabled());
controller.setPlainTextEncryptEnabled(server.getEncrypt().isPlainTextEncrypt());
return controller;
}
@Bean
@ConditionalOnBean(ResourceController.class)
public ResourceControllerAdvice resourceControllerAdvice() {
return new ResourceControllerAdvice();
}
private EnvironmentRepository encrypted(EnvironmentRepository envRepository, ConfigServerProperties server) {
EnvironmentEncryptorEnvironmentRepository encrypted = new EnvironmentEncryptorEnvironmentRepository(
envRepository, this.environmentEncryptors, this.observationRegistry);
encrypted.setOverrides(server.getOverrides());
return encrypted;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(org.springframework.cloud.context.scope.refresh.RefreshScope.class)
static class RefreshableEnvironmentControllerConfiguration extends EnvironmentControllerConfiguration {
@Override
@Bean
@RefreshScope
public EnvironmentController environmentController(EnvironmentRepository envRepository,
ConfigServerProperties server) {
return super.delegateController(envRepository, server);
}
}
}

View File

@@ -1,256 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.style.ToStringCreator;
/**
* @author Dave Syer
* @author Roy Clarkson
*/
@ConfigurationProperties(ConfigServerProperties.PREFIX)
public class ConfigServerProperties {
/**
* Config Server properties prefix.
*/
public static final String PREFIX = "spring.cloud.config.server";
/**
* Flag indicating config server is enabled.
*/
private boolean enabled = true;
/**
* Flag indicating that the config server should initialize its own Environment with
* properties from the remote repository. Off by default because it delays startup but
* can be useful when embedding the server in another application.
*/
private boolean bootstrap;
/**
* Prefix for configuration resource paths (default is empty). Useful when embedding
* in another application when you don't want to change the context path or servlet
* path.
*/
private String prefix;
/**
* Default repository label when incoming requests do not have a specific label.
*/
private String defaultLabel;
/**
* Extra map for a property source to be sent to all clients unconditionally.
*/
private Map<String, String> overrides = new LinkedHashMap<String, String>();
/**
* Flag to indicate that YAML documents that are text or collections (not a map)
* should be returned in "native" form.
*/
private boolean stripDocumentFromYaml = true;
/**
* Flag to indicate that If HTTP 404 needs to be sent if Application is not Found.
*/
private boolean acceptEmpty = true;
/**
* Default application name when incoming requests do not have a specific one.
*/
private String defaultApplicationName = "application";
/**
* Default application profile when incoming requests do not have a specific one.
*/
private String defaultProfile = "default";
/**
* Flag indicating that if there are any errors reading properties from a subordinate
* environment repository in a composite environment repository, then the entire
* composite read should fail. Useful when set to false when a Vault repository is in
* the composite to allow clients to still read properties from other repositories
* without providing a valid Vault token.
*
* Defaults to true, resulting in a failure on any error.
*/
private boolean failOnCompositeError = true;
/**
* By default the location order we use in GenericResourceRepository is the order in
* which they are listed. Prior to Hoxton.SR11 the order used to be reverse. If this
* property is set to true then we will reverse ther order like it used to be prior to
* Hoxton.SR11.
*/
private boolean reverseLocationOrder = false;
/**
* Decryption configuration for when server handles encrypted properties before
* sending them to clients.
*/
private Encrypt encrypt = new Encrypt();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Encrypt getEncrypt() {
return this.encrypt;
}
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
public boolean isBootstrap() {
return this.bootstrap;
}
public void setBootstrap(boolean bootstrap) {
this.bootstrap = bootstrap;
}
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Map<String, String> getOverrides() {
return this.overrides;
}
public void setOverrides(Map<String, String> overrides) {
this.overrides = overrides;
}
public boolean isStripDocumentFromYaml() {
return this.stripDocumentFromYaml;
}
public void setStripDocumentFromYaml(boolean stripDocumentFromYaml) {
this.stripDocumentFromYaml = stripDocumentFromYaml;
}
public boolean isAcceptEmpty() {
return this.acceptEmpty;
}
public void setAcceptEmpty(boolean acceptEmpty) {
this.acceptEmpty = acceptEmpty;
}
public String getDefaultApplicationName() {
return this.defaultApplicationName;
}
public void setDefaultApplicationName(String defaultApplicationName) {
this.defaultApplicationName = defaultApplicationName;
}
public String getDefaultProfile() {
return this.defaultProfile;
}
public void setDefaultProfile(String defaultProfile) {
this.defaultProfile = defaultProfile;
}
public boolean isFailOnCompositeError() {
return failOnCompositeError;
}
public void setFailOnCompositeError(boolean failOnCompositeError) {
this.failOnCompositeError = failOnCompositeError;
}
public boolean isReverseLocationOrder() {
return reverseLocationOrder;
}
public void setReverseLocationOrder(boolean reverseLocationOrder) {
this.reverseLocationOrder = reverseLocationOrder;
}
@Override
public String toString() {
return new ToStringCreator(this).append("enabled", enabled).append("bootstrap", bootstrap)
.append("prefix", prefix).append("defaultLabel", defaultLabel).append("overrides", overrides)
.append("stripDocumentFromYaml", stripDocumentFromYaml).append("acceptEmpty", acceptEmpty)
.append("defaultApplicationName", defaultApplicationName).append("defaultProfile", defaultProfile)
.append("failOnCompositeError", failOnCompositeError).append("encrypt", encrypt)
.append("reverseLocationOrder", reverseLocationOrder).toString();
}
/**
* Encryption properties.
*/
public static class Encrypt {
/**
* Enable decryption of environment properties before sending to client.
*/
private boolean enabled = true;
/**
* Enable decryption of environment properties served by plain text endpoint
* {@link org.springframework.cloud.config.server.resource.ResourceController}.
*/
private boolean plainTextEncrypt = false;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isPlainTextEncrypt() {
return plainTextEncrypt;
}
public void setPlainTextEncrypt(boolean plainTextEncrypt) {
this.plainTextEncrypt = plainTextEncrypt;
}
@Override
public String toString() {
return new ToStringCreator(this).append("enabled", enabled).append("plainTextEncrypt", plainTextEncrypt)
.toString();
}
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.support.ClientOptions;
/**
* @author Alberto C. Ríos
*/
@Configuration(proxyBeanMethods = false)
@Profile("credhub")
public class CredhubConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.cloud.config.server.credhub")
public CredHubProperties credHubProperties() {
return new CredHubProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.cloud.config.server.credhub")
public ClientOptions clientOptions() {
return new ClientOptions();
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2002-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.config.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.cloud.config.server.encryption.LocatorTextEncryptor;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.util.StringUtils;
/**
* Default text encryption auto-configuration.
*
* @author Olga Maciaszek-Sharma
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(RsaEncryptionAutoConfiguration.class)
@EnableConfigurationProperties
public class DefaultTextEncryptionAutoConfiguration {
@Autowired
ApplicationContext context;
@Bean
@ConditionalOnMissingBean
public KeyProperties keyProperties() {
return new KeyProperties();
}
@Bean
@ConditionalOnMissingBean(TextEncryptor.class)
@ConditionalOnBean(TextEncryptorLocator.class)
public TextEncryptor defaultLocatorBasedTextEncryptor(TextEncryptorLocator locator) {
return new LocatorTextEncryptor(locator);
}
@Bean
@ConditionalOnMissingBean(TextEncryptor.class)
public TextEncryptor defaultTextEncryptor(KeyProperties key) {
if (StringUtils.hasText(key.getKey())) {
return new EncryptorFactory(key.getSalt()).create(key.getKey());
}
return Encryptors.noOpText();
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.config.server.encryption.CipherEnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.SingleTextEncryptorLocator;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* Autoconfiguration for text encryptors and environment encryptors (non-web stuff). Users
* can provide beans of the same type as any or all of the beans defined here in
* application code to override the default behaviour.
*
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
* @author Dave Syer
* @author Olga Maciaszek-Sharma
*
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DefaultTextEncryptionAutoConfiguration.class)
public class EncryptionAutoConfiguration {
@Bean
@ConditionalOnBean(TextEncryptor.class)
@ConditionalOnMissingBean(TextEncryptorLocator.class)
public SingleTextEncryptorLocator singleTextEncryptorLocator(TextEncryptor encryptor) {
return new SingleTextEncryptorLocator(encryptor);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.config.server.encrypt.enabled", matchIfMissing = true)
@ConditionalOnBean(TextEncryptorLocator.class)
public EnvironmentEncryptor environmentEncryptor(@Autowired(required = false) TextEncryptorLocator locator,
TextEncryptor encryptor) {
if (locator == null) {
locator = new SingleTextEncryptorLocator(encryptor);
}
return new CipherEnvironmentEncryptor(locator);
}
}

View File

@@ -1,572 +0,0 @@
/*
* Copyright 2013-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 org.springframework.cloud.config.server.config;
import java.util.List;
import java.util.Optional;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.http.client.HttpClient;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.tmatesoft.svn.core.SVNException;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.ssm.SsmClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.config.server.composite.CompositeEnvironmentBeanFactoryPostProcessor;
import org.springframework.cloud.config.server.composite.ConditionalOnMissingSearchPathLocator;
import org.springframework.cloud.config.server.composite.ConditionalOnSearchPathLocator;
import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentProperties;
import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentRepository;
import org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentProperties;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentRepository;
import org.springframework.cloud.config.server.environment.AwsS3EnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.AwsSecretsManagerEnvironmentProperties;
import org.springframework.cloud.config.server.environment.AwsSecretsManagerEnvironmentRepository;
import org.springframework.cloud.config.server.environment.AwsSecretsManagerEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.CompositeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
import org.springframework.cloud.config.server.environment.ConfigurableHttpConnectionFactory;
import org.springframework.cloud.config.server.environment.ConsulEnvironmentWatch;
import org.springframework.cloud.config.server.environment.CredhubEnvironmentProperties;
import org.springframework.cloud.config.server.environment.CredhubEnvironmentRepository;
import org.springframework.cloud.config.server.environment.CredhubEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.EnvironmentWatch;
import org.springframework.cloud.config.server.environment.GoogleSecretManagerEnvironmentProperties;
import org.springframework.cloud.config.server.environment.GoogleSecretManagerEnvironmentRepository;
import org.springframework.cloud.config.server.environment.GoogleSecretManagerEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.HttpClientConfigurableHttpConnectionFactory;
import org.springframework.cloud.config.server.environment.HttpClientVaultRestTemplateFactory;
import org.springframework.cloud.config.server.environment.HttpRequestConfigTokenProvider;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentProperties;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentRepository;
import org.springframework.cloud.config.server.environment.JdbcEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentProperties;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.NativeEnvironmentProperties;
import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.NativeEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.RedisEnvironmentProperties;
import org.springframework.cloud.config.server.environment.RedisEnvironmentRepository;
import org.springframework.cloud.config.server.environment.RedisEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.SearchPathCompositeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.SvnEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.SvnKitEnvironmentProperties;
import org.springframework.cloud.config.server.environment.SvnKitEnvironmentRepository;
import org.springframework.cloud.config.server.environment.VaultEnvironmentProperties;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepository;
import org.springframework.cloud.config.server.environment.VaultEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.environment.vault.SpringVaultClientConfiguration;
import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository;
import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepositoryFactory;
import org.springframework.cloud.config.server.support.GitCredentialsProviderFactory;
import org.springframework.cloud.config.server.support.GoogleCloudSourceSupport;
import org.springframework.cloud.config.server.support.TransportConfigCallbackFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.vault.core.VaultTemplate;
/**
* @author Dave Syer
* @author Ryan Baxter
* @author Daniel Lavoie
* @author Dylan Roberts
* @author Alberto C. Ríos
* @author Scott Frederick
* @author Tejas Pandilwar
* @author Iulian Antohe
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class, CredhubEnvironmentProperties.class,
JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class,
RedisEnvironmentProperties.class, AwsS3EnvironmentProperties.class,
AwsSecretsManagerEnvironmentProperties.class, AwsParameterStoreEnvironmentProperties.class,
GoogleSecretManagerEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultConfiguration.class,
VaultRepositoryConfiguration.class, SpringVaultRepositoryConfiguration.class, CredhubConfiguration.class,
CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class,
GitRepositoryConfiguration.class, RedisRepositoryConfiguration.class, GoogleCloudSourceConfiguration.class,
AwsS3RepositoryConfiguration.class, AwsSecretsManagerRepositoryConfiguration.class,
AwsParameterStoreRepositoryConfiguration.class, GoogleSecretManagerRepositoryConfiguration.class,
// DefaultRepositoryConfiguration must be last
DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public MultipleJGitEnvironmentProperties multipleJGitEnvironmentProperties() {
return new MultipleJGitEnvironmentProperties();
}
@Bean
@ConditionalOnMissingBean(ConfigTokenProvider.class)
public ConfigTokenProvider defaultConfigTokenProvider(ObjectProvider<HttpServletRequest> httpRequest) {
return new HttpRequestConfigTokenProvider(httpRequest);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AbstractHealthIndicator.class)
@ConditionalOnProperty(value = "spring.cloud.config.server.health.enabled", matchIfMissing = true)
protected static class ConfigServerActuatorConfiguration {
@Bean
public ConfigServerHealthIndicator configServerHealthIndicator(EnvironmentRepository repository) {
return new ConfigServerHealthIndicator(repository);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty("spring.cloud.config.server.consul.watch.enabled")
protected static class ConsulEnvironmentWatchConfiguration {
@Bean
public EnvironmentWatch environmentWatch() {
return new ConsulEnvironmentWatch();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(EnvironmentWatch.class)
protected static class DefaultEnvironmentWatch {
@Bean
public EnvironmentWatch environmentWatch() {
return new EnvironmentWatch.Default();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(TransportConfigCallback.class)
static class JGitFactoryConfig {
@Bean
public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory(
ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<ConfigurableHttpConnectionFactory> jgitHttpConnectionFactory,
Optional<TransportConfigCallback> customTransportConfigCallback,
Optional<GoogleCloudSourceSupport> googleCloudSourceSupport,
GitCredentialsProviderFactory gitCredentialsProviderFactory) {
final TransportConfigCallbackFactory transportConfigCallbackFactory = new TransportConfigCallbackFactory(
customTransportConfigCallback.orElse(null), googleCloudSourceSupport.orElse(null));
return new MultipleJGitEnvironmentRepositoryFactory(environment, server, jgitHttpConnectionFactory,
transportConfigCallbackFactory, gitCredentialsProviderFactory);
}
@Bean
@ConditionalOnMissingBean
public GitCredentialsProviderFactory gitCredentialsProviderFactory() {
return new GitCredentialsProviderFactory();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HttpClient.class, TransportConfigCallback.class })
static class JGitHttpClientConfig {
@Bean
public ConfigurableHttpConnectionFactory httpClientConnectionFactory() {
return new HttpClientConfigurableHttpConnectionFactory();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(S3Client.class)
static class AwsS3FactoryConfig {
@Bean
public AwsS3EnvironmentRepositoryFactory awsS3EnvironmentRepositoryFactory(ConfigServerProperties server) {
return new AwsS3EnvironmentRepositoryFactory(server);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SecretsManagerClient.class)
static class AwsSecretsManagerFactoryConfig {
@Bean
public AwsSecretsManagerEnvironmentRepositoryFactory awsSecretsManagerEnvironmentRepositoryFactory(
ConfigServerProperties configServerProperties) {
return new AwsSecretsManagerEnvironmentRepositoryFactory(configServerProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SsmClient.class)
static class AwsParameterStoreFactoryConfig {
@Bean
public AwsParameterStoreEnvironmentRepositoryFactory awsParameterStoreEnvironmentRepositoryFactory(
ConfigServerProperties server) {
return new AwsParameterStoreEnvironmentRepositoryFactory(server);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SVNException.class)
static class SvnFactoryConfig {
@Bean
public SvnEnvironmentRepositoryFactory svnEnvironmentRepositoryFactory(ConfigurableEnvironment environment,
ConfigServerProperties server, ObjectProvider<ObservationRegistry> observationRegistry) {
return new SvnEnvironmentRepositoryFactory(environment, server,
observationRegistry.getIfAvailable(() -> ObservationRegistry.NOOP));
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.vault.core.VaultTemplate")
@SuppressWarnings("deprecation")
static class VaultFactoryConfig {
@Bean
public VaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory(
ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
Optional<VaultEnvironmentRepositoryFactory.VaultRestTemplateFactory> vaultRestTemplateFactory,
ConfigTokenProvider tokenProvider) {
return new VaultEnvironmentRepositoryFactory(request, watch, vaultRestTemplateFactory, tokenProvider);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SecretManagerServiceClient.class)
static class GoogleSecretManagerFactoryConfig {
@Bean
public GoogleSecretManagerEnvironmentRepositoryFactory googleSecretManagerEnvironmentRepositoryFactory(
ObjectProvider<HttpServletRequest> request) {
return new GoogleSecretManagerEnvironmentRepositoryFactory(request);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
@ConditionalOnMissingClass("org.springframework.vault.core.VaultTemplate")
@SuppressWarnings("deprecation")
static class VaultHttpClientConfig {
@Bean
public VaultEnvironmentRepositoryFactory.VaultRestTemplateFactory vaultRestTemplateFactory() {
return new HttpClientVaultRestTemplateFactory();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(VaultTemplate.class)
@Import(SpringVaultClientConfiguration.class)
static class SpringVaultFactoryConfig {
@Bean
public SpringVaultEnvironmentRepositoryFactory vaultEnvironmentRepositoryFactory(
ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
SpringVaultClientConfiguration vaultClientConfiguration) {
return new SpringVaultEnvironmentRepositoryFactory(request, watch, vaultClientConfiguration);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JdbcTemplate.class)
@ConditionalOnProperty(value = "spring.cloud.config.server.jdbc.enabled", matchIfMissing = true)
static class JdbcFactoryConfig {
@Bean
@ConditionalOnBean(JdbcTemplate.class)
public JdbcEnvironmentRepositoryFactory jdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc,
JdbcEnvironmentRepository.PropertiesResultSetExtractor propertiesResultSetExtractor) {
return new JdbcEnvironmentRepositoryFactory(jdbc, propertiesResultSetExtractor);
}
@Bean
@ConditionalOnMissingBean(JdbcEnvironmentRepository.PropertiesResultSetExtractor.class)
public JdbcEnvironmentRepository.PropertiesResultSetExtractor propertiesResultSetExtractor() {
return new JdbcEnvironmentRepository.PropertiesResultSetExtractor();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StringRedisTemplate.class)
static class RedisFactoryConfig {
@Bean
@ConditionalOnBean(StringRedisTemplate.class)
public RedisEnvironmentRepositoryFactory redisEnvironmentRepositoryFactory(StringRedisTemplate redis) {
return new RedisEnvironmentRepositoryFactory(redis);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CredHubOperations.class)
static class CredhubFactoryConfig {
@Bean
public CredhubEnvironmentRepositoryFactory credhubEnvironmentRepositoryFactory(
Optional<CredHubOperations> credHubOperations) {
return new CredhubEnvironmentRepositoryFactory(credHubOperations.orElse(null));
}
}
@Configuration(proxyBeanMethods = false)
static class NativeFactoryConfig {
@Bean
public NativeEnvironmentRepositoryFactory nativeEnvironmentRepositoryFactory(
ConfigurableEnvironment environment, ConfigServerProperties properties,
ObjectProvider<ObservationRegistry> observationRegistry) {
return new NativeEnvironmentRepositoryFactory(environment, properties,
observationRegistry.getIfAvailable(() -> ObservationRegistry.NOOP));
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("native")
class NativeRepositoryConfiguration {
@Bean
public NativeEnvironmentRepository nativeEnvironmentRepository(NativeEnvironmentRepositoryFactory factory,
NativeEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("git")
class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Profile("awss3")
class AwsS3RepositoryConfiguration {
@Bean
@ConditionalOnMissingBean(AwsS3EnvironmentRepository.class)
public AwsS3EnvironmentRepository awsS3EnvironmentRepository(AwsS3EnvironmentRepositoryFactory factory,
AwsS3EnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("awsparamstore")
class AwsParameterStoreRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean(AwsParameterStoreEnvironmentRepository.class)
public AwsParameterStoreEnvironmentRepository awsParameterStoreEnvironmentRepository(
AwsParameterStoreEnvironmentRepositoryFactory factory,
AwsParameterStoreEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("awssecretsmanager")
class AwsSecretsManagerRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean(AwsSecretsManagerEnvironmentRepository.class)
public AwsSecretsManagerEnvironmentRepository awsSecretsManagerEnvironmentRepository(
AwsSecretsManagerEnvironmentRepositoryFactory factory,
AwsSecretsManagerEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("subversion")
class SvnRepositoryConfiguration {
@Bean
public SvnKitEnvironmentRepository svnKitEnvironmentRepository(SvnEnvironmentRepositoryFactory factory,
SvnKitEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.vault.core.VaultTemplate")
@Profile("vault")
@SuppressWarnings("deprecation")
class VaultRepositoryConfiguration {
@Bean
public VaultEnvironmentRepository vaultEnvironmentRepository(VaultEnvironmentRepositoryFactory factory,
VaultEnvironmentProperties environmentProperties) throws Exception {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(VaultTemplate.class)
@Profile("vault")
class SpringVaultRepositoryConfiguration {
@Bean
public SpringVaultEnvironmentRepository vaultEnvironmentRepository(SpringVaultEnvironmentRepositoryFactory factory,
VaultEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("credhub")
class CredhubRepositoryConfiguration {
@Bean
public CredhubEnvironmentRepository credhubEnvironmentRepository(CredhubEnvironmentRepositoryFactory factory,
CredhubEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("jdbc")
@ConditionalOnClass(JdbcTemplate.class)
@ConditionalOnProperty(value = "spring.cloud.config.server.jdbc.enabled", matchIfMissing = true)
class JdbcRepositoryConfiguration {
@Bean
@ConditionalOnBean(JdbcTemplate.class)
public JdbcEnvironmentRepository jdbcEnvironmentRepository(JdbcEnvironmentRepositoryFactory factory,
JdbcEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("redis")
@ConditionalOnClass(StringRedisTemplate.class)
class RedisRepositoryConfiguration {
@Bean
@ConditionalOnBean(StringRedisTemplate.class)
public RedisEnvironmentRepository redisEnvironmentRepository(RedisEnvironmentRepositoryFactory factory,
RedisEnvironmentProperties environmentProperties) {
return factory.build(environmentProperties);
}
}
@Configuration(proxyBeanMethods = false)
@Profile("composite")
class CompositeRepositoryConfiguration {
@Bean
public static CompositeEnvironmentBeanFactoryPostProcessor compositeEnvironmentRepositoryBeanFactoryPostProcessor(
Environment environment) {
return new CompositeEnvironmentBeanFactoryPostProcessor(environment);
}
@Primary
@Bean
@ConditionalOnSearchPathLocator
public SearchPathCompositeEnvironmentRepository searchPathCompositeEnvironmentRepository(
List<EnvironmentRepository> environmentRepositories, ConfigServerProperties properties,
ObjectProvider<ObservationRegistry> observationRegistry) {
return new SearchPathCompositeEnvironmentRepository(environmentRepositories,
observationRegistry.getIfAvailable(() -> ObservationRegistry.NOOP),
properties.isFailOnCompositeError());
}
@Primary
@Bean
@ConditionalOnMissingSearchPathLocator
public CompositeEnvironmentRepository compositeEnvironmentRepository(
List<EnvironmentRepository> environmentRepositories, ConfigServerProperties properties,
ObjectProvider<ObservationRegistry> observationRegistry) {
return new CompositeEnvironmentRepository(environmentRepositories,
observationRegistry.getIfAvailable(() -> ObservationRegistry.NOOP),
properties.isFailOnCompositeError());
}
}
@Configuration(proxyBeanMethods = false)
@Profile("secret-manager")
@ConditionalOnClass(SecretManagerServiceClient.class)
class GoogleSecretManagerRepositoryConfiguration {
@Bean
public GoogleSecretManagerEnvironmentRepository googleSecretManagerEnvironmentRepository(
GoogleSecretManagerEnvironmentRepositoryFactory factory,
GoogleSecretManagerEnvironmentProperties environmentProperties) throws Exception {
return factory.build(environmentProperties);
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.config;
import com.google.auth.oauth2.GoogleCredentials;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.config.server.support.GoogleCloudSourceSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Adds Google Cloud Source OAuth2 support, if
* com.google.auth:google-auth-library-oauth2-http library is on classpath.
*
* @author Eduard Wirch
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GoogleCredentials.class, TransportConfigCallback.class })
public class GoogleCloudSourceConfiguration {
@Bean
public GoogleCloudSourceSupport createGoogleCloudSourceSupport() {
return new GoogleCloudSourceSupport();
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cloud.config.server.encryption.CipherResourceJsonEncryptor;
import org.springframework.cloud.config.server.encryption.CipherResourcePropertiesEncryptor;
import org.springframework.cloud.config.server.encryption.CipherResourceYamlEncryptor;
import org.springframework.cloud.config.server.encryption.ResourceEncryptor;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Adds configuration to decrypt plain text files served through
* {@link org.springframework.cloud.config.server.resource.ResourceController}. Each
* supported extension is added as a key with its associated @{link
* org.springframework.cloud.config.server.encryption.ResourceEncryptor} implementation as
* a value.
*
* @author Sean Stiglitz
*/
@Configuration
@ConditionalOnExpression("${spring.cloud.config.server.encrypt.enabled:true} && ${spring.cloud.config.server.encrypt.plainTextEncrypt:true}")
@ConditionalOnBean(TextEncryptorLocator.class)
public class ResourceEncryptorConfiguration {
@Autowired
private TextEncryptorLocator encryptor;
@Bean
Map<String, ResourceEncryptor> resourceEncryptors() {
Map<String, ResourceEncryptor> resourceEncryptorMap = new HashMap<>();
addSupportedExtensionsToMap(resourceEncryptorMap, new CipherResourceJsonEncryptor(encryptor));
addSupportedExtensionsToMap(resourceEncryptorMap, new CipherResourcePropertiesEncryptor(encryptor));
addSupportedExtensionsToMap(resourceEncryptorMap, new CipherResourceYamlEncryptor(encryptor));
return resourceEncryptorMap;
}
private void addSupportedExtensionsToMap(Map<String, ResourceEncryptor> resourceEncryptorMap,
ResourceEncryptor resourceEncryptor) {
for (String ext : resourceEncryptor.getSupportedExtensions()) {
resourceEncryptorMap.put(ext, resourceEncryptor);
}
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.config.server.environment.SearchPathLocator;
import org.springframework.cloud.config.server.resource.GenericResourceRepository;
import org.springframework.cloud.config.server.resource.ResourceRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
* @author Tim Ysewyn
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ResourceRepository.class)
public class ResourceRepositoryConfiguration {
@Bean
@ConditionalOnBean(SearchPathLocator.class)
public ResourceRepository resourceRepository(SearchPathLocator service, ConfigServerProperties properties) {
return new GenericResourceRepository(service, properties);
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2002-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.config.server.config;
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.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.cloud.bootstrap.encrypt.RsaProperties;
import org.springframework.cloud.config.server.encryption.KeyStoreTextEncryptorLocator;
import org.springframework.cloud.config.server.encryption.TextEncryptorLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.rsa.crypto.RsaAlgorithm;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
/**
* Autoconfiguration for RSA encryption.
*
* @author Olga Maciaszek-Sharma
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "encrypt.key-store", value = "location")
@ConditionalOnClass(RsaSecretEncryptor.class)
@EnableConfigurationProperties
public class RsaEncryptionAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public KeyProperties keyProperties() {
return new KeyProperties();
}
@Bean
@ConditionalOnMissingBean
public TextEncryptorLocator textEncryptorLocator(KeyProperties key, RsaProperties rsaProperties) {
KeyProperties.KeyStore keyStore = key.getKeyStore();
KeyStoreTextEncryptorLocator locator = new KeyStoreTextEncryptorLocator(
new KeyStoreKeyFactory(keyStore.getLocation(), keyStore.getPassword().toCharArray(),
key.getKeyStore().getType()),
keyStore.getSecret(), keyStore.getAlias());
RsaAlgorithm algorithm = rsaProperties.getAlgorithm();
locator.setRsaAlgorithm(algorithm);
locator.setSalt(rsaProperties.getSalt());
locator.setStrong(rsaProperties.isStrong());
return locator;
}
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.config.server.environment.ConfigTokenProvider;
import org.springframework.cloud.config.server.environment.EnvironmentConfigTokenProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.AppRoleClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.AwsEc2ClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.AwsIamClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.AzureMsiClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.CertificateClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.CubbyholeClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.GcpGceClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.GcpIamClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.KubernetesClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.PcfClientAuthenticationProvider;
import org.springframework.cloud.config.server.environment.vault.authentication.TokenClientAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.vault.core.VaultTemplate;
/**
* @author Scott Frederick
*/
@Configuration(proxyBeanMethods = false)
public class VaultConfiguration {
private static final String VAULT_TOKEN_PROPERTY_NAME = "spring.cloud.config.server.vault.token";
@Bean
@ConditionalOnProperty(VAULT_TOKEN_PROPERTY_NAME)
public ConfigTokenProvider vaultConfigTokenProvider(Environment environment) {
return new EnvironmentConfigTokenProvider(environment, VAULT_TOKEN_PROPERTY_NAME);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(VaultTemplate.class)
public static class VaultClientAuthenticationProviderConfiguration {
@Bean
public AppRoleClientAuthenticationProvider appRoleClientAuthenticationProvider() {
return new AppRoleClientAuthenticationProvider();
}
@Bean
public AwsEc2ClientAuthenticationProvider awsEc2ClientAuthenticationProvider() {
return new AwsEc2ClientAuthenticationProvider();
}
@Bean
public AwsIamClientAuthenticationProvider awsIamClientAuthenticationProvider() {
return new AwsIamClientAuthenticationProvider();
}
@Bean
public AzureMsiClientAuthenticationProvider azureMsiClientAuthenticationProvider() {
return new AzureMsiClientAuthenticationProvider();
}
@Bean
public CertificateClientAuthenticationProvider certificateClientAuthenticationProvider() {
return new CertificateClientAuthenticationProvider();
}
@Bean
public CubbyholeClientAuthenticationProvider cubbyholeClientAuthenticationProvider() {
return new CubbyholeClientAuthenticationProvider();
}
@Bean
public GcpGceClientAuthenticationProvider gcpGceClientAuthenticationProvider() {
return new GcpGceClientAuthenticationProvider();
}
@Bean
public GcpIamClientAuthenticationProvider gcpIamClientAuthenticationProvider() {
return new GcpIamClientAuthenticationProvider();
}
@Bean
public KubernetesClientAuthenticationProvider kubernetesClientAuthenticationProvider() {
return new KubernetesClientAuthenticationProvider();
}
@Bean
public PcfClientAuthenticationProvider pcfClientAuthenticationProvider() {
return new PcfClientAuthenticationProvider();
}
@Bean
public TokenClientAuthenticationProvider tokenClientAuthenticationProvider() {
return new TokenClientAuthenticationProvider();
}
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2020-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 org.springframework.cloud.config.server.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.config.server.encryption.vault.VaultEnvironmentEncryptor;
import org.springframework.cloud.config.server.environment.vault.SpringVaultEnvironmentRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.vault.core.VaultTemplate;
/**
* Auto configuration for vault encryptor.
*
* @author Alexey Zhokhov
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(VaultTemplate.class)
@Profile("vault")
public class VaultEncryptionAutoConfiguration {
@Bean
public VaultEnvironmentEncryptor vaultEnvironmentEncryptor(
SpringVaultEnvironmentRepository vaultEnvironmentRepository) {
return new VaultEnvironmentEncryptor(vaultEnvironmentRepository.getKeyValueTemplate());
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.diagnostics;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.cloud.config.server.environment.JGitEnvironmentRepository;
/**
* @author Ryan Baxter
*/
public class GitUriFailureAnalyzer extends AbstractFailureAnalyzer<IllegalStateException> {
/**
* Description of the failure.
*/
public static final String DESCRIPTION = "Invalid config server configuration.";
/**
* Action to take for git failure.
*/
public static final String ACTION = "If you are using the git profile, you need to set a Git URI in your "
+ "configuration. If you have set spring.cloud.config.server.bootstrap=true, "
+ "you need to use a composite configuration.";
@Override
protected FailureAnalysis analyze(Throwable rootFailure, IllegalStateException cause) {
if (JGitEnvironmentRepository.MESSAGE.equalsIgnoreCase(cause.getMessage())) {
return new FailureAnalysis(DESCRIPTION, ACTION, cause);
}
return null;
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.encryption;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.util.StringUtils;
/**
* Abstract base class for any @{link
* org.springframework.cloud.config.server.encryption.ResourceEncryptor} implementations.
* Meant to house shared configuration and logic.
*
* @author Sean Stiglitz
*/
abstract class AbstractCipherResourceEncryptor implements ResourceEncryptor {
protected final String CIPHER_MARKER = "{cipher}";
private final TextEncryptorLocator encryptor;
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
AbstractCipherResourceEncryptor(TextEncryptorLocator encryptor) {
this.encryptor = encryptor;
}
@Override
public abstract List<String> getSupportedExtensions();
@Override
public abstract String decrypt(String text, Environment environment) throws IOException;
protected String decryptWithJacksonParser(String text, String name, String[] profiles, JsonFactory factory)
throws IOException {
Set<String> valsToDecrpyt = new HashSet<String>();
JsonParser parser = factory.createParser(text);
JsonToken token;
while ((token = parser.nextToken()) != null) {
if (token.equals(JsonToken.VALUE_STRING) && parser.getValueAsString().startsWith(CIPHER_MARKER)) {
valsToDecrpyt.add(parser.getValueAsString().trim());
}
}
for (String value : valsToDecrpyt) {
String decryptedValue = decryptValue(value.replace(CIPHER_MARKER, ""), name, profiles);
text = text.replace(value, decryptedValue);
}
return text;
}
protected String decryptValue(String value, String name, String[] profiles) {
return encryptor
.locate(this.helper.getEncryptorKeys(name, StringUtils.arrayToCommaDelimitedString(profiles), value))
.decrypt(this.helper.stripPrefix(value));
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.encryption;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* EnvironmentEncryptor that can decrypt property values prefixed with {cipher} marker.
*
* @author Dave Syer
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
@Component
public class CipherEnvironmentEncryptor implements EnvironmentEncryptor {
private static Log logger = LogFactory.getLog(CipherEnvironmentEncryptor.class);
private final TextEncryptorLocator encryptor;
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
@Autowired
public CipherEnvironmentEncryptor(TextEncryptorLocator encryptor) {
this.encryptor = encryptor;
}
@Override
public Environment decrypt(Environment environment) {
return this.encryptor != null ? decrypt(environment, this.encryptor) : environment;
}
private Environment decrypt(Environment environment, TextEncryptorLocator encryptor) {
Environment result = new Environment(environment);
for (PropertySource source : environment.getPropertySources()) {
Map<Object, Object> map = new LinkedHashMap<Object, Object>(source.getSource());
for (Map.Entry<Object, Object> entry : new LinkedHashSet<>(map.entrySet())) {
Object key = entry.getKey();
String name = key.toString();
if (entry.getValue() != null && entry.getValue().toString().startsWith("{cipher}")) {
String value = entry.getValue().toString();
map.remove(key);
try {
value = value.substring("{cipher}".length());
value = encryptor
.locate(this.helper.getEncryptorKeys(name,
StringUtils.arrayToCommaDelimitedString(environment.getProfiles()), value))
.decrypt(this.helper.stripPrefix(value));
}
catch (Exception e) {
value = "<n/a>";
name = "invalid." + name;
String message = "Cannot decrypt key: " + key + " (" + e.getClass() + ": " + e.getMessage()
+ ")";
if (logger.isDebugEnabled()) {
logger.debug(message, e);
}
else if (logger.isWarnEnabled()) {
logger.warn(message);
}
}
map.put(name, value);
}
}
result.add(new PropertySource(source.getName(), map));
}
return result;
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.encryption;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.core.JsonFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.stereotype.Component;
/**
* {@link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
* implementation that can decrypt property values prefixed with {cipher} marker in a JSON
* file.
*
* @author Sean Stiglitz
*/
@Component
public class CipherResourceJsonEncryptor extends AbstractCipherResourceEncryptor implements ResourceEncryptor {
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("json");
private final JsonFactory factory;
public CipherResourceJsonEncryptor(TextEncryptorLocator encryptor) {
super(encryptor);
this.factory = new JsonFactory();
}
@Override
public List<String> getSupportedExtensions() {
return SUPPORTED_EXTENSIONS;
}
@Override
public String decrypt(String text, Environment environment) throws IOException {
return decryptWithJacksonParser(text, environment.getName(), environment.getProfiles(), factory);
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.encryption;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.stereotype.Component;
/**
* {@link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
* implementation that can decrypt property values prefixed with {cipher} marker in a
* Properties file.
*
* @author Sean Stiglitz
*/
@Component
public class CipherResourcePropertiesEncryptor extends AbstractCipherResourceEncryptor implements ResourceEncryptor {
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("properties");
public CipherResourcePropertiesEncryptor(TextEncryptorLocator encryptor) {
super(encryptor);
}
@Override
public List<String> getSupportedExtensions() {
return SUPPORTED_EXTENSIONS;
}
@Override
public String decrypt(String text, Environment environment) throws IOException {
Set<String> valsToDecrpyt = new HashSet<String>();
Properties properties = new Properties();
StringBuffer sb = new StringBuffer();
properties.load(new ByteArrayInputStream(text.getBytes()));
for (Object value : properties.values()) {
String valueStr = value.toString();
if (valueStr.startsWith(CIPHER_MARKER)) {
valsToDecrpyt.add(valueStr);
}
}
for (String value : valsToDecrpyt) {
String decryptedValue = decryptValue(value.replace(CIPHER_MARKER, ""), environment.getName(),
environment.getProfiles());
text = text.replace(value, decryptedValue);
}
return text;
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.encryption;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.stereotype.Component;
/**
* {@link org.springframework.cloud.config.server.encryption.ResourceEncryptor}
* implementation that can decrypt property values prefixed with {cipher} marker in a YAML
* file.
*
* @author Sean Stiglitz
*/
@Component
public class CipherResourceYamlEncryptor extends AbstractCipherResourceEncryptor implements ResourceEncryptor {
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("yml", "yaml");
private final YAMLFactory factory;
public CipherResourceYamlEncryptor(TextEncryptorLocator encryptor) {
super(encryptor);
this.factory = new YAMLFactory();
}
@Override
public List<String> getSupportedExtensions() {
return SUPPORTED_EXTENSIONS;
}
@Override
public String decrypt(String text, Environment environment) throws IOException {
return decryptWithJacksonParser(text, environment.getName(), environment.getProfiles(), factory);
}
}

View File

@@ -1,287 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.encryption;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.context.encrypt.KeyFormatException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.rsa.crypto.RsaKeyHolder;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dave Syer
* @author Tim Ysewyn
*
*/
@RestController
@RequestMapping(path = "${spring.cloud.config.server.prefix:}")
public class EncryptionController {
private static Log logger = LogFactory.getLog(EncryptionController.class);
volatile private TextEncryptorLocator encryptorLocator;
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
private String defaultApplicationName = "application";
private String defaultProfile = "default";
public EncryptionController(TextEncryptorLocator encryptorLocator) {
this.encryptorLocator = encryptorLocator;
}
public void setDefaultApplicationName(String defaultApplicationName) {
this.defaultApplicationName = defaultApplicationName;
}
public void setDefaultProfile(String defaultProfile) {
this.defaultProfile = defaultProfile;
}
@GetMapping("/key")
public String getPublicKey() {
return getPublicKey(defaultApplicationName, defaultProfile);
}
@GetMapping("/key/{name}/{profiles}")
public String getPublicKey(@PathVariable String name, @PathVariable String profiles) {
TextEncryptor encryptor = getEncryptor(name, profiles, "");
if (!(encryptor instanceof RsaKeyHolder)) {
throw new KeyNotAvailableException();
}
return ((RsaKeyHolder) encryptor).getPublicKey();
}
@GetMapping("encrypt/status")
public Map<String, Object> status() {
TextEncryptor encryptor = getEncryptor(defaultApplicationName, defaultProfile, "");
validateEncryptionWeakness(encryptor);
return Collections.singletonMap("status", "OK");
}
@PostMapping("encrypt")
public String encrypt(@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
return encrypt(defaultApplicationName, defaultProfile, data, type);
}
@PostMapping("/encrypt/{name}/{profiles}")
public String encrypt(@PathVariable String name, @PathVariable String profiles, @RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
String input = stripFormData(data, type, false);
TextEncryptor encryptor = getEncryptor(name, profiles, input);
validateEncryptionWeakness(encryptor);
Map<String, String> keys = helper.getEncryptorKeys(name, profiles, input);
String textToEncrypt = helper.stripPrefix(input);
String encrypted = helper.addPrefix(keys, encryptor.encrypt(textToEncrypt));
if (logger.isInfoEnabled()) {
logger.info("Encrypted data");
}
return encrypted;
}
@PostMapping("decrypt")
public String decrypt(@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
return decrypt(defaultApplicationName, defaultProfile, data, type);
}
@PostMapping("/decrypt/{name}/{profiles}")
public String decrypt(@PathVariable String name, @PathVariable String profiles, @RequestBody String data,
@RequestHeader("Content-Type") MediaType type) {
try {
TextEncryptor encryptor = getEncryptor(name, profiles, data);
checkDecryptionPossible(encryptor);
validateEncryptionWeakness(encryptor);
String input = stripFormData(helper.stripPrefix(data), type, true);
String decrypted = encryptor.decrypt(input);
if (logger.isInfoEnabled()) {
logger.info("Decrypted cipher data");
}
return decrypted;
}
catch (IllegalArgumentException | IllegalStateException e) {
if (logger.isErrorEnabled()) {
logger.error("Cannot decrypt key:" + name + ", value:" + data, e);
}
throw new InvalidCipherException();
}
}
private TextEncryptor getEncryptor(String name, String profiles, String data) {
if (encryptorLocator == null) {
if (logger.isDebugEnabled()) {
logger.debug("Text encryptorLocator is null.");
}
throw new KeyNotInstalledException();
}
TextEncryptor encryptor = encryptorLocator.locate(helper.getEncryptorKeys(name, profiles, data));
if (encryptor == null) {
if (logger.isDebugEnabled()) {
logger.debug("TextEncryptor is null.");
}
throw new KeyNotInstalledException();
}
return encryptor;
}
private void validateEncryptionWeakness(TextEncryptor textEncryptor) {
if (textEncryptor.encrypt("FOO").equals("FOO")) {
throw new EncryptionTooWeakException();
}
}
private void checkDecryptionPossible(TextEncryptor textEncryptor) {
if (textEncryptor instanceof RsaSecretEncryptor && !((RsaSecretEncryptor) textEncryptor).canDecrypt()) {
throw new DecryptionNotSupportedException();
}
}
private String stripFormData(String data, MediaType type, boolean cipher) {
if (data.endsWith("=") && !type.equals(MediaType.TEXT_PLAIN)) {
try {
data = URLDecoder.decode(data, "UTF-8");
if (cipher) {
data = data.replace(" ", "+");
}
}
catch (UnsupportedEncodingException e) {
// Really?
}
String candidate = data.substring(0, data.length() - 1);
if (cipher) {
if (data.endsWith("=")) {
if (data.length() / 2 != (data.length() + 1) / 2) {
try {
Hex.decode(candidate);
return candidate;
}
catch (IllegalArgumentException e) {
try {
Base64Utils.decode(candidate.getBytes());
return candidate;
}
catch (IllegalArgumentException ex) {
}
}
}
}
return data;
}
// User posted data with content type form but meant it to be text/plain
data = candidate;
}
return data;
}
@ExceptionHandler(KeyFormatException.class)
public ResponseEntity<Map<String, Object>> keyFormat() {
Map<String, Object> body = new HashMap<>();
body.put("status", "BAD_REQUEST");
body.put("description", "Key data not in correct format (PEM or jks keystore)");
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(KeyNotAvailableException.class)
public ResponseEntity<Map<String, Object>> keyUnavailable() {
Map<String, Object> body = new HashMap<>();
body.put("status", "NOT_FOUND");
body.put("description", "No public key available");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(DecryptionNotSupportedException.class)
public ResponseEntity<Map<String, Object>> decryptionDisabled() {
Map<String, Object> body = new HashMap<>();
body.put("status", "BAD_REQUEST");
body.put("description", "Server-side decryption is not supported");
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(KeyNotInstalledException.class)
public ResponseEntity<Map<String, Object>> notInstalled() {
Map<String, Object> body = new HashMap<>();
body.put("status", "NO_KEY");
body.put("description", "No key was installed for encryption service");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(EncryptionTooWeakException.class)
public ResponseEntity<Map<String, Object>> encryptionTooWeak() {
Map<String, Object> body = new HashMap<>();
body.put("status", "INVALID");
body.put("description", "The encryption algorithm is not strong enough");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(InvalidCipherException.class)
public ResponseEntity<Map<String, Object>> invalidCipher() {
Map<String, Object> body = new HashMap<>();
body.put("status", "INVALID");
body.put("description", "Text not encrypted with this key");
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
}
@SuppressWarnings("serial")
class KeyNotInstalledException extends RuntimeException {
}
@SuppressWarnings("serial")
class KeyNotAvailableException extends RuntimeException {
}
@SuppressWarnings("serial")
class EncryptionTooWeakException extends RuntimeException {
}
@SuppressWarnings("serial")
class InvalidCipherException extends RuntimeException {
}
@SuppressWarnings("serial")
class DecryptionNotSupportedException extends RuntimeException {
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.encryption;
import org.springframework.cloud.config.environment.Environment;
/**
* Service interface for decrypting properties in Environment object.
*
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
*
*/
public interface EnvironmentEncryptor {
Environment decrypt(Environment environment);
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.encryption;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.util.StringUtils;
/**
* Shared helper class for encryption and decryption concerns where the plain text and
* cipher texts can optionally contain prefixes of <code>{name:value}</code> pairs.
* Special treatment is given to the "name" and "profiles" keys, which can be provided by
* the caller, explicitly instead of by the input text strings. This is to support
* independent decryptions using different cryptographic keys for different applications
* and profiles, if needed (this class does not have any crypto features, but it can be
* used by components that do).
*
* @author Dave Syer
*
*/
class EnvironmentPrefixHelper {
/**
* key for the "profiles" prefix pair (usually a Spring profile or comma separated
* value).
*/
private static final String PROFILES = "profiles";
/**
* key for the "name" (Environment name or application name).
*/
private static final String NAME = "name";
/**
* If plain text actually starts with text in the form <code>{name:value}</code>
* prefix it with this to signal the start of the plain text.
*/
private static final String ESCAPE = "{plain}";
/**
* Extract keys for looking up a {@link TextEncryptor} from the input text in the form
* of a prefix of zero or many <code>{name:value}</code> pairs. The name and profiles
* properties are always added to the keys (replacing any provided in the inputs).
* @param name application name
* @param profiles list of profiles
* @param text text to cipher
* @return encryptor keys
*/
public Map<String, String> getEncryptorKeys(String name, String profiles, String text) {
Map<String, String> keys = new LinkedHashMap<String, String>();
text = removeEnvironmentPrefix(text);
keys.put(NAME, name);
keys.put(PROFILES, profiles);
if (text.contains(ESCAPE)) {
text = text.substring(0, text.indexOf(ESCAPE));
}
String[] tokens = StringUtils.split(text, "}");
while (tokens != null) {
String token = tokens[0].trim();
if (token.startsWith("{")) {
String key = "";
String value = "";
if (token.contains(":") && !token.endsWith(":")) {
key = token.substring(1, token.indexOf(":"));
value = token.substring(token.indexOf(":") + 1);
}
else {
key = token.substring(1);
}
keys.put(key, value);
}
text = tokens[1];
tokens = StringUtils.split(text, "}");
}
return keys;
}
/**
* Add a prefix to the input text (usually a cipher) consisting of the
* <code>{name:value}</code> pairs. The "name" and "profiles" keys are special in that
* they are stripped since that information is always available when deriving the keys
* in {@link #getEncryptorKeys(String, String, String)}.
* @param keys name, value pairs
* @param input input to append
* @return prefixed input
*/
public String addPrefix(Map<String, String> keys, String input) {
keys.remove(NAME);
keys.remove(PROFILES);
StringBuilder builder = new StringBuilder();
for (String key : keys.keySet()) {
builder.append("{").append(key).append(":").append(keys.get(key)).append("}");
}
builder.append(input);
return builder.toString();
}
public String stripPrefix(String value) {
if (!value.contains("}")) {
return value;
}
if (value.contains(ESCAPE)) {
return value.substring(value.indexOf(ESCAPE) + ESCAPE.length());
}
return value.replaceFirst("^(\\{.*?:.*?\\})+", "");
}
private String removeEnvironmentPrefix(String input) {
return input.replaceFirst("\\{name:.*\\}", "").replaceFirst("\\{profiles:.*\\}", "");
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.encryption;
import java.util.Map;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.rsa.crypto.RsaAlgorithm;
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
/**
* A {@link TextEncryptorLocator} that pulls RSA key pairs out of a keystore. The input
* map can contain entries for "key" or "secret" or both, or neither. The secret in the
* input map is not, in general, the secret in the keystore, but is dereferenced through a
* {@link SecretLocator} (so for example you can keep a table of encrypted secrets and
* update it separately to the keystore).
*
* @author Dave Syer
*
*/
public class KeyStoreTextEncryptorLocator implements TextEncryptorLocator {
private final static String KEY = "key";
private final static String SECRET = "secret";
private KeyStoreKeyFactory keys;
private String defaultSecret;
private String defaultAlias;
private RsaSecretEncryptor defaultEncryptor;
private SecretLocator secretLocator = new PassthruSecretLocator();
private RsaAlgorithm rsaAlgorithm = RsaAlgorithm.DEFAULT;
private boolean strong = false;
private String salt = "deadbeef";
public KeyStoreTextEncryptorLocator(KeyStoreKeyFactory keys, String defaultSecret, String defaultAlias) {
this.keys = keys;
this.defaultAlias = defaultAlias;
this.defaultSecret = defaultSecret;
}
/**
* @param secretLocator the secretLocator to set
*/
public void setSecretLocator(SecretLocator secretLocator) {
this.secretLocator = secretLocator;
}
public void setRsaAlgorithm(RsaAlgorithm rsaAlgorithm) {
this.rsaAlgorithm = rsaAlgorithm;
}
public void setStrong(boolean strong) {
this.strong = strong;
}
public void setSalt(String salt) {
this.salt = salt;
}
@Override
public TextEncryptor locate(Map<String, String> keys) {
String alias = keys.containsKey(KEY) ? keys.get(KEY) : this.defaultAlias;
String secret = keys.containsKey(SECRET) ? keys.get(SECRET) : this.defaultSecret;
if (alias.equals(this.defaultAlias) && secret.equals(this.defaultSecret)) {
if (this.defaultEncryptor == null) {
this.defaultEncryptor = rsaSecretEncryptor(alias, secret);
}
return this.defaultEncryptor;
}
else {
return rsaSecretEncryptor(alias, secret);
}
}
private RsaSecretEncryptor rsaSecretEncryptor(String alias, String secret) {
return new RsaSecretEncryptor(this.keys.getKeyPair(alias, this.secretLocator.locate(secret)), this.rsaAlgorithm,
this.salt, this.strong);
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2012-2019 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.config.server.encryption;
import java.util.Map;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* @author Dave Syer
*
*/
public class LocatorTextEncryptor implements TextEncryptor {
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
private TextEncryptorLocator locator;
public LocatorTextEncryptor(TextEncryptorLocator locator) {
this.locator = locator;
}
@Override
public String encrypt(String text) {
Map<String, String> keys = this.helper.getEncryptorKeys("configserver", "default", text);
return getLocator().locate(keys).encrypt(this.helper.stripPrefix(text));
}
private TextEncryptorLocator getLocator() {
return this.locator;
}
@Override
public String decrypt(String encryptedText) {
Map<String, String> keys = this.helper.getEncryptorKeys("configserver", "default", encryptedText);
return getLocator().locate(keys).decrypt(this.helper.stripPrefix(encryptedText));
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.encryption;
/**
* @author Dave Syer
*
*/
public class PassthruSecretLocator implements SecretLocator {
@Override
public char[] locate(String secret) {
return secret == null ? new char[0] : secret.toCharArray();
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2002-2019 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.config.server.encryption;
import java.io.IOException;
import java.util.List;
import org.springframework.cloud.config.environment.Environment;
/**
* Interface for decrypting values in plain text files served through
* {@link org.springframework.cloud.config.server.resource.ResourceController}.
*
* @author Sean Stiglitz
*/
public interface ResourceEncryptor {
List<String> getSupportedExtensions();
String decrypt(String text, Environment environment) throws IOException;
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.encryption;
/**
* @author Dave Syer
*
*/
public interface SecretLocator {
char[] locate(String secret);
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.encryption;
import java.util.Map;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* @author Bartosz Wojtkiewicz
* @author Dave Syer
*
*/
public class SingleTextEncryptorLocator implements TextEncryptorLocator {
private TextEncryptor encryptor;
public SingleTextEncryptorLocator(TextEncryptor encryptor) {
this.encryptor = encryptor == null ? Encryptors.noOpText() : encryptor;
}
@Override
public TextEncryptor locate(Map<String, String> keys) {
return this.encryptor;
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.encryption;
import java.util.Map;
import org.springframework.security.crypto.encrypt.TextEncryptor;
/**
* @author Dave Syer
* @author Bartosz Wojtkiewicz
*
*/
public interface TextEncryptorLocator {
TextEncryptor locate(Map<String, String> keys);
}

View File

@@ -1,124 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.encryption.vault;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.encryption.CipherEnvironmentEncryptor;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
import org.springframework.util.ObjectUtils;
import org.springframework.vault.core.VaultKeyValueOperations;
import org.springframework.vault.support.VaultResponse;
/**
* VaultEnvironmentEncryptor that can decrypt property values prefixed with {vault}
* marker.
*
* @author Alexey Zhokhov
*/
public class VaultEnvironmentEncryptor implements EnvironmentEncryptor {
private static final Log logger = LogFactory.getLog(CipherEnvironmentEncryptor.class);
private final VaultKeyValueOperations keyValueTemplate;
public VaultEnvironmentEncryptor(VaultKeyValueOperations keyValueTemplate) {
this.keyValueTemplate = keyValueTemplate;
}
@Override
public Environment decrypt(Environment environment) {
Map<String, VaultResponse> loadedVaultKeys = new HashMap<>();
Environment result = new Environment(environment);
for (PropertySource source : environment.getPropertySources()) {
Map<Object, Object> map = new LinkedHashMap<>(source.getSource());
for (Map.Entry<Object, Object> entry : new LinkedHashSet<>(map.entrySet())) {
Object key = entry.getKey();
String name = key.toString();
if (entry.getValue() != null && entry.getValue().toString().startsWith("{vault}")) {
String value = entry.getValue().toString();
map.remove(key);
try {
value = value.substring("{vault}".length());
if (!value.startsWith(":")) {
throw new RuntimeException("Wrong format");
}
value = value.substring(1);
if (!value.contains("#")) {
throw new RuntimeException("Wrong format");
}
String[] parts = value.split("#");
if (parts.length == 1) {
throw new RuntimeException("Wrong format");
}
if (ObjectUtils.isEmpty(parts[0]) || ObjectUtils.isEmpty(parts[1])) {
throw new RuntimeException("Wrong format");
}
String vaultKey = parts[0];
String vaultParamName = parts[1];
if (!loadedVaultKeys.containsKey(vaultKey)) {
loadedVaultKeys.put(vaultKey, keyValueTemplate.get(vaultKey));
}
VaultResponse vaultResponse = loadedVaultKeys.get(vaultKey);
if (vaultResponse == null || (vaultResponse.getData() == null
|| !vaultResponse.getData().containsKey(vaultParamName))) {
value = null;
}
else {
value = vaultResponse.getData().get(vaultParamName).toString();
}
}
catch (Exception e) {
value = "<n/a>";
name = "invalid." + name;
String message = "Cannot resolve key: " + key + " (" + e.getClass() + ": " + e.getMessage()
+ ")";
if (logger.isDebugEnabled()) {
logger.debug(message, e);
}
else if (logger.isWarnEnabled()) {
logger.warn(message);
}
}
map.put(name, value);
}
}
result.add(new PropertySource(source.getName(), map));
}
return result;
}
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.environment;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.support.AbstractScmAccessor;
import org.springframework.cloud.config.server.support.AbstractScmAccessorProperties;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* @author Dave Syer
*
*/
public abstract class AbstractScmEnvironmentRepository extends AbstractScmAccessor
implements EnvironmentRepository, SearchPathLocator, Ordered {
private EnvironmentCleaner cleaner = new EnvironmentCleaner();
private int order = Ordered.LOWEST_PRECEDENCE;
private final ObservationRegistry observationRegistry;
public AbstractScmEnvironmentRepository(ConfigurableEnvironment environment,
ObservationRegistry observationRegistry) {
super(environment);
this.observationRegistry = observationRegistry;
}
public AbstractScmEnvironmentRepository(ConfigurableEnvironment environment,
AbstractScmAccessorProperties properties, ObservationRegistry observationRegistry) {
super(environment, properties);
this.order = properties.getOrder();
this.observationRegistry = observationRegistry;
}
@Override
public synchronized Environment findOne(String application, String profile, String label) {
return findOne(application, profile, label, false);
}
@Override
public synchronized Environment findOne(String application, String profile, String label, boolean includeOrigin) {
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
new NativeEnvironmentProperties(), this.observationRegistry);
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
Environment result = delegate.findOne(application, profile, "", includeOrigin);
result.setVersion(locations.getVersion());
result.setLabel(label);
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(), getUri());
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.config.client.ConfigClientProperties.STATE_HEADER;
/**
* @author Spencer Gibb
* @author Mark Paluch
* @author Haroun Pacquee
* @author Haytham Mohamed
* @author Scott Frederick
*/
public abstract class AbstractVaultEnvironmentRepository implements EnvironmentRepository, Ordered {
private static Log log = LogFactory.getLog(AbstractVaultEnvironmentRepository.class);
// TODO: move to watchState:String on findOne?
protected final ObjectProvider<HttpServletRequest> request;
protected final EnvironmentWatch watch;
/**
* The key in vault shared by all applications. Defaults to application. Set to empty
* to disable.
*/
protected String defaultKey;
/**
* Vault profile separator. Defaults to comma.
*/
@NotEmpty
protected String profileSeparator;
protected int order;
public AbstractVaultEnvironmentRepository(ObjectProvider<HttpServletRequest> request, EnvironmentWatch watch,
VaultEnvironmentProperties properties) {
this.defaultKey = properties.getDefaultKey();
this.profileSeparator = properties.getProfileSeparator();
this.order = properties.getOrder();
this.request = request;
this.watch = watch;
}
@Override
public Environment findOne(String application, String profile, String label) {
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
List<String> scrubbedProfiles = scrubProfiles(profiles);
List<String> keys = findKeys(application, scrubbedProfiles);
Environment environment = new Environment(application, profiles, label, null, getWatchState());
for (String key : keys) {
// read raw 'data' key from vault
String data = read(key);
if (data != null) {
// data is in json format of which, yaml is a superset, so parse
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ByteArrayResource(data.getBytes()));
Properties properties = yaml.getObject();
if (!properties.isEmpty()) {
environment.add(new PropertySource("vault:" + key, properties));
}
}
}
return environment;
}
protected abstract String read(String key);
private String getWatchState() {
HttpServletRequest servletRequest = this.request.getIfAvailable();
if (servletRequest != null) {
try {
String state = servletRequest.getHeader(STATE_HEADER);
return this.watch.watch(state);
}
catch (IllegalStateException e) {
log.debug("Could not get state.", e);
return null;
}
}
return null;
}
private List<String> findKeys(String application, List<String> profiles) {
List<String> keys = new ArrayList<>();
if (StringUtils.hasText(this.defaultKey) && !this.defaultKey.equals(application)) {
keys.add(this.defaultKey);
addProfiles(keys, this.defaultKey, profiles);
}
// application may have comma-separated list of names
String[] applications = StringUtils.commaDelimitedListToStringArray(application);
for (String app : applications) {
keys.add(app);
addProfiles(keys, app, profiles);
}
Collections.reverse(keys);
return keys;
}
private List<String> scrubProfiles(String[] profiles) {
List<String> scrubbedProfiles = new ArrayList<>(Arrays.asList(profiles));
scrubbedProfiles.remove("default");
return scrubbedProfiles;
}
private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + this.profileSeparator + profile);
}
}
public void setDefaultKey(String defaultKey) {
this.defaultKey = defaultKey;
}
public void setProfileSeparator(String profileSeparator) {
this.profileSeparator = profileSeparator;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2018-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 org.springframework.cloud.config.server.environment;
import java.net.URI;
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
import software.amazon.awssdk.regions.Region;
import org.springframework.util.StringUtils;
abstract class AwsClientBuilderConfigurer {
private AwsClientBuilderConfigurer() {
}
static void configureClientBuilder(AwsClientBuilder<?, ?> clientBuilder, String region, String endpoint) {
if (StringUtils.hasText(region)) {
clientBuilder.region(Region.of(region));
if (StringUtils.hasText(endpoint)) {
clientBuilder.endpointOverride(URI.create(endpoint));
}
}
}
}

View File

@@ -1,177 +0,0 @@
/*
* Copyright 2018-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 org.springframework.cloud.config.server.environment;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
import org.springframework.validation.annotation.Validated;
/**
* @author Iulian Antohe
*/
@Validated
@ConfigurationProperties("spring.cloud.config.server.awsparamstore")
public class AwsParameterStoreEnvironmentProperties implements EnvironmentRepositoryProperties {
static final String DEFAULT_PATH_SEPARATOR = "/";
private static final String DEFAULT_ORIGIN = "aws:ssm:parameter:";
private static final String DEFAULT_PREFIX = DEFAULT_PATH_SEPARATOR + "config";
private static final String DEFAULT_PROFILE_SEPARATOR = "-";
/**
* The order of the environment repository.
*/
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* The region to be used by the AWS Parameter Store client.
*/
private String region;
/**
* The service endpoint to be used by the AWS Parameter Store client.
*/
private String endpoint;
/**
* Prefix indicating the property's origin. Defaults to "aws:ssm:parameter:".
*/
@NotNull
private String origin = DEFAULT_ORIGIN;
/**
* Prefix indicating first level for every property loaded from the AWS Parameter
* Store. Value must start with a forward slash followed by one or more valid path
* segments or be empty. Defaults to "/config".
*/
@NotNull
@Pattern(regexp = "(/[a-zA-Z0-9.\\-_]+)*")
private String prefix = DEFAULT_PREFIX;
/**
* String that separates an appended profile from the context name. Note that an AWS
* parameter name can only contain dots, dashes and underscores next to alphanumeric
* characters. Defaults to "-".
*/
@NotBlank
@Pattern(regexp = "[a-zA-Z0-9.\\-_/]+")
private String profileSeparator = DEFAULT_PROFILE_SEPARATOR;
/**
* Flag to indicate the retrieval of all AWS parameters within a hierarchy. Defaults
* to "true".
*/
private boolean recursive = true;
/**
* Flag to indicate the retrieval of all AWS parameters in a hierarchy with their
* value decrypted. Defaults to "true".
*/
private boolean decryptValues = true;
/**
* The maximum number of items to return for an AWS Parameter Store API call. Defaults
* to "10".
*/
@Min(1)
@Max(10)
private int maxResults = 10;
public int getOrder() {
return order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getProfileSeparator() {
return profileSeparator;
}
public void setProfileSeparator(String profileSeparator) {
this.profileSeparator = profileSeparator;
}
public boolean isRecursive() {
return recursive;
}
public void setRecursive(boolean recursive) {
this.recursive = recursive;
}
public boolean isDecryptValues() {
return decryptValues;
}
public void setDecryptValues(boolean decryptValues) {
this.decryptValues = decryptValues;
}
public int getMaxResults() {
return maxResults;
}
public void setMaxResults(int maxResults) {
this.maxResults = maxResults;
}
}

View File

@@ -1,176 +0,0 @@
/*
* Copyright 2013-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 org.springframework.cloud.config.server.environment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest;
import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse;
import software.amazon.awssdk.services.ssm.model.Parameter;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.config.server.environment.AwsParameterStoreEnvironmentProperties.DEFAULT_PATH_SEPARATOR;
/**
* @author Iulian Antohe
*/
public class AwsParameterStoreEnvironmentRepository implements EnvironmentRepository, Ordered {
private final SsmClient awsSsmClient;
private final ConfigServerProperties configServerProperties;
private final AwsParameterStoreEnvironmentProperties environmentProperties;
private final int order;
public AwsParameterStoreEnvironmentRepository(SsmClient awsSsmClient, ConfigServerProperties configServerProperties,
AwsParameterStoreEnvironmentProperties environmentProperties) {
this.awsSsmClient = awsSsmClient;
this.configServerProperties = configServerProperties;
this.environmentProperties = environmentProperties;
this.order = environmentProperties.getOrder();
}
@Override
public Environment findOne(String application, String profile, String label) {
if (!StringUtils.hasLength(application)) {
application = configServerProperties.getDefaultApplicationName();
}
if (!StringUtils.hasLength(profile)) {
profile = configServerProperties.getDefaultProfile();
}
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
Environment result = new Environment(application, profiles, label, null, null);
Set<String> paths = buildParameterPaths(application, profiles);
List<PropertySource> propertySources = getPropertySources(paths);
result.addAll(propertySources);
return result;
}
private Set<String> buildParameterPaths(String application, String[] profiles) {
Set<String> result = new LinkedHashSet<>();
String prefix = environmentProperties.getPrefix();
String defaultApplication = configServerProperties.getDefaultApplicationName();
String profileSeparator = environmentProperties.getProfileSeparator();
String defaultProfile = configServerProperties.getDefaultProfile();
List<String> orderedProfiles = Stream.concat(Arrays.stream(profiles).filter(p -> !p.equals(defaultProfile)),
Arrays.stream(new String[] { defaultProfile })).collect(Collectors.toList());
if (application.equals(defaultApplication)) {
for (String profile : orderedProfiles) {
result.add(prefix + DEFAULT_PATH_SEPARATOR + defaultApplication + profileSeparator + profile
+ DEFAULT_PATH_SEPARATOR);
}
}
else {
for (String profile : orderedProfiles) {
result.add(prefix + DEFAULT_PATH_SEPARATOR + application + profileSeparator + profile
+ DEFAULT_PATH_SEPARATOR);
result.add(prefix + DEFAULT_PATH_SEPARATOR + defaultApplication + profileSeparator + profile
+ DEFAULT_PATH_SEPARATOR);
}
result.add(prefix + DEFAULT_PATH_SEPARATOR + application + DEFAULT_PATH_SEPARATOR);
}
result.add(prefix + DEFAULT_PATH_SEPARATOR + defaultApplication + DEFAULT_PATH_SEPARATOR);
return result;
}
private List<PropertySource> getPropertySources(Set<String> parameterPaths) {
List<PropertySource> result = new ArrayList<>();
for (String path : parameterPaths) {
String name = environmentProperties.getOrigin() + path;
Map<String, String> source = getPropertiesByParameterPath(path);
if (!source.isEmpty()) {
result.add(new PropertySource(name, source));
}
}
Map<String, String> overrides = configServerProperties.getOverrides();
if (!overrides.isEmpty()) {
result.add(0, new PropertySource("overrides", overrides));
}
return result;
}
private Map<String, String> getPropertiesByParameterPath(String path) {
Map<String, String> result = new HashMap<>();
GetParametersByPathRequest request = GetParametersByPathRequest.builder().path(path)
.recursive(environmentProperties.isRecursive()).withDecryption(environmentProperties.isDecryptValues())
.maxResults(environmentProperties.getMaxResults()).build();
GetParametersByPathResponse response = awsSsmClient.getParametersByPath(request);
if (response != null) {
addParametersToProperties(path, response.parameters(), result);
while (StringUtils.hasLength(response.nextToken())) {
response = awsSsmClient
.getParametersByPath(request.toBuilder().nextToken(response.nextToken()).build());
addParametersToProperties(path, response.parameters(), result);
}
}
return result;
}
private void addParametersToProperties(String path, List<Parameter> parameters, Map<String, String> properties) {
for (Parameter parameter : parameters) {
String name = StringUtils.delete(parameter.name(), path).replace(DEFAULT_PATH_SEPARATOR, ".");
properties.put(name, parameter.value());
}
}
@Override
public int getOrder() {
return order;
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2018-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 org.springframework.cloud.config.server.environment;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.SsmClientBuilder;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import static org.springframework.cloud.config.server.environment.AwsClientBuilderConfigurer.configureClientBuilder;
/**
* @author Iulian Antohe
*/
public class AwsParameterStoreEnvironmentRepositoryFactory implements
EnvironmentRepositoryFactory<AwsParameterStoreEnvironmentRepository, AwsParameterStoreEnvironmentProperties> {
private final ConfigServerProperties configServerProperties;
public AwsParameterStoreEnvironmentRepositoryFactory(ConfigServerProperties configServerProperties) {
this.configServerProperties = configServerProperties;
}
@Override
public AwsParameterStoreEnvironmentRepository build(AwsParameterStoreEnvironmentProperties environmentProperties) {
SsmClientBuilder clientBuilder = SsmClient.builder();
configureClientBuilder(clientBuilder, environmentProperties.getRegion(), environmentProperties.getEndpoint());
SsmClient client = clientBuilder.build();
return new AwsParameterStoreEnvironmentRepository(client, configServerProperties, environmentProperties);
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
/**
* @author Clay McCoy
*/
@ConfigurationProperties("spring.cloud.config.server.awss3")
public class AwsS3EnvironmentProperties implements EnvironmentRepositoryProperties {
/**
* AWS region that contains config.
*/
private String region;
/**
* Adds the ability to override the baseUrl of the s3 client.
*/
private String endpoint;
/**
* Name of the S3 bucket that contains config.
*/
private String bucket;
private int order;
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getBucket() {
return bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
public int getOrder() {
return order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -1,296 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.core.Ordered;
import org.springframework.core.io.InputStreamResource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* @author Clay McCoy
* @author Scott Frederick
* @author Daniel Aiken
*/
public class AwsS3EnvironmentRepository implements EnvironmentRepository, Ordered, SearchPathLocator {
private static final Log LOG = LogFactory.getLog(AwsS3EnvironmentRepository.class);
private static final String AWS_S3_RESOURCE_SCHEME = "s3://";
private static final String PATH_SEPARATOR = "/";
private final S3Client s3Client;
private final String bucketName;
private final ConfigServerProperties serverProperties;
protected int order = Ordered.LOWEST_PRECEDENCE;
public AwsS3EnvironmentRepository(S3Client s3Client, String bucketName, ConfigServerProperties server) {
this.s3Client = s3Client;
this.bucketName = bucketName;
this.serverProperties = server;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public Environment findOne(String specifiedApplication, String specifiedProfiles, String specifiedLabel) {
final String application = ObjectUtils.isEmpty(specifiedApplication)
? serverProperties.getDefaultApplicationName() : specifiedApplication;
final String profiles = ObjectUtils.isEmpty(specifiedProfiles) ? serverProperties.getDefaultProfile()
: specifiedProfiles;
final String label = ObjectUtils.isEmpty(specifiedLabel) ? serverProperties.getDefaultLabel() : specifiedLabel;
String[] profileArray = parseProfiles(profiles);
List<String> apps = Arrays.asList(StringUtils.commaDelimitedListToStringArray(application.replace(" ", "")));
if (!apps.contains(serverProperties.getDefaultApplicationName())) {
apps = new ArrayList<>(apps);
apps.add(serverProperties.getDefaultApplicationName());
}
final Environment environment = new Environment(application, profileArray);
environment.setLabel(label);
for (String profile : profileArray) {
for (String app : apps) {
addPropertySource(environment, app, profile, label);
}
}
// Add propertysources without profiles as well
for (String app : apps) {
addPropertySource(environment, app, null, label);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Returning Environment: " + environment);
}
return environment;
}
private void addPropertySource(Environment environment, String app, String profile, String label) {
S3ConfigFile s3ConfigFile = getS3ConfigFile(app, profile, label);
if (s3ConfigFile != null) {
environment.setVersion(s3ConfigFile.getVersion());
final Properties config = s3ConfigFile.read();
config.putAll(serverProperties.getOverrides());
StringBuilder propertySourceName = new StringBuilder().append("s3:").append(app);
if (profile != null) {
propertySourceName.append("-").append(profile);
}
PropertySource propertySource = new PropertySource(propertySourceName.toString(), config);
if (LOG.isDebugEnabled()) {
LOG.debug("Adding property source to environment " + propertySource);
}
environment.add(propertySource);
}
}
private String[] parseProfiles(String profiles) {
return StringUtils.commaDelimitedListToStringArray(profiles);
}
private S3ConfigFile getS3ConfigFile(String application, String profile, String label) {
String objectKeyPrefix = buildObjectKeyPrefix(application, profile, label);
return getS3ConfigFile(objectKeyPrefix);
}
private String buildObjectKeyPrefix(String application, String profile, String label) {
StringBuilder objectKeyPrefix = new StringBuilder();
if (!ObjectUtils.isEmpty(label)) {
objectKeyPrefix.append(label).append(PATH_SEPARATOR);
}
objectKeyPrefix.append(application);
if (!ObjectUtils.isEmpty(profile)) {
objectKeyPrefix.append("-").append(profile);
}
return objectKeyPrefix.toString();
}
private S3ConfigFile getS3ConfigFile(String keyPrefix) {
if (LOG.isDebugEnabled()) {
LOG.debug("Getting S3 config file for prefix " + keyPrefix);
}
try {
final ResponseInputStream<GetObjectResponse> responseInputStream = getObject(keyPrefix + ".properties");
return new PropertyS3ConfigFile(responseInputStream.response().versionId(), responseInputStream);
}
catch (Exception eProperties) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Did not find " + keyPrefix + ".properties. Trying yml extension", eProperties);
}
final ResponseInputStream<GetObjectResponse> responseInputStream = getObject(keyPrefix + ".yml");
return new YamlS3ConfigFile(responseInputStream.response().versionId(), responseInputStream);
}
catch (Exception eYml) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Did not find " + keyPrefix + ".yml. Trying yaml extension", eYml);
}
final ResponseInputStream<GetObjectResponse> responseInputStream = getObject(keyPrefix + ".yaml");
return new YamlS3ConfigFile(responseInputStream.response().versionId(), responseInputStream);
}
catch (Exception eYaml) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Did not find " + keyPrefix + ".yaml. Trying json extension", eYaml);
}
final ResponseInputStream<GetObjectResponse> responseInputStream = getObject(
keyPrefix + ".json");
return new JsonS3ConfigFile(responseInputStream.response().versionId(), responseInputStream);
}
catch (Exception eJson) {
if (LOG.isDebugEnabled()) {
LOG.debug("Did not find S3 config file with properties, yml, yaml, or json extension for "
+ keyPrefix, eJson);
}
return null;
}
}
}
}
}
private ResponseInputStream<GetObjectResponse> getObject(String key) throws Exception {
if (LOG.isDebugEnabled()) {
LOG.debug("Getting object with key " + key);
}
return s3Client.getObject(GetObjectRequest.builder().bucket(bucketName).key(key).build());
}
@Override
public Locations getLocations(String application, String profiles, String label) {
StringBuilder baseLocation = new StringBuilder(AWS_S3_RESOURCE_SCHEME + bucketName + PATH_SEPARATOR);
if (!StringUtils.hasText(label) && StringUtils.hasText(serverProperties.getDefaultLabel())) {
label = serverProperties.getDefaultLabel();
}
// both the passed in label and the default label property could be null
if (StringUtils.hasText(label)) {
baseLocation.append(label);
}
return new Locations(application, profiles, label, null, new String[] { baseLocation.toString() });
}
}
abstract class S3ConfigFile {
protected static final Log LOG = LogFactory.getLog(S3ConfigFile.class);
private final String version;
protected S3ConfigFile(String version) {
this.version = version;
}
String getVersion() {
return version;
}
abstract Properties read();
}
class PropertyS3ConfigFile extends S3ConfigFile {
final InputStream inputStream;
PropertyS3ConfigFile(String version, InputStream inputStream) {
super(version);
this.inputStream = inputStream;
}
@Override
public Properties read() {
Properties props = new Properties();
try (InputStream in = inputStream) {
props.load(in);
}
catch (IOException e) {
LOG.warn("Exception thrown when reading property file", e);
throw new IllegalStateException("Cannot load environment", e);
}
return props;
}
}
class YamlS3ConfigFile extends S3ConfigFile {
final InputStream inputStream;
YamlS3ConfigFile(String version, InputStream inputStream) {
super(version);
this.inputStream = inputStream;
}
@Override
public Properties read() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
try (InputStream in = inputStream) {
yaml.setResources(new InputStreamResource(in));
return yaml.getObject();
}
catch (IOException e) {
LOG.warn("Could not read YAML file", e);
throw new IllegalStateException("Cannot load environment", e);
}
}
}
class JsonS3ConfigFile extends YamlS3ConfigFile {
// YAML is a superset of JSON, which means you can parse JSON with a YAML parser
JsonS3ConfigFile(String version, InputStream inputStream) {
super(version, inputStream);
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import static org.springframework.cloud.config.server.environment.AwsClientBuilderConfigurer.configureClientBuilder;
public class AwsS3EnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<AwsS3EnvironmentRepository, AwsS3EnvironmentProperties> {
final private ConfigServerProperties server;
public AwsS3EnvironmentRepositoryFactory(ConfigServerProperties server) {
this.server = server;
}
@Override
public AwsS3EnvironmentRepository build(AwsS3EnvironmentProperties environmentProperties) {
final S3ClientBuilder clientBuilder = S3Client.builder();
configureClientBuilder(clientBuilder, environmentProperties.getRegion(), environmentProperties.getEndpoint());
final S3Client client = clientBuilder.build();
AwsS3EnvironmentRepository repository = new AwsS3EnvironmentRepository(client,
environmentProperties.getBucket(), server);
repository.setOrder(environmentProperties.getOrder());
return repository;
}
}

View File

@@ -1,141 +0,0 @@
/*
* Copyright 2018-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 org.springframework.cloud.config.server.environment;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Tejas Pandilwar
*/
@ConfigurationProperties("spring.cloud.config.server.aws-secretsmanager")
public class AwsSecretsManagerEnvironmentProperties implements EnvironmentRepositoryProperties {
static final String DEFAULT_PATH_SEPARATOR = "/";
private static final String DEFAULT_PREFIX = DEFAULT_PATH_SEPARATOR + "secret";
private static final String DEFAULT_PROFILE_SEPARATOR = "-";
private static final String DEFAULT_ORIGIN = "aws:secrets:";
/**
* The region to be used by AWS Secrets Manager client.
*/
private String region;
/**
* The endpoint to be used by AWS Secrets Manager client. This can be used to specify
* an alternate endpoint for the API requests.
*/
private String endpoint;
/**
* The default staging label to be used to fetch the secret values. If unset, an
* active version of the secret will be fetched (AWSCURRENT).
*/
private String defaultLabel;
/**
* The order of the environment repository.
*/
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* Prefix indicating first level for every property loaded from AWS Secrets Manager.
* Value must start with a forward slash followed by a valid path segment or be empty.
* Defaults to "/secret".
*/
@NotNull
@Pattern(regexp = "(/[a-zA-Z0-9.\\-_]+)*")
private String prefix = DEFAULT_PREFIX;
/**
* String that separates profile from the application name.
*/
@NotNull
@Pattern(regexp = "[a-zA-Z0-9.\\-_]+")
private String profileSeparator = DEFAULT_PROFILE_SEPARATOR;
/**
* Prefix which indicates the origin of the property. Defaults to "aws:secrets:".
*/
@NotNull
private String origin = DEFAULT_ORIGIN;
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getDefaultLabel() {
return defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
public int getOrder() {
return order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getProfileSeparator() {
return profileSeparator;
}
public void setProfileSeparator(String profileSeparator) {
this.profileSeparator = profileSeparator;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright 2018-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 org.springframework.cloud.config.server.environment;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.core.Ordered;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.config.server.environment.AwsSecretsManagerEnvironmentProperties.DEFAULT_PATH_SEPARATOR;
/**
* @author Tejas Pandilwar
* @author KNV Srinivas
*/
public class AwsSecretsManagerEnvironmentRepository implements EnvironmentRepository, Ordered {
private static final Log log = LogFactory.getLog(AwsSecretsManagerEnvironmentRepository.class);
private final ObjectMapper objectMapper;
private final SecretsManagerClient awsSmClient;
private final ConfigServerProperties configServerProperties;
private final AwsSecretsManagerEnvironmentProperties environmentProperties;
private final int order;
public AwsSecretsManagerEnvironmentRepository(SecretsManagerClient awsSmClient,
ConfigServerProperties configServerProperties,
AwsSecretsManagerEnvironmentProperties environmentProperties) {
this.awsSmClient = awsSmClient;
this.configServerProperties = configServerProperties;
this.environmentProperties = environmentProperties;
this.order = environmentProperties.getOrder();
this.objectMapper = new ObjectMapper();
}
@Override
public Environment findOne(String application, String profileList, String label) {
final String defaultApplication = configServerProperties.getDefaultApplicationName();
final String defaultProfile = configServerProperties.getDefaultProfile();
final String defaultLabel = environmentProperties.getDefaultLabel();
if (ObjectUtils.isEmpty(application)) {
application = defaultApplication;
}
if (ObjectUtils.isEmpty(profileList)) {
profileList = defaultProfile;
}
if (StringUtils.isEmpty(label)) {
label = defaultLabel;
}
String[] profiles = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(profileList));
Environment environment = new Environment(application, profiles, label, null, null);
Map<String, String> overrides = configServerProperties.getOverrides();
if (!overrides.isEmpty()) {
environment.add(new PropertySource("overrides", overrides));
}
for (String profile : profiles) {
addPropertySource(environment, application, profile, label);
if (!defaultApplication.equals(application)) {
addPropertySource(environment, defaultApplication, profile, label);
}
}
if (!Arrays.asList(profiles).contains(defaultProfile)) {
addPropertySource(environment, application, defaultProfile, label);
}
if (!Arrays.asList(profiles).contains(defaultProfile) && !defaultApplication.equals(application)) {
addPropertySource(environment, defaultApplication, defaultProfile, label);
}
if (!defaultApplication.equals(application)) {
addPropertySource(environment, application, null, label);
}
addPropertySource(environment, defaultApplication, null, label);
return environment;
}
private void addPropertySource(Environment environment, String application, String profile, String label) {
String path = buildPath(application, profile);
Map<Object, Object> properties = findProperties(path, label);
if (!properties.isEmpty()) {
environment.add(new PropertySource(environmentProperties.getOrigin() + path, properties));
}
}
private String buildPath(String application, String profile) {
String prefix = environmentProperties.getPrefix();
String profileSeparator = environmentProperties.getProfileSeparator();
if (profile == null || profile.isEmpty()) {
return prefix + DEFAULT_PATH_SEPARATOR + application + DEFAULT_PATH_SEPARATOR;
}
else {
return prefix + DEFAULT_PATH_SEPARATOR + application + profileSeparator + profile + DEFAULT_PATH_SEPARATOR;
}
}
private Map<Object, Object> findProperties(String path, String label) {
Map<Object, Object> properties = new HashMap<>();
GetSecretValueRequest request = GetSecretValueRequest.builder().secretId(path).versionStage(label).build();
try {
GetSecretValueResponse response = awsSmClient.getSecretValue(request);
if (response != null) {
Map<String, Object> secretMap = objectMapper.readValue(response.secretString(),
new TypeReference<Map<String, Object>>() {
});
for (Map.Entry<String, Object> secretEntry : secretMap.entrySet()) {
properties.put(secretEntry.getKey(), secretEntry.getValue());
}
}
}
catch (ResourceNotFoundException | IOException e) {
log.debug(String.format(
"Skip adding propertySource. Unable to load secrets from AWS Secrets Manager for secretId=%s",
path), e);
}
return properties;
}
@Override
public int getOrder() {
return order;
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2018-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 org.springframework.cloud.config.server.environment;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import static org.springframework.cloud.config.server.environment.AwsClientBuilderConfigurer.configureClientBuilder;
/**
* @author Tejas Pandilwar
*/
public class AwsSecretsManagerEnvironmentRepositoryFactory implements
EnvironmentRepositoryFactory<AwsSecretsManagerEnvironmentRepository, AwsSecretsManagerEnvironmentProperties> {
private final ConfigServerProperties configServerProperties;
public AwsSecretsManagerEnvironmentRepositoryFactory(ConfigServerProperties configServerProperties) {
this.configServerProperties = configServerProperties;
}
@Override
public AwsSecretsManagerEnvironmentRepository build(AwsSecretsManagerEnvironmentProperties environmentProperties) {
SecretsManagerClientBuilder clientBuilder = SecretsManagerClient.builder();
configureClientBuilder(clientBuilder, environmentProperties.getRegion(), environmentProperties.getEndpoint());
SecretsManagerClient client = clientBuilder.build();
return new AwsSecretsManagerEnvironmentRepository(client, configServerProperties, environmentProperties);
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.core.OrderComparator;
/**
* An {@link EnvironmentRepository} composed of multiple ordered
* {@link EnvironmentRepository}s.
*
* @author Ryan Baxter
*/
public class CompositeEnvironmentRepository implements EnvironmentRepository {
Log log = LogFactory.getLog(getClass());
protected List<EnvironmentRepository> environmentRepositories;
protected boolean failOnError;
/**
* Creates a new {@link CompositeEnvironmentRepository}.
* @param environmentRepositories The list of {@link EnvironmentRepository}s to create
* the composite from.
* @param observationRegistry observation registry
* @param failOnError whether to throw an exception if there is an error.
*/
public CompositeEnvironmentRepository(List<EnvironmentRepository> environmentRepositories,
ObservationRegistry observationRegistry, boolean failOnError) {
// Sort the environment repositories by the priority
Collections.sort(environmentRepositories, OrderComparator.INSTANCE);
this.environmentRepositories = observationRegistry.isNoop() ? environmentRepositories
: environmentRepositories.stream()
.map(e -> ObservationEnvironmentRepositoryWrapper.wrap(observationRegistry, e))
.collect(Collectors.toList());
this.failOnError = failOnError;
}
/**
* Creates a new {@link CompositeEnvironmentRepository}.
* @param environmentRepositories The list of {@link EnvironmentRepository}s to create
* the composite from.
* @param failOnError whether to throw an exception if there is an error.
*/
public CompositeEnvironmentRepository(List<EnvironmentRepository> environmentRepositories, boolean failOnError) {
// Sort the environment repositories by the priority
this(environmentRepositories, ObservationRegistry.NOOP, failOnError);
}
@Override
public Environment findOne(String application, String profile, String label) {
return findOne(application, profile, label, false);
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
Environment env = new Environment(application, new String[] { profile }, label, null, null);
if (this.environmentRepositories.size() == 1) {
Environment envRepo = this.environmentRepositories.get(0).findOne(application, profile, label,
includeOrigin);
env.addAll(envRepo.getPropertySources());
env.setVersion(envRepo.getVersion());
env.setState(envRepo.getState());
}
else {
for (EnvironmentRepository repo : environmentRepositories) {
try {
env.addAll(repo.findOne(application, profile, label, includeOrigin).getPropertySources());
}
catch (Exception e) {
if (failOnError) {
throw e;
}
else {
log.info("Error adding environment for " + repo);
}
}
}
}
return env;
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
/**
* @author Scott Frederick
*/
public interface ConfigTokenProvider {
String getToken();
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
/**
* @author Dylan Roberts
*/
public interface ConfigurableHttpConnectionFactory extends HttpConnectionFactory {
void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties) throws Exception;
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.util.ArrayList;
import java.util.List;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("spring.cloud.config.server.consul.watch")
public class ConsulEnvironmentWatch implements EnvironmentWatch {
/**
* Consul index header name.
*/
public static final String CONSUL_INDEX = "X-Consul-Index";
/**
* Consul token header name.
*/
public static final String CONSUL_TOKEN = "X-Consul-Token";
/**
* Response types.
*/
public static final ParameterizedTypeReference<List<String>> RESPONSE_TYPE = new ParameterizedTypeReference<List<String>>() {
};
private static final String WATCH_URL = "{scheme}://{host}:{port}/v1/kv/{path}?keys&recurse&wait={wait}&index={index}";
private static Log LOG = LogFactory.getLog(ConsulEnvironmentWatch.class);
private RestTemplate restTemplate = new RestTemplate();
/** Consul agent scheme. Defaults to 'http'. */
@NotNull
private String scheme = "http";
/** Consul agent hostname. Defaults to 'localhost'. */
@NotNull
private String host = "localhost";
/** Consul agent port. Defaults to '8500'. */
@NotNull
private int port = 8500;
/** Path to watch in consul key/value store. */
@NotNull
private String path;
/** Consul wait value (eg, 3m or 30s). */
@NotNull
private String wait = "3m";
/** Consul ACL token. */
private String token;
@Override
public String watch(String state) {
ArrayList<String> params = new ArrayList<>();
params.add(this.scheme);
params.add(this.host);
params.add(String.valueOf(this.port));
params.add(this.path);
params.add(this.wait);
params.add(StringUtils.hasText(state) ? state : "");
try {
HttpHeaders headers = new HttpHeaders();
if (StringUtils.hasText(this.token)) {
headers.add(CONSUL_TOKEN, this.token);
}
HttpEntity<Object> request = new HttpEntity<>(headers);
ResponseEntity<List<String>> response = this.restTemplate.exchange(WATCH_URL, HttpMethod.GET, request,
RESPONSE_TYPE, params.toArray());
if (response.getStatusCode().is2xxSuccessful()) {
String consulIndex = response.getHeaders().getFirst(CONSUL_INDEX);
return consulIndex;
}
}
catch (HttpStatusCodeException e) {
if (!e.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
LOG.error("Unable to watch consul path " + this.path, e);
return null;
}
}
// TODO: error handling?
return null;
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
public void setPath(String path) {
this.path = path;
if (this.path.startsWith("/")) {
this.path = this.path.substring(1);
}
}
public void setWait(String wait) {
this.wait = wait;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Alberto C. Ríos
*/
@ConfigurationProperties("spring.cloud.config.server.credhub")
public class CredhubEnvironmentProperties implements EnvironmentRepositoryProperties {
private int order = Ordered.LOWEST_PRECEDENCE;
public int getOrder() {
return this.order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.util.Arrays;
import java.util.Map;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.SimpleCredentialName;
import org.springframework.credhub.support.json.JsonCredential;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static java.util.stream.Collectors.toMap;
/**
* @author Alberto C. Ríos
* @author KNV Srinivas
*/
public class CredhubEnvironmentRepository implements EnvironmentRepository, Ordered {
private CredHubOperations credHubOperations;
private static final String DEFAULT_PROFILE = "default";
private static final String DEFAULT_LABEL = "master";
private static final String DEFAULT_APPLICATION = "application";
private int order = Ordered.LOWEST_PRECEDENCE;
public CredhubEnvironmentRepository(CredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
@Override
public Environment findOne(String application, String profilesList, String label) {
if (ObjectUtils.isEmpty(profilesList)) {
profilesList = DEFAULT_PROFILE;
}
if (ObjectUtils.isEmpty(label)) {
label = DEFAULT_LABEL;
}
String[] profiles = StringUtils.commaDelimitedListToStringArray(profilesList);
Environment environment = new Environment(application, profiles, label, null, null);
for (String profile : profiles) {
environment.add(new PropertySource("credhub-" + application + "-" + profile + "-" + label,
findProperties(application, profile, label)));
if (!DEFAULT_APPLICATION.equals(application)) {
addDefaultPropertySource(environment, DEFAULT_APPLICATION, profile, label);
}
}
if (!Arrays.asList(profiles).contains(DEFAULT_PROFILE)) {
addDefaultPropertySource(environment, application, DEFAULT_PROFILE, label);
}
if (!Arrays.asList(profiles).contains(DEFAULT_PROFILE) && !DEFAULT_APPLICATION.equals(application)) {
addDefaultPropertySource(environment, DEFAULT_APPLICATION, DEFAULT_PROFILE, label);
}
return environment;
}
private void addDefaultPropertySource(Environment environment, String application, String profile, String label) {
Map<Object, Object> properties = findProperties(application, profile, label);
if (!properties.isEmpty()) {
PropertySource propertySource = new PropertySource("credhub-" + application + "-" + profile + "-" + label,
properties);
environment.add(propertySource);
}
}
private Map<Object, Object> findProperties(String application, String profile, String label) {
String path = "/" + application + "/" + profile + "/" + label;
return this.credHubOperations.credentials().findByPath(path).stream()
.map(credentialSummary -> credentialSummary.getName().getName())
.map(name -> this.credHubOperations.credentials().getByName(new SimpleCredentialName(name),
JsonCredential.class))
.map(CredentialDetails::getValue).flatMap(jsonCredential -> jsonCredential.entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b));
}
@Override
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.credhub.core.CredHubOperations;
/**
* @author Alberto C. Ríos
* @author KNV Srinivas
*/
public class CredhubEnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<CredhubEnvironmentRepository, CredhubEnvironmentProperties> {
private CredHubOperations credhubOperations;
public CredhubEnvironmentRepositoryFactory(CredHubOperations credhubOperations) {
this.credhubOperations = credhubOperations;
}
@Override
public CredhubEnvironmentRepository build(CredhubEnvironmentProperties environmentProperties) {
CredhubEnvironmentRepository repository = new CredhubEnvironmentRepository(this.credhubOperations);
repository.setOrder(environmentProperties.getOrder());
return repository;
}
}

View File

@@ -1,90 +0,0 @@
/*
* 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.config.server.environment;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.docs.ObservationDocumentation;
enum DocumentedConfigObservation implements ObservationDocumentation {
/**
* Observation created around an EnvironmentRepository.
*/
ENVIRONMENT_REPOSITORY {
@Override
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
return ObservationEnvironmentRepositoryObservationConvention.class;
}
@Override
public KeyName[] getLowCardinalityKeyNames() {
return LowCardinalityTags.values();
}
@Override
public String getPrefix() {
return "spring.cloud.config.environment";
}
};
enum LowCardinalityTags implements KeyName {
/**
* Implementation of the EnvironmentRepository.
*/
ENVIRONMENT_CLASS {
@Override
public String asString() {
return "spring.cloud.config.environment.class";
}
},
/**
* Application name for which properties are being queried for.
*/
PROFILE {
@Override
public String asString() {
return "spring.cloud.config.environment.profile";
}
},
/**
* Label for which properties are being queried for.
*/
LABEL {
@Override
public String asString() {
return "spring.cloud.config.environment.label";
}
},
/**
* Application name for which properties are being queried for.
*/
APPLICATION {
@Override
public String asString() {
return "spring.cloud.config.environment.application";
}
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.util.Map;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.environment.PropertyValueDescriptor;
/**
* @author Dave Syer
* @author Michael Prankl
*/
public class EnvironmentCleaner {
public Environment clean(Environment value, String workingDir, String uri) {
Environment result = new Environment(value);
for (PropertySource source : value.getPropertySources()) {
String name = source.getName().replace(workingDir, "");
name = name.replace("applicationConfig: [", "");
name = uri + "/" + name.replace("]", "");
result.add(new PropertySource(name, clean(source.getSource(), uri)));
}
return result;
}
protected Map<?, ?> clean(Map<?, ?> source, String uri) {
for (Map.Entry<?, ?> entry : source.entrySet()) {
if (entry.getValue() instanceof PropertyValueDescriptor) {
PropertyValueDescriptor descriptor = (PropertyValueDescriptor) entry.getValue();
if (!uri.endsWith("/")) {
uri = uri + "/";
}
String updated = descriptor.getOrigin().replace("[", "[" + uri);
descriptor.setOrigin(updated);
}
}
return source;
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.core.env.Environment;
/**
* @author Scott Frederick
*/
public class EnvironmentConfigTokenProvider implements ConfigTokenProvider {
private final Environment environment;
private final String propertyName;
public EnvironmentConfigTokenProvider(Environment environment, String propertyName) {
this.environment = environment;
this.propertyName = propertyName;
}
@Override
public String getToken() {
return environment.getProperty(propertyName);
}
}

View File

@@ -1,517 +0,0 @@
/*
* 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.config.server.environment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.EnvironmentMediaType;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.support.PathUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.prepareEnvironment;
import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.resolvePlaceholders;
/**
* @author Dave Syer
* @author Spencer Gibb
* @author Roy Clarkson
* @author Bartosz Wojtkiewicz
* @author Rafal Zukowski
* @author Ivan Corrales Solera
* @author Daniel Frey
* @author Ian Bondoc
* @author Chen Li
*
*/
@RestController
@RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
private static final Log LOG = LogFactory.getLog(EnvironmentController.class);
private EnvironmentRepository repository;
private ObjectMapper objectMapper;
private boolean stripDocument = true;
private boolean acceptEmpty = true;
public EnvironmentController(EnvironmentRepository repository) {
this(repository, new ObjectMapper());
}
public EnvironmentController(EnvironmentRepository repository, ObjectMapper objectMapper) {
this.repository = repository;
this.objectMapper = objectMapper;
}
/**
* Flag to indicate that YAML documents which are not a map should be stripped of the
* "document" prefix that is added by Spring (to facilitate conversion to Properties).
* @param stripDocument the flag to set
*/
public void setStripDocumentFromYaml(boolean stripDocument) {
this.stripDocument = stripDocument;
}
/**
* Flag to indicate that If HTTP 404 needs to be sent if Application is not Found.
* @param acceptEmpty the flag to set
*/
public void setAcceptEmpty(boolean acceptEmpty) {
this.acceptEmpty = acceptEmpty;
}
@GetMapping(path = "/{name}/{profiles:(?!.*\\b\\.(?:ya?ml|properties|json)\\b).*}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Environment defaultLabel(@PathVariable String name, @PathVariable String profiles) {
return getEnvironment(name, profiles, null, false);
}
@GetMapping(path = "/{name}/{profiles:(?!.*\\b\\.(?:ya?ml|properties|json)\\b).*}",
produces = EnvironmentMediaType.V2_JSON)
public Environment defaultLabelIncludeOrigin(@PathVariable String name, @PathVariable String profiles) {
return getEnvironment(name, profiles, null, true);
}
@GetMapping(path = "/{name}/{profiles}/{label:.*}", produces = MediaType.APPLICATION_JSON_VALUE)
public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) {
return getEnvironment(name, profiles, label, false);
}
@GetMapping(path = "/{name}/{profiles}/{label:.*}", produces = EnvironmentMediaType.V2_JSON)
public Environment labelledIncludeOrigin(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
return getEnvironment(name, profiles, label, true);
}
public Environment getEnvironment(String name, String profiles, String label, boolean includeOrigin) {
try {
name = normalize(name);
label = normalize(label);
Environment environment = this.repository.findOne(name, profiles, label, includeOrigin);
if (!this.acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())) {
throw new EnvironmentNotFoundException("Profile Not found");
}
return environment;
}
catch (Exception e) {
LOG.warn(String.format("Error getting the Environment with name=%s profiles=%s label=%s includeOrigin=%b",
name, profiles, label, includeOrigin), e);
throw e;
}
}
private String normalize(String part) {
if (PathUtils.isInvalidEncodedLocation(part)) {
throw new InvalidEnvironmentRequestException("Invalid request");
}
return Environment.normalize(part);
}
@GetMapping("/{name}-{profiles}.properties")
public ResponseEntity<String> properties(@PathVariable String name, @PathVariable String profiles,
@RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException {
return labelledProperties(name, profiles, null, resolvePlaceholders);
}
@GetMapping("/{label}/{name}-{profiles}.properties")
public ResponseEntity<String> labelledProperties(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
throws IOException {
validateProfiles(profiles);
Environment environment = labelled(name, profiles, label);
Map<String, Object> properties = convertToProperties(environment);
String propertiesString = getPropertiesString(properties);
if (resolvePlaceholders) {
propertiesString = resolvePlaceholders(prepareEnvironment(environment), propertiesString);
}
return getSuccess(propertiesString);
}
@GetMapping("{name}-{profiles}.json")
public ResponseEntity<String> jsonProperties(@PathVariable String name, @PathVariable String profiles,
@RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws Exception {
return labelledJsonProperties(name, profiles, null, resolvePlaceholders);
}
@GetMapping("/{label}/{name}-{profiles}.json")
public ResponseEntity<String> labelledJsonProperties(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
throws Exception {
validateProfiles(profiles);
Environment environment = labelled(name, profiles, label);
Map<String, Object> properties = convertToMap(environment);
String json = this.objectMapper.writeValueAsString(properties);
if (resolvePlaceholders) {
json = resolvePlaceholders(prepareEnvironment(environment), json);
}
return getSuccess(json, MediaType.APPLICATION_JSON);
}
private String getPropertiesString(Map<String, Object> properties) {
StringBuilder output = new StringBuilder();
for (Entry<String, Object> entry : properties.entrySet()) {
if (output.length() > 0) {
output.append("\n");
}
output.append(entry.getKey()).append(": ").append(entry.getValue());
}
return output.toString();
}
@GetMapping({ "/{name}-{profiles}.yml", "/{name}-{profiles}.yaml" })
public ResponseEntity<String> yaml(@PathVariable String name, @PathVariable String profiles,
@RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws Exception {
return labelledYaml(name, profiles, null, resolvePlaceholders);
}
@GetMapping({ "/{label}/{name}-{profiles}.yml", "/{label}/{name}-{profiles}.yaml" })
public ResponseEntity<String> labelledYaml(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders)
throws Exception {
validateProfiles(profiles);
Environment environment = labelled(name, profiles, label);
Map<String, Object> result = convertToMap(environment);
if (this.stripDocument && result.size() == 1 && result.keySet().iterator().next().equals("document")) {
Object value = result.get("document");
if (value instanceof Collection) {
return getSuccess(new Yaml().dumpAs(value, Tag.SEQ, FlowStyle.BLOCK));
}
else {
return getSuccess(new Yaml().dumpAs(value, Tag.STR, FlowStyle.BLOCK));
}
}
String yaml = new Yaml().dumpAsMap(result);
if (resolvePlaceholders) {
yaml = resolvePlaceholders(prepareEnvironment(environment), yaml);
}
return getSuccess(yaml);
}
/**
* Method {@code convertToMap} converts an {@code Environment} to a nested Map which
* represents a yml/json structure.
* @param input the environment to be converted
* @return the nested map containing the environment's properties
*/
private Map<String, Object> convertToMap(Environment input) {
// First use the current convertToProperties to get a flat Map from the
// environment
Map<String, Object> properties = convertToProperties(input);
// The root map which holds all the first level properties
Map<String, Object> rootMap = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
PropertyNavigator nav = new PropertyNavigator(key);
nav.setMapValue(rootMap, value);
}
return rootMap;
}
@ExceptionHandler(RepositoryException.class)
public void noSuchLabel(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.NOT_FOUND.value());
}
@ExceptionHandler(IllegalArgumentException.class)
public void illegalArgument(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
@ExceptionHandler(EnvironmentException.class)
public void environmentException(HttpServletResponse response, EnvironmentException e) throws IOException {
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}
private void validateProfiles(String profiles) {
if (profiles.contains("-")) {
throw new IllegalArgumentException(
"Properties output not supported for name or profiles containing hyphens");
}
}
private HttpHeaders getHttpHeaders(MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
return httpHeaders;
}
private ResponseEntity<String> getSuccess(String body) {
return new ResponseEntity<>(body, getHttpHeaders(MediaType.TEXT_PLAIN), HttpStatus.OK);
}
private ResponseEntity<String> getSuccess(String body, MediaType mediaType) {
return new ResponseEntity<>(body, getHttpHeaders(mediaType), HttpStatus.OK);
}
private Map<String, Object> convertToProperties(Environment profiles) {
List<PropertySource> sources = new ArrayList<>(profiles.getPropertySources());
Collections.reverse(sources);
Map<String, Object> combinedMap = new LinkedHashMap<>();
Map<String, Map<String, Object>> arrayMap = new LinkedHashMap<>();
for (PropertySource source : sources) {
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) source.getSource();
Map<String, Map<String, Object>> currentArrayMap = new LinkedHashMap<>();
for (Entry<String, Object> entry : value.entrySet()) {
if (!entry.getKey().contains("[")) {
combinedMap.put(entry.getKey(), entry.getValue());
}
else {
String prefixKey = entry.getKey().substring(0, entry.getKey().indexOf("["));
currentArrayMap.computeIfAbsent(prefixKey, k -> new LinkedHashMap<>()).put(entry.getKey(),
entry.getValue());
}
}
// Override array properties by prefix key
arrayMap.putAll(currentArrayMap);
}
// Combine all unique keys for array values into the combined map
for (Entry<String, Map<String, Object>> entry : arrayMap.entrySet()) {
combinedMap.putAll(entry.getValue());
}
postProcessProperties(combinedMap);
return combinedMap;
}
private void postProcessProperties(Map<String, Object> propertiesMap) {
propertiesMap.keySet().removeIf(key -> key.equals("spring.profiles"));
}
/**
* Class {@code PropertyNavigator} is used to navigate through the property key and
* create necessary Maps and Lists making up the nested structure to finally set the
* property value at the leaf node.
* <p>
* The following rules in yml/json are implemented: <pre>
* 1. an array element can be:
* - a value (leaf)
* - a map
* - a nested array
* 2. a map value can be:
* - a value (leaf)
* - a nested map
* - an array
* </pre>
*/
private static final class PropertyNavigator {
private final String propertyKey;
// Supports keys like org.x and org.x.y like in boot logging
private String prefix = "";
private int currentPos;
private NodeType valueType;
private PropertyNavigator(String propertyKey) {
this.propertyKey = propertyKey;
this.currentPos = -1;
this.valueType = NodeType.MAP;
}
@SuppressWarnings("unchecked")
private void setMapValue(Map<String, Object> map, Object value) {
String key = getKey();
if (NodeType.MAP.equals(this.valueType)) {
Map<String, Object> nestedMap;
if (map.get(key) instanceof Map) {
nestedMap = (Map<String, Object>) map.get(key);
}
else if (map.get(key) != null) {
// not an object, set prefix for later
prefix = key + ".";
nestedMap = map;
}
else {
// value of key is null
nestedMap = new LinkedHashMap<>();
map.put(key, nestedMap);
}
setMapValue(nestedMap, value);
}
else if (NodeType.ARRAY.equals(this.valueType)) {
List<Object> list = (List<Object>) map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
setListValue(list, value);
}
else {
// use compound prefix
map.put(prefix + key, value);
}
}
private void setListValue(List<Object> list, Object value) {
int index = getIndex();
// Fill missing elements if needed
while (list.size() <= index) {
list.add(null);
}
if (NodeType.MAP.equals(this.valueType)) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) list.get(index);
if (map == null) {
map = new LinkedHashMap<>();
list.set(index, map);
}
setMapValue(map, value);
}
else if (NodeType.ARRAY.equals(this.valueType)) {
@SuppressWarnings("unchecked")
List<Object> nestedList = (List<Object>) list.get(index);
if (nestedList == null) {
nestedList = new ArrayList<>();
list.set(index, nestedList);
}
setListValue(nestedList, value);
}
else {
list.set(index, value);
}
}
private int getIndex() {
// Consider [
int start = this.currentPos + 1;
for (int i = start; i < this.propertyKey.length(); i++) {
char c = this.propertyKey.charAt(i);
if (c == ']') {
this.currentPos = i;
break;
}
else if (!Character.isDigit(c)) {
throw new IllegalArgumentException("Invalid key: " + this.propertyKey);
}
}
// If no closing ] or if '[]'
if (this.currentPos < start || this.currentPos == start) {
throw new IllegalArgumentException("Invalid key: " + this.propertyKey);
}
else {
int index = Integer.parseInt(this.propertyKey.substring(start, this.currentPos));
// Skip the closing ]
this.currentPos++;
if (this.currentPos == this.propertyKey.length()) {
this.valueType = NodeType.LEAF;
}
else {
switch (this.propertyKey.charAt(this.currentPos)) {
case '.':
this.valueType = NodeType.MAP;
break;
case '[':
this.valueType = NodeType.ARRAY;
break;
default:
throw new IllegalArgumentException("Invalid key: " + this.propertyKey);
}
}
return index;
}
}
private String getKey() {
// Consider initial value or previous char '.' or '['
int start = this.currentPos + 1;
int openingBracketPosition = -1;
for (int i = start; i < this.propertyKey.length(); i++) {
char currentChar = this.propertyKey.charAt(i);
if (currentChar == '.') {
this.valueType = NodeType.MAP;
this.currentPos = i;
break;
}
else if (currentChar == '[') {
openingBracketPosition = i;
}
else if (currentChar == ']') {
String bracketContents = this.propertyKey.substring(openingBracketPosition + 1, i);
try {
Integer.parseInt(bracketContents);
this.valueType = NodeType.ARRAY;
this.currentPos = openingBracketPosition;
break;
}
catch (NumberFormatException e) {
// This means the key contains a [ and a ] but the contents were
// not an integer so it's not an array
}
}
}
// If there's no delimiter then it's a key of a leaf
if (this.currentPos < start) {
this.currentPos = this.propertyKey.length();
this.valueType = NodeType.LEAF;
// Else if we encounter '..' or '.[' or start of the property is . or [
// then it's invalid
}
else if (this.currentPos == start) {
throw new IllegalArgumentException("Invalid key: " + this.propertyKey);
}
return this.propertyKey.substring(start, this.currentPos);
}
private enum NodeType {
LEAF, MAP, ARRAY
}
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.environment;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.environment.PropertyValueDescriptor;
import org.springframework.cloud.config.server.encryption.EnvironmentEncryptor;
/**
* A delegating {@link EnvironmentRepository} that can decrypt the properties if an
* {@link EnvironmentEncryptor} is provided.
*
* @author Dave Syer
*
*/
public class EnvironmentEncryptorEnvironmentRepository implements EnvironmentRepository {
private EnvironmentRepository delegate;
private final List<EnvironmentEncryptor> environmentEncryptors;
private Map<String, String> overrides = new LinkedHashMap<>();
public EnvironmentEncryptorEnvironmentRepository(EnvironmentRepository delegate,
ObservationRegistry observationRegistry) {
this(delegate, null, observationRegistry);
}
public EnvironmentEncryptorEnvironmentRepository(EnvironmentRepository delegate,
List<EnvironmentEncryptor> environmentEncryptors, ObservationRegistry observationRegistry) {
this.delegate = ObservationEnvironmentRepositoryWrapper.wrap(observationRegistry, delegate);
this.environmentEncryptors = environmentEncryptors;
}
@Override
public Environment findOne(String name, String profiles, String label) {
return findOne(name, profiles, label, false);
}
@Override
public Environment findOne(String name, String profiles, String label, boolean includeOrigin) {
Environment environment = this.delegate.findOne(name, profiles, label, includeOrigin);
if (this.environmentEncryptors != null) {
for (EnvironmentEncryptor environmentEncryptor : environmentEncryptors) {
environment = environmentEncryptor.decrypt(environment);
}
}
if (!this.overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", getOverridesMap(includeOrigin)));
}
return environment;
}
private Map<?, ?> getOverridesMap(boolean includeOrigin) {
if (!includeOrigin) {
return this.overrides;
}
Map<Object, Object> map = new LinkedHashMap<>();
for (Map.Entry entry : this.overrides.entrySet()) {
map.put(entry.getKey(), new PropertyValueDescriptor(entry.getValue(), "Config server overrides"));
}
return map;
}
/**
* @param overrides the overrides to set
*/
public void setOverrides(Map<String, String> overrides) {
this.overrides = new HashMap<>(overrides);
for (String key : overrides.keySet()) {
if (overrides.get(key).contains("\\{")) {
this.overrides.put(key, overrides.get(key).replace("\\{", "{"));
}
if (overrides.get(key).contains("\\${")) {
this.overrides.put(key, overrides.get(key).replace("\\${", "${"));
}
}
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.environment;
/**
* @author Nicolas Homble
*/
@SuppressWarnings("serial")
public class EnvironmentException extends RuntimeException {
public EnvironmentException(String string) {
super(string);
}
public EnvironmentException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author Chidambaram Sundaram
*/
@SuppressWarnings("serial")
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Application Not Found")
public class EnvironmentNotFoundException extends RuntimeException {
public EnvironmentNotFoundException(String string) {
super(string);
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import org.springframework.cloud.config.environment.Environment;
/**
* @author Dave Syer
* @author Roy Clarkson
*/
public interface EnvironmentRepository {
Environment findOne(String application, String profile, String label);
default Environment findOne(String application, String profile, String label, boolean includeOrigin) {
return findOne(application, profile, label);
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
/**
* @param <T> {@link EnvironmentRepository} type
* @param <P> {@link EnvironmentRepositoryProperties} type
* @author Dylan Roberts
*/
public interface EnvironmentRepositoryFactory<T extends EnvironmentRepository, P extends EnvironmentRepositoryProperties> {
T build(P environmentProperties) throws Exception;
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.util.Map;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
/**
* A PropertySourceLocator that reads from an EnvironmentRepository.
*
* @author Dave Syer
*/
public class EnvironmentRepositoryPropertySourceLocator implements PropertySourceLocator {
private EnvironmentRepository repository;
private String name;
private String profiles;
private String label;
public EnvironmentRepositoryPropertySourceLocator(EnvironmentRepository repository, String name, String profiles,
String label) {
this.repository = repository;
this.name = name;
this.profiles = profiles;
this.label = label;
}
@Override
public org.springframework.core.env.PropertySource<?> locate(Environment environment) {
CompositePropertySource composite = new CompositePropertySource("configService");
for (PropertySource source : this.repository.findOne(this.name, this.profiles, this.label, false)
.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source.getSource();
composite.addPropertySource(new MapPropertySource(source.getName(), map));
}
return composite;
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
/**
* @author Spencer Gibb
*/
public interface EnvironmentWatch {
String watch(String state);
/**
* Default implementation of the {@link EnvironmentWatch}.
*/
class Default implements EnvironmentWatch {
@Override
public String watch(String state) {
return null;
}
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.environment;
/**
* @author Nicolas Homble
*
*/
@SuppressWarnings("serial")
public class FailedToConstructEnvironmentException extends EnvironmentException {
public FailedToConstructEnvironmentException(String string) {
super(string);
}
public FailedToConstructEnvironmentException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright 2013-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 org.springframework.cloud.config.server.environment;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Jose Maria Alvarez
*/
@ConfigurationProperties("spring.cloud.config.server.gcp-secret-manager")
public class GoogleSecretManagerEnvironmentProperties implements EnvironmentRepositoryProperties {
private int order = Ordered.LOWEST_PRECEDENCE;
private String applicationLabel = "application";
private String profileLabel = "profile";
private String serviceAccount = null;
private boolean tokenMandatory = true;
private Integer version = 1;
/**
* The metadata URL to get the project ID from.
*/
public static final String GOOGLE_METADATA_PROJECT_URL = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
@Override
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return order;
}
public Boolean getTokenMandatory() {
return tokenMandatory;
}
public void setTokenMandatory(Boolean tokenMandatory) {
this.tokenMandatory = tokenMandatory;
}
public String getApplicationLabel() {
return applicationLabel;
}
public void setApplicationLabel(String applicationLabel) {
this.applicationLabel = applicationLabel;
}
public String getProfileLabel() {
return profileLabel;
}
public void setProfileLabel(String profileLabel) {
this.profileLabel = profileLabel;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
public String getServiceAccount() {
return serviceAccount;
}
public void setServiceAccount(String serviceAccount) {
this.serviceAccount = serviceAccount;
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2013-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 org.springframework.cloud.config.server.environment;
import java.util.HashMap;
import java.util.Map;
import com.google.cloud.secretmanager.v1.Secret;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.cloud.config.server.environment.secretmanager.GoogleConfigProvider;
import org.springframework.cloud.config.server.environment.secretmanager.GoogleSecretComparatorByVersion;
import org.springframework.cloud.config.server.environment.secretmanager.GoogleSecretManagerAccessStrategy;
import org.springframework.cloud.config.server.environment.secretmanager.GoogleSecretManagerAccessStrategyFactory;
import org.springframework.cloud.config.server.environment.secretmanager.HttpHeaderGoogleConfigProvider;
import org.springframework.core.Ordered;
import org.springframework.web.client.RestTemplate;
/**
* @author Jose Maria Alvarez
* @author KNV Srinivas
*/
public class GoogleSecretManagerEnvironmentRepository implements EnvironmentRepository, Ordered {
private String applicationLabel;
private String profileLabel;
private GoogleSecretManagerAccessStrategy accessStrategy;
private boolean tokenMandatory;
private GoogleConfigProvider configProvider;
private final int order;
public GoogleSecretManagerEnvironmentRepository(ObjectProvider<HttpServletRequest> request, RestTemplate rest,
GoogleSecretManagerEnvironmentProperties properties) {
this.applicationLabel = properties.getApplicationLabel();
this.profileLabel = properties.getProfileLabel();
this.configProvider = new HttpHeaderGoogleConfigProvider(request);
this.accessStrategy = GoogleSecretManagerAccessStrategyFactory.forVersion(rest, configProvider, properties);
this.tokenMandatory = properties.getTokenMandatory();
this.order = properties.getOrder();
}
@Override
public Environment findOne(String application, String profile, String label) {
if (StringUtils.isEmpty(label)) {
label = "master";
}
if (StringUtils.isEmpty(profile)) {
profile = "default";
}
if (!profile.startsWith("default")) {
profile = "default," + profile;
}
String[] profiles = org.springframework.util.StringUtils
.trimArrayElements(org.springframework.util.StringUtils.commaDelimitedListToStringArray(profile));
Environment result = new Environment(application, profile, label, null, null);
if (tokenMandatory) {
if (accessStrategy.checkRemotePermissions()) {
addPropertySource(application, profiles, result);
}
}
else {
addPropertySource(application, profiles, result);
}
return result;
}
private void addPropertySource(String application, String[] profiles, Environment result) {
for (String profileUnit : profiles) {
Map<?, ?> secrets = getSecrets(application, profileUnit);
if (!secrets.isEmpty()) {
result.add(new PropertySource("gsm:" + application + "-" + profileUnit, secrets));
}
}
}
/**
* @param application the application name
* @param profile the profile name
* @return the properties to add into the environment
*/
private Map<?, ?> getSecrets(String application, String profile) {
Map<String, String> result = new HashMap<>();
String prefix = configProvider.getValue(HttpHeaderGoogleConfigProvider.PREFIX_HEADER, false);
for (Secret secret : accessStrategy.getSecrets()) {
if (secret.getLabelsOrDefault(applicationLabel, "application").equalsIgnoreCase(application)
&& secret.getLabelsOrDefault(profileLabel, "profile").equalsIgnoreCase(profile)) {
result.put(accessStrategy.getSecretName(secret),
accessStrategy.getSecretValue(secret, new GoogleSecretComparatorByVersion()));
}
else if (StringUtils.isNotBlank(prefix) && accessStrategy.getSecretName(secret).startsWith(prefix)) {
result.put(StringUtils.removeStart(accessStrategy.getSecretName(secret), prefix),
accessStrategy.getSecretValue(secret, new GoogleSecretComparatorByVersion()));
}
}
return result;
}
@Override
public int getOrder() {
return order;
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2013-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 org.springframework.cloud.config.server.environment;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.web.client.RestTemplate;
/**
* @author Jose Maria Alvarez
*/
public class GoogleSecretManagerEnvironmentRepositoryFactory implements
EnvironmentRepositoryFactory<GoogleSecretManagerEnvironmentRepository, GoogleSecretManagerEnvironmentProperties> {
private final ObjectProvider<HttpServletRequest> request;
public GoogleSecretManagerEnvironmentRepositoryFactory(ObjectProvider<HttpServletRequest> request) {
this.request = request;
}
@Override
public GoogleSecretManagerEnvironmentRepository build(
GoogleSecretManagerEnvironmentProperties environmentProperties) throws Exception {
return new GoogleSecretManagerEnvironmentRepository(request, new RestTemplate(), environmentProperties);
}
}

View File

@@ -1,201 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.apache.HttpClientConnection;
import org.springframework.cloud.config.server.support.HttpClient4Support;
import org.springframework.util.ObjectUtils;
import static java.util.stream.Collectors.toMap;
/**
* @author Dylan Roberts
*/
public class HttpClientConfigurableHttpConnectionFactory implements ConfigurableHttpConnectionFactory {
private static final String PLACEHOLDER_PATTERN_STRING = "\\{(\\w+)}";
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(PLACEHOLDER_PATTERN_STRING);
Log log = LogFactory.getLog(getClass());
Map<String, HttpClientBuilder> httpClientBuildersByUri = new LinkedHashMap<>();
@Override
public void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties)
throws GeneralSecurityException {
addHttpClient(environmentProperties);
for (JGitEnvironmentProperties repo : environmentProperties.getRepos().values()) {
addHttpClient(repo);
}
}
@Override
public HttpConnection create(URL url) throws IOException {
return create(url, null);
}
@Override
public HttpConnection create(URL url, Proxy proxy) throws IOException {
HttpClientBuilder builder = lookupHttpClientBuilder(url);
if (builder != null) {
return new HttpClientConnection(url.toString(), null, builder.build());
}
else {
/*
* No matching builder found: let jGit handle the creation of the HttpClient
*/
return new HttpClientConnection(url.toString());
}
}
private void addHttpClient(JGitEnvironmentProperties properties) throws GeneralSecurityException {
if (properties.getUri() != null && properties.getUri().startsWith("http")) {
this.httpClientBuildersByUri.put(properties.getUri(), HttpClient4Support.builder(properties));
}
}
private HttpClientBuilder lookupHttpClientBuilder(final URL url) {
Map<String, HttpClientBuilder> builderMap = this.httpClientBuildersByUri.entrySet().stream().filter(entry -> {
String key = entry.getKey();
String spec = getUrlWithPlaceholders(url, key);
if (spec.equals(key)) {
return true;
}
int index = spec.lastIndexOf("/");
while (index != -1) {
spec = spec.substring(0, index);
if (spec.equals(key)) {
return true;
}
index = spec.lastIndexOf("/");
}
return false;
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
if (builderMap.isEmpty()) {
this.log.warn(String.format("No custom http config found for URL: %s", url));
return null;
}
if (builderMap.size() > 1) {
/*
* Try to determine if there is an exact match URL or not. So if there is a
* placeholder in the URL, filter it out. We should be left with only URLs
* which have no placeholders. That is the one we want to use in the case
* there are multiple matches.
*/
List<String> keys = builderMap.keySet().stream().filter(key -> !PLACEHOLDER_PATTERN.matcher(key).find())
.collect(Collectors.toList());
if (keys.size() == 1) {
return builderMap.get(keys.get(0));
}
this.log.error(String.format(
"More than one git repo URL template matched URL:"
+ " %s, proxy and skipSslValidation config won't be applied. Matched templates: %s",
url, builderMap.keySet().stream().collect(Collectors.joining(", "))));
return null;
}
return new ArrayList<>(builderMap.values()).get(0);
}
private String getUrlWithPlaceholders(URL url, String key) {
String spec = url.toString();
String[] tokens = key.split(PLACEHOLDER_PATTERN_STRING);
// if token[0] equals url then there was no placeholder in the the url, so
// matching needed
if (tokens.length >= 1 && !tokens[0].equals(url.toString())) {
List<Placeholder> placeholders = getPlaceholders(key);
List<String> values = getValues(spec, tokens);
if (placeholders.size() == values.size()) {
for (int i = 0; i < values.size(); i++) {
// if the key does not start with first part of the spec before the
// place holder then its
// not a match
String specBeforePlaceholder = spec.substring(0, placeholders.get(i).start);
if (key.startsWith(specBeforePlaceholder)) {
spec = specBeforePlaceholder + "{" + placeholders.get(i).group + "}"
+ spec.substring(placeholders.get(i).start + values.get(i).length());
}
}
}
}
return spec;
}
private List<String> getValues(String spec, String[] tokens) {
List<String> values = new LinkedList<>();
for (String token : tokens) {
String[] valueTokens = spec.split(token);
if (!ObjectUtils.isEmpty(valueTokens[0])) {
values.add(valueTokens[0]);
}
if (valueTokens.length > 1) {
spec = valueTokens[1];
}
}
if (tokens.length == 1 && !ObjectUtils.isEmpty(spec)) {
values.add(spec);
}
return values;
}
private List<Placeholder> getPlaceholders(String key) {
Pattern pattern = Pattern.compile(PLACEHOLDER_PATTERN_STRING);
Matcher matcher = pattern.matcher(key);
List<Placeholder> placeholders = new LinkedList<>();
while (matcher.find()) {
placeholders.add(new Placeholder(matcher.group(1), matcher.start(), matcher.end()));
}
return placeholders;
}
private static class Placeholder {
String group;
int start;
int end;
Placeholder(String group, int start, int end) {
this.start = start;
this.end = end;
this.group = group;
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.security.GeneralSecurityException;
import org.apache.hc.client5.http.classic.HttpClient;
import org.springframework.cloud.config.server.support.HttpClientSupport;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @author Dylan Roberts
*/
public class HttpClientVaultRestTemplateFactory implements VaultEnvironmentRepositoryFactory.VaultRestTemplateFactory {
@Override
public RestTemplate build(VaultEnvironmentProperties environmentProperties) throws GeneralSecurityException {
HttpClient httpClient = HttpClientSupport.builder(environmentProperties).build();
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.util.StringUtils;
/**
* @author Scott Frederick
*/
public class HttpRequestConfigTokenProvider implements ConfigTokenProvider {
private ObjectProvider<HttpServletRequest> httpRequest;
public HttpRequestConfigTokenProvider(ObjectProvider<HttpServletRequest> httpRequest) {
this.httpRequest = httpRequest;
}
@Override
public String getToken() {
HttpServletRequest request = httpRequest.getIfAvailable();
if (request == null) {
throw new IllegalStateException("No HttpServletRequest available");
}
String token = request.getHeader(ConfigClientProperties.TOKEN_HEADER);
if (!StringUtils.hasLength(token)) {
throw new IllegalArgumentException(
"Missing required header in HttpServletRequest: " + ConfigClientProperties.TOKEN_HEADER);
}
return token;
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author Chidambaram Sundaram
*/
@SuppressWarnings("serial")
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid Request")
public class InvalidEnvironmentRequestException extends RuntimeException {
public InvalidEnvironmentRequestException(String string) {
super(string);
}
}

View File

@@ -1,276 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.util.HashMap;
import java.util.Map;
import jakarta.validation.constraints.Pattern;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.cloud.config.server.support.AbstractScmAccessorProperties;
import org.springframework.cloud.config.server.support.HttpEnvironmentRepositoryProperties;
/**
* @author Dylan Roberts
* @author Gareth Clay
*/
public class JGitEnvironmentProperties extends AbstractScmAccessorProperties
implements HttpEnvironmentRepositoryProperties {
/**
* Master label.
*/
public static final String MASTER_LABEL = "master";
/**
* Main label.
*/
public static final String MAIN_LABEL = "main";
private static final String DEFAULT_LABEL = MAIN_LABEL;
/**
* To maintain compatibility we will try the master branch in addition to main when we
* try to fetch the default branch.
*/
private boolean tryMasterBranch = true;
/**
* Flag to indicate that the repository should be cloned on startup (not on demand).
* Generally leads to slower startup but faster first query.
*/
private boolean cloneOnStart = false;
/**
* Flag to indicate that the submodules in the repository should be cloned.
*/
private boolean cloneSubmodules = false;
/**
* Flag to indicate that the repository should force pull. If true discard any local
* changes and take from remote repository.
*/
private boolean forcePull;
/**
* Timeout (in seconds) for obtaining HTTP or SSH connection (if applicable), defaults
* to 5 seconds.
*/
private int timeout = 5;
/**
* Flag to indicate that the branch should be deleted locally if it's origin tracked
* branch was removed.
*/
private boolean deleteUntrackedBranches = false;
/**
* Flag to indicate that SSL certificate validation should be bypassed when
* communicating with a repository served over an HTTPS connection.
*/
private boolean skipSslValidation = false;
/**
* Time (in seconds) between refresh of the git repository.
*/
private int refreshRate = 0;
/**
* Valid SSH private key. Must be set if ignoreLocalSshSettings is true and Git URI is
* SSH format.
*/
private String privateKey;
/**
* One of ssh-dss, ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, or
* ecdsa-sha2-nistp521. Must be set if hostKey is also set.
*/
private String hostKeyAlgorithm;
/**
* Valid SSH host key. Must be set if hostKeyAlgorithm is also set.
*/
private String hostKey;
/**
* Location of custom .known_hosts file.
*/
private String knownHostsFile;
/**
* Override server authentication method order. This should allow for evading login
* prompts if server has keyboard-interactive authentication before the publickey
* method.
*/
@Pattern(regexp = "([\\w -]+,)*([\\w -]+)")
private String preferredAuthentications;
/**
* If true, use property-based instead of file-based SSH config.
*/
private boolean ignoreLocalSshSettings;
/**
* If false, ignore errors with host key.
*/
private boolean strictHostKeyChecking = true;
/**
* HTTP proxy configuration.
*/
private Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxy = new HashMap<>();
public JGitEnvironmentProperties() {
super();
setDefaultLabel(DEFAULT_LABEL);
}
public boolean isCloneOnStart() {
return this.cloneOnStart;
}
public void setCloneOnStart(boolean cloneOnStart) {
this.cloneOnStart = cloneOnStart;
}
public boolean isCloneSubmodules() {
return this.cloneSubmodules;
}
public void setCloneSubmodules(boolean cloneSubmodules) {
this.cloneSubmodules = cloneSubmodules;
}
public boolean isForcePull() {
return this.forcePull;
}
public void setForcePull(boolean forcePull) {
this.forcePull = forcePull;
}
@Override
public int getTimeout() {
return this.timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public boolean isDeleteUntrackedBranches() {
return this.deleteUntrackedBranches;
}
public void setDeleteUntrackedBranches(boolean deleteUntrackedBranches) {
this.deleteUntrackedBranches = deleteUntrackedBranches;
}
public int getRefreshRate() {
return this.refreshRate;
}
public void setRefreshRate(int refreshRate) {
this.refreshRate = refreshRate;
}
public String getPrivateKey() {
return this.privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public String getHostKeyAlgorithm() {
return this.hostKeyAlgorithm;
}
public void setHostKeyAlgorithm(String hostKeyAlgorithm) {
this.hostKeyAlgorithm = hostKeyAlgorithm;
}
public String getHostKey() {
return this.hostKey;
}
public void setHostKey(String hostKey) {
this.hostKey = hostKey;
}
public String getKnownHostsFile() {
return this.knownHostsFile;
}
public void setKnownHostsFile(String knownHostsFile) {
this.knownHostsFile = knownHostsFile;
}
public String getPreferredAuthentications() {
return this.preferredAuthentications;
}
public void setPreferredAuthentications(String preferredAuthentications) {
this.preferredAuthentications = preferredAuthentications;
}
public boolean isIgnoreLocalSshSettings() {
return this.ignoreLocalSshSettings;
}
public void setIgnoreLocalSshSettings(boolean ignoreLocalSshSettings) {
this.ignoreLocalSshSettings = ignoreLocalSshSettings;
}
@Override
public boolean isStrictHostKeyChecking() {
return this.strictHostKeyChecking;
}
@Override
public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
this.strictHostKeyChecking = strictHostKeyChecking;
}
@Override
public boolean isSkipSslValidation() {
return this.skipSslValidation;
}
public void setSkipSslValidation(boolean skipSslValidation) {
this.skipSslValidation = skipSslValidation;
}
@Override
public Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> getProxy() {
return this.proxy;
}
public void setProxy(Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> proxy) {
this.proxy = proxy;
}
public boolean isTryMasterBranch() {
return tryMasterBranch;
}
public void setTryMasterBranch(boolean tryMasterBranch) {
this.tryMasterBranch = tryMasterBranch;
}
}

View File

@@ -1,780 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.micrometer.observation.ObservationRegistry;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.DeleteBranchCommand;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.ListBranchCommand.ListMode;
import org.eclipse.jgit.api.MergeCommand;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.StatusCommand;
import org.eclipse.jgit.api.TransportCommand;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.lib.BranchTrackingStatus;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.TagOpt;
import org.eclipse.jgit.transport.TrackingRefUpdate;
import org.eclipse.jgit.util.FileUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.config.server.support.GitCredentialsProviderFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static java.lang.String.format;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
/**
* An {@link EnvironmentRepository} backed by a single git repository.
*
* @author Dave Syer
* @author Roy Clarkson
* @author Marcos Barbero
* @author Daniel Lavoie
* @author Ryan Lynch
* @author Gareth Clay
* @author ChaoDong Xi
*/
public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
implements EnvironmentRepository, SearchPathLocator, InitializingBean {
/**
* Error message for URI for git repo.
*/
public static final String MESSAGE = "You need to configure a uri for the git repository.";
private static final String FILE_URI_PREFIX = "file:";
private static final String LOCAL_BRANCH_REF_PREFIX = "refs/remotes/origin/";
/**
* Timeout (in seconds) for obtaining HTTP or SSH connection (if applicable). Default
* 5 seconds.
*/
private int timeout;
/**
* Time (in seconds) between refresh of the git repository.
*/
private int refreshRate = 0;
/**
* Time of the last refresh of the git repository.
*/
private long lastRefresh;
/**
* Flag to indicate that the repository should be cloned on startup (not on demand).
* Generally leads to slower startup but faster first query.
*/
private boolean cloneOnStart;
private JGitEnvironmentRepository.JGitFactory gitFactory;
private String defaultLabel;
/**
* Factory used to create the credentials provider to use to connect to the Git
* repository.
*/
private GitCredentialsProviderFactory gitCredentialsProviderFactory = new GitCredentialsProviderFactory();
/**
* Transport configuration callback for JGit commands.
*/
private TransportConfigCallback transportConfigCallback;
/**
* Flag to indicate that the repository should force pull. If true discard any local
* changes and take from remote repository.
*/
private boolean forcePull;
/**
* Flag to indicate that the branch should be deleted locally if it's origin tracked
* branch was removed.
*/
private boolean deleteUntrackedBranches;
/**
* Flag to indicate that SSL certificate validation should be bypassed when
* communicating with a repository served over an HTTPS connection.
*/
private boolean skipSslValidation;
private boolean tryMasterBranch;
private final ObservationRegistry observationRegistry;
public JGitEnvironmentRepository(ConfigurableEnvironment environment, JGitEnvironmentProperties properties,
ObservationRegistry observationRegistry) {
super(environment, properties, observationRegistry);
this.cloneOnStart = properties.isCloneOnStart();
this.defaultLabel = properties.getDefaultLabel();
this.forcePull = properties.isForcePull();
this.timeout = properties.getTimeout();
this.deleteUntrackedBranches = properties.isDeleteUntrackedBranches();
this.refreshRate = properties.getRefreshRate();
this.skipSslValidation = properties.isSkipSslValidation();
this.gitFactory = new JGitFactory(properties.isCloneSubmodules());
this.tryMasterBranch = properties.isTryMasterBranch();
this.observationRegistry = observationRegistry;
}
public boolean isTryMasterBranch() {
return tryMasterBranch;
}
public void setTryMasterBranch(boolean tryMasterBranch) {
this.tryMasterBranch = tryMasterBranch;
}
public boolean isCloneOnStart() {
return this.cloneOnStart;
}
public void setCloneOnStart(boolean cloneOnStart) {
this.cloneOnStart = cloneOnStart;
}
public int getTimeout() {
return this.timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getRefreshRate() {
return this.refreshRate;
}
public void setRefreshRate(int refreshRate) {
this.refreshRate = refreshRate;
}
public TransportConfigCallback getTransportConfigCallback() {
return this.transportConfigCallback;
}
public void setTransportConfigCallback(TransportConfigCallback transportConfigCallback) {
this.transportConfigCallback = transportConfigCallback;
}
public JGitFactory getGitFactory() {
return this.gitFactory;
}
public void setGitFactory(JGitFactory gitFactory) {
this.gitFactory = gitFactory;
}
public void setGitCredentialsProviderFactory(GitCredentialsProviderFactory gitCredentialsProviderFactory) {
this.gitCredentialsProviderFactory = gitCredentialsProviderFactory;
}
GitCredentialsProviderFactory getGitCredentialsProviderFactory() {
return gitCredentialsProviderFactory;
}
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
public boolean isForcePull() {
return this.forcePull;
}
public void setForcePull(boolean forcePull) {
this.forcePull = forcePull;
}
public boolean isDeleteUntrackedBranches() {
return this.deleteUntrackedBranches;
}
public void setDeleteUntrackedBranches(boolean deleteUntrackedBranches) {
this.deleteUntrackedBranches = deleteUntrackedBranches;
}
public boolean isSkipSslValidation() {
return this.skipSslValidation;
}
public void setSkipSslValidation(boolean skipSslValidation) {
this.skipSslValidation = skipSslValidation;
}
@Override
public synchronized Locations getLocations(String application, String profile, String label) {
if (label == null) {
label = this.defaultLabel;
}
String version;
try {
version = refresh(label);
}
catch (Exception e) {
if (this.defaultLabel.equals(label) && JGitEnvironmentProperties.MAIN_LABEL.equals(this.defaultLabel)
&& tryMasterBranch) {
logger.info("Could not refresh default label " + label, e);
logger.info("Will try to refresh master label instead.");
version = refresh(JGitEnvironmentProperties.MASTER_LABEL);
}
else {
throw e;
}
}
return new Locations(application, profile, label, version,
getSearchLocations(getWorkingDirectory(), application, profile, label));
}
@Override
public synchronized void afterPropertiesSet() throws Exception {
Assert.state(getUri() != null, MESSAGE);
if (this.cloneOnStart) {
initClonedRepository();
}
}
/**
* Get the working directory ready.
* @param label label to refresh
* @return head id
*/
public String refresh(String label) {
Git git = null;
try {
git = createGitClient();
if (shouldPull(git)) {
FetchResult fetchStatus = fetch(git, label);
if (this.deleteUntrackedBranches && fetchStatus != null) {
deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
}
}
// checkout after fetch so we can get any new branches, tags, ect.
// if nothing to update so just checkout and merge.
// Merge because remote branch could have been updated before
checkout(git, label);
tryMerge(git, label);
// always return what is currently HEAD as the version
return git.getRepository().findRef("HEAD").getObjectId().getName();
}
catch (RefNotFoundException e) {
throw new NoSuchLabelException("No such label: " + label, e);
}
catch (NoRemoteRepositoryException e) {
throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
}
catch (GitAPIException e) {
throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + getUri(), e);
}
catch (Exception e) {
throw new IllegalStateException("Cannot load environment", e);
}
finally {
try {
if (git != null) {
git.close();
}
}
catch (Exception e) {
this.logger.warn("Could not close git repository", e);
}
}
}
private void tryMerge(Git git, String label) {
try {
if (isBranch(git, label)) {
// merge results from fetch
merge(git, label);
if (!isClean(git, label)) {
this.logger.warn("The local repository is dirty or ahead of origin. Resetting" + " it to origin/"
+ label + ".");
resetHard(git, label, LOCAL_BRANCH_REF_PREFIX + label);
}
}
}
catch (GitAPIException e) {
throw new NoSuchRepositoryException("Cannot clone or checkout repository: " + getUri(), e);
}
}
/**
* Clones the remote repository and then opens a connection to it. Checks out to the
* defaultLabel if specified.
* @throws GitAPIException when cloning fails
* @throws IOException when repo opening fails
*/
private void initClonedRepository() throws GitAPIException, IOException {
if (!getUri().startsWith(FILE_URI_PREFIX)) {
deleteBaseDirIfExists();
Git git = cloneToBasedir();
if (git != null) {
git.close();
}
git = openGitRepository();
// Check if git points to valid repository and default label is not empty or
// null.
if (null != git && git.getRepository() != null && !ObjectUtils.isEmpty(getDefaultLabel())) {
// Checkout the default branch set for repo in git. This may not always be
// master. It depends on the
// admin and organization settings.
String defaultBranchInGit = git.getRepository().getBranch();
// If default branch is not empty and NOT equal to defaultLabel, then
// checkout the branch/tag/commit-id.
if (!ObjectUtils.isEmpty(defaultBranchInGit)
&& !getDefaultLabel().equalsIgnoreCase(defaultBranchInGit)) {
checkoutDefaultBranchWithRetry(git);
}
}
if (git != null) {
git.close();
}
}
}
private void checkoutDefaultBranchWithRetry(Git git) throws GitAPIException {
try {
checkout(git, getDefaultLabel());
}
catch (Exception e) {
if (JGitEnvironmentProperties.MAIN_LABEL.equals(getDefaultLabel()) && tryMasterBranch) {
logger.info("Could not checkout default label " + getDefaultLabel(), e);
logger.info("Will try to checkout master label instead.");
checkout(git, JGitEnvironmentProperties.MASTER_LABEL);
}
else {
throw e;
}
}
}
/**
* Deletes local branches if corresponding remote branch was removed.
* @param trackingRefUpdates list of tracking ref updates
* @param git git instance
* @return list of deleted branches
*/
private Collection<String> deleteUntrackedLocalBranches(Collection<TrackingRefUpdate> trackingRefUpdates, Git git) {
if (CollectionUtils.isEmpty(trackingRefUpdates)) {
return Collections.emptyList();
}
Collection<String> branchesToDelete = new ArrayList<>();
for (TrackingRefUpdate trackingRefUpdate : trackingRefUpdates) {
ReceiveCommand receiveCommand = trackingRefUpdate.asReceiveCommand();
if (receiveCommand.getType() == DELETE) {
String localRefName = trackingRefUpdate.getLocalName();
if (StringUtils.startsWithIgnoreCase(localRefName, LOCAL_BRANCH_REF_PREFIX)) {
String localBranchName = localRefName.substring(LOCAL_BRANCH_REF_PREFIX.length(),
localRefName.length());
branchesToDelete.add(localBranchName);
}
}
}
if (CollectionUtils.isEmpty(branchesToDelete)) {
return Collections.emptyList();
}
try {
// make sure that deleted branch not a current one
checkoutDefaultBranchWithRetry(git);
return deleteBranches(git, branchesToDelete);
}
catch (Exception ex) {
String message = format("Failed to delete %s branches.", branchesToDelete);
warn(message, ex);
return Collections.emptyList();
}
}
private List<String> deleteBranches(Git git, Collection<String> branchesToDelete) throws GitAPIException {
DeleteBranchCommand deleteBranchCommand = git.branchDelete()
.setBranchNames(branchesToDelete.toArray(new String[0]))
// local branch can contain data which is not merged to HEAD - force
// delete it anyway, since local copy should be R/O
.setForce(true);
List<String> resultList = deleteBranchCommand.call();
this.logger.info(format("Deleted %s branches from %s branches to delete.", resultList, branchesToDelete));
return resultList;
}
private Ref checkout(Git git, String label) throws GitAPIException {
CheckoutCommand checkout = git.checkout();
if (shouldTrack(git, label)) {
trackBranch(git, checkout, label);
}
else {
// works for tags and local branches
checkout.setName(label);
}
return checkout.call();
}
protected boolean shouldPull(Git git) throws GitAPIException {
boolean shouldPull;
if (this.refreshRate > 0 && System.currentTimeMillis() - this.lastRefresh < (this.refreshRate * 1000)) {
return false;
}
Status gitStatus;
try {
gitStatus = git.status().call();
}
catch (JGitInternalException e) {
onPullInvalidIndex(git, e);
gitStatus = git.status().call();
}
boolean isWorkingTreeClean = gitStatus.isClean();
String originUrl = git.getRepository().getConfig().getString("remote", "origin", "url");
if (this.forcePull && !isWorkingTreeClean) {
shouldPull = true;
logDirty(gitStatus);
}
else {
shouldPull = isWorkingTreeClean && originUrl != null;
}
if (!isWorkingTreeClean && !this.forcePull) {
this.logger.info("Cannot pull from remote " + originUrl + ", the working tree is not clean.");
}
return shouldPull;
}
protected void onPullInvalidIndex(Git git, JGitInternalException e) {
if (!e.getMessage().contains("Short read of block.")) {
throw e;
}
if (!this.forcePull) {
throw e;
}
try {
new File(getWorkingDirectory(), ".git/index").delete();
git.reset().setMode(ResetType.HARD).setRef("HEAD").call();
}
catch (GitAPIException ex) {
e.addSuppressed(ex);
throw e;
}
}
@SuppressWarnings("unchecked")
private void logDirty(Status status) {
Set<String> dirties = dirties(status.getAdded(), status.getChanged(), status.getRemoved(), status.getMissing(),
status.getModified(), status.getConflicting(), status.getUntracked());
this.logger.warn(format("Dirty files found: %s", dirties));
}
@SuppressWarnings("unchecked")
private Set<String> dirties(Set<String>... changes) {
Set<String> dirties = new HashSet<>();
for (Set<String> files : changes) {
dirties.addAll(files);
}
return dirties;
}
private boolean shouldTrack(Git git, String label) throws GitAPIException {
return isBranch(git, label) && !isLocalBranch(git, label);
}
protected FetchResult fetch(Git git, String label) {
FetchCommand fetch = git.fetch();
fetch.setRemote("origin");
fetch.setTagOpt(TagOpt.FETCH_TAGS);
fetch.setRemoveDeletedRefs(this.deleteUntrackedBranches);
if (this.refreshRate > 0) {
this.setLastRefresh(System.currentTimeMillis());
}
configureCommand(fetch);
try {
FetchResult result = fetch.call();
if (result.getTrackingRefUpdates() != null && result.getTrackingRefUpdates().size() > 0) {
this.logger.info("Fetched for remote " + label + " and found " + result.getTrackingRefUpdates().size()
+ " updates");
}
return result;
}
catch (Exception ex) {
String message = "Could not fetch remote for " + label + " remote: "
+ git.getRepository().getConfig().getString("remote", "origin", "url");
warn(message, ex);
return null;
}
}
private MergeResult merge(Git git, String label) {
try {
MergeCommand merge = git.merge();
merge.include(git.getRepository().findRef("origin/" + label));
MergeResult result = merge.call();
if (!result.getMergeStatus().isSuccessful()) {
this.logger.warn("Merged from remote " + label + " with result " + result.getMergeStatus());
}
return result;
}
catch (Exception ex) {
String message = "Could not merge remote for " + label + " remote: "
+ git.getRepository().getConfig().getString("remote", "origin", "url");
warn(message, ex);
return null;
}
}
private Ref resetHard(Git git, String label, String ref) {
ResetCommand reset = git.reset();
reset.setRef(ref);
reset.setMode(ResetType.HARD);
try {
Ref resetRef = reset.call();
if (resetRef != null) {
this.logger.info("Reset label " + label + " to version " + resetRef.getObjectId());
}
return resetRef;
}
catch (Exception ex) {
String message = "Could not reset to remote for " + label + " (current ref=" + ref + "), remote: "
+ git.getRepository().getConfig().getString("remote", "origin", "url");
warn(message, ex);
return null;
}
}
private Git createGitClient() throws IOException, GitAPIException {
File lock = new File(getWorkingDirectory(), ".git/index.lock");
if (lock.exists()) {
// The only way this can happen is if another JVM (e.g. one that
// crashed earlier) created the lock. We can attempt to recover by
// wiping the slate clean.
this.logger.info("Deleting stale JGit lock file at " + lock);
lock.delete();
}
if (new File(getWorkingDirectory(), ".git").exists()) {
return openGitRepository();
}
else {
return copyRepository();
}
}
// Synchronize here so that multiple requests don't all try and delete the
// base dir
// together (this is a once only operation, so it only holds things up on
// the first
// request).
private synchronized Git copyRepository() throws IOException, GitAPIException {
deleteBaseDirIfExists();
getBasedir().mkdirs();
Assert.state(getBasedir().exists(), "Could not create basedir: " + getBasedir());
if (getUri().startsWith(FILE_URI_PREFIX)) {
return copyFromLocalRepository();
}
else {
return cloneToBasedir();
}
}
private Git openGitRepository() throws IOException {
Git git = this.gitFactory.getGitByOpen(getWorkingDirectory());
return git;
}
private Git copyFromLocalRepository() throws IOException {
Git git;
File remote = new UrlResource(StringUtils.cleanPath(getUri())).getFile();
Assert.state(remote.isDirectory(), "No directory at " + getUri());
File gitDir = new File(remote, ".git");
Assert.state(gitDir.exists(), "No .git at " + getUri());
Assert.state(gitDir.isDirectory(), "No .git directory at " + getUri());
git = this.gitFactory.getGitByOpen(remote);
return git;
}
private Git cloneToBasedir() throws GitAPIException {
CloneCommand clone = this.gitFactory.getCloneCommandByCloneRepository().setURI(getUri())
.setDirectory(getBasedir());
configureCommand(clone);
try {
return clone.call();
}
catch (GitAPIException e) {
this.logger.warn("Error occured cloning to base directory.", e);
deleteBaseDirIfExists();
throw e;
}
}
private void deleteBaseDirIfExists() {
if (getBasedir().exists()) {
for (File file : getBasedir().listFiles()) {
try {
FileUtils.delete(file, FileUtils.RECURSIVE);
}
catch (IOException e) {
throw new IllegalStateException("Failed to initialize base directory", e);
}
}
}
}
private void configureCommand(TransportCommand<?, ?> command) {
command.setTimeout(this.timeout);
if (this.transportConfigCallback != null) {
command.setTransportConfigCallback(this.transportConfigCallback);
}
CredentialsProvider credentialsProvider = getCredentialsProvider();
if (credentialsProvider != null) {
command.setCredentialsProvider(credentialsProvider);
}
}
private CredentialsProvider getCredentialsProvider() {
return this.gitCredentialsProviderFactory.createFor(this.getUri(), getUsername(), getPassword(),
getPassphrase(), isSkipSslValidation());
}
private boolean isClean(Git git, String label) {
StatusCommand status = git.status();
try {
BranchTrackingStatus trackingStatus = BranchTrackingStatus.of(git.getRepository(), label);
boolean isBranchAhead = trackingStatus != null && trackingStatus.getAheadCount() > 0;
return status.call().isClean() && !isBranchAhead;
}
catch (Exception e) {
String message = "Could not execute status command on local repository. Cause: ("
+ e.getClass().getSimpleName() + ") " + e.getMessage();
warn(message, e);
return false;
}
}
private void trackBranch(Git git, CheckoutCommand checkout, String label) {
checkout.setCreateBranch(true).setName(label).setUpstreamMode(SetupUpstreamMode.TRACK)
.setStartPoint("origin/" + label);
}
private boolean isBranch(Git git, String label) throws GitAPIException {
return containsBranch(git, label, ListMode.ALL);
}
private boolean isLocalBranch(Git git, String label) throws GitAPIException {
return containsBranch(git, label, null);
}
private boolean containsBranch(Git git, String label, ListMode listMode) throws GitAPIException {
ListBranchCommand command = git.branchList();
if (listMode != null) {
command.setListMode(listMode);
}
List<Ref> branches = command.call();
for (Ref ref : branches) {
if (ref.getName().equals("refs/heads/" + label) || ref.getName().equals("refs/remotes/origin/" + label)) {
return true;
}
}
return false;
}
protected void warn(String message, Exception ex) {
this.logger.warn(message);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Stacktrace for: " + message, ex);
}
}
public long getLastRefresh() {
return this.lastRefresh;
}
public void setLastRefresh(long lastRefresh) {
this.lastRefresh = lastRefresh;
}
/**
* Wraps the static method calls to {@link org.eclipse.jgit.api.Git} and
* {@link org.eclipse.jgit.api.CloneCommand} allowing for easier unit testing.
*/
public static class JGitFactory {
private final boolean cloneSubmodules;
public JGitFactory() {
this(false);
}
public JGitFactory(boolean cloneSubmodules) {
this.cloneSubmodules = cloneSubmodules;
}
public Git getGitByOpen(File file) throws IOException {
Git git = Git.open(file);
return git;
}
public CloneCommand getCloneCommandByCloneRepository() {
CloneCommand command = Git.cloneRepository().setCloneSubmodules(cloneSubmodules);
return command;
}
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Dylan Roberts
* @author Thomas Vitale
*/
@ConfigurationProperties("spring.cloud.config.server.jdbc")
public class JdbcEnvironmentProperties implements EnvironmentRepositoryProperties {
private static final String DEFAULT_SQL = "SELECT \"KEY\", \"VALUE\" from PROPERTIES"
+ " where APPLICATION=? and PROFILE=? and LABEL=?";
private static final String DEFAULT_SQL_WITHOUT_PROFILE = "SELECT \"KEY\", \"VALUE\" from PROPERTIES"
+ " where APPLICATION=? and PROFILE is null and LABEL=?";
/**
* Flag to indicate that JDBC environment repository configuration is enabled.
*/
private boolean enabled = true;
private int order = Ordered.LOWEST_PRECEDENCE - 10;
/** SQL used to query database for keys and values. */
private String sql = DEFAULT_SQL;
/** SQL used to query database for keys and values when profile is null. */
private String sqlWithoutProfile = DEFAULT_SQL_WITHOUT_PROFILE;
/**
* Flag to determine how to handle query exceptions.
*/
private boolean failOnError = true;
private String defaultLabel = "master";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getOrder() {
return this.order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
public String getSql() {
return this.sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getSqlWithoutProfile() {
return this.sqlWithoutProfile;
}
public void setSqlWithoutProfile(String sqlWithoutProfile) {
this.sqlWithoutProfile = sqlWithoutProfile;
}
public boolean isFailOnError() {
return failOnError;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public boolean isConfigIncomplete() {
// sql and sqlWithoutProfile should be customized at the same time
return !this.sql.equals(DEFAULT_SQL) && this.sqlWithoutProfile.equals(DEFAULT_SQL_WITHOUT_PROFILE);
}
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
}

View File

@@ -1,194 +0,0 @@
/*
* Copyright 2016-2019 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.config.server.environment;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.util.StringUtils;
/**
* An {@link EnvironmentRepository} that picks up data from a relational database. The
* database should have a table called "PROPERTIES" with columns "APPLICATION", "PROFILE",
* "LABEL" (with the usual {@link Environment} meaning), plus "KEY" and "VALUE" for the
* key and value pairs in {@link Properties} style. Property values behave in the same way
* as they would if they came from Spring Boot properties files named
* <code>{application}-{profile}.properties</code>, including all the encryption and
* decryption, which will be applied as post-processing steps (i.e. not in this repository
* directly).
*
* @author Dave Syer
*
*/
public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered {
private static final Log logger = LogFactory.getLog(JdbcEnvironmentRepository.class);
private final JdbcTemplate jdbc;
private final PropertiesResultSetExtractor extractor;
private int order;
private String sql;
private String sqlWithoutProfile;
private boolean failOnError;
private boolean configIncomplete;
private String defaultLabel;
@Deprecated
public JdbcEnvironmentRepository(JdbcTemplate jdbc, JdbcEnvironmentProperties properties) {
this(jdbc, properties, new PropertiesResultSetExtractor());
}
public JdbcEnvironmentRepository(JdbcTemplate jdbc, JdbcEnvironmentProperties properties,
PropertiesResultSetExtractor extractor) {
this.jdbc = jdbc;
this.order = properties.getOrder();
this.sql = properties.getSql();
this.sqlWithoutProfile = properties.getSqlWithoutProfile();
this.failOnError = properties.isFailOnError();
this.extractor = extractor;
this.configIncomplete = properties.isConfigIncomplete();
this.defaultLabel = properties.getDefaultLabel();
}
public String getSql() {
return this.sql;
}
public void setSql(String sql) {
this.sql = sql;
}
@Override
public Environment findOne(String application, String profile, String label) {
String config = application;
if (StringUtils.isEmpty(label)) {
label = this.defaultLabel;
}
if (!StringUtils.hasText(profile)) {
profile = "default";
}
// fallback to previous logic: always include profile "default"
if (configIncomplete && !profile.startsWith("default")) {
profile = "default," + profile;
}
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
Environment environment = new Environment(application, profiles, label, null, null);
if (!config.startsWith("application")) {
config = "application," + config;
}
List<String> applications = new ArrayList<>(
new LinkedHashSet<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray(config))));
List<String> envs = new ArrayList<>(new LinkedHashSet<>(Arrays.asList(profiles)));
Collections.reverse(applications);
Collections.reverse(envs);
for (String env : envs) {
for (String app : applications) {
addPropertySource(environment, app, env, label);
}
}
// add properties without profile, equivalent to foo.yml, application.yml
if (!configIncomplete) {
for (String app : applications) {
addPropertySource(environment, app, null, label);
}
}
return environment;
}
private void addPropertySource(Environment environment, String application, String profile, String label) {
try {
Map<String, Object> source;
String name;
if (profile != null) {
source = this.jdbc.query(this.sql, this.extractor, application, profile, label);
name = application + "-" + profile;
}
else {
source = this.jdbc.query(this.sqlWithoutProfile, this.extractor, application, label);
name = application;
}
if (source != null && !source.isEmpty()) {
environment.add(new PropertySource(name, source));
}
}
catch (DataAccessException e) {
if (!failOnError) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to retrieve configuration from JDBC Repository", e);
}
}
else {
throw e;
}
}
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public boolean isFailOnError() {
return failOnError;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public static class PropertiesResultSetExtractor implements ResultSetExtractor<Map<String, Object>> {
@Override
public Map<String, Object> extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<String, Object> map = new LinkedHashMap<>();
while (rs.next()) {
map.put(rs.getString(1), rs.getString(2));
}
return map;
}
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* @author Dylan Roberts
*/
public class JdbcEnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<JdbcEnvironmentRepository, JdbcEnvironmentProperties> {
private final JdbcTemplate jdbc;
private final JdbcEnvironmentRepository.PropertiesResultSetExtractor extractor;
@Deprecated
public JdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc) {
this(jdbc, new JdbcEnvironmentRepository.PropertiesResultSetExtractor());
}
public JdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc,
JdbcEnvironmentRepository.PropertiesResultSetExtractor extractor) {
this.jdbc = jdbc;
this.extractor = extractor;
}
@Override
public JdbcEnvironmentRepository build(JdbcEnvironmentProperties environmentProperties) {
return new JdbcEnvironmentRepository(this.jdbc, environmentProperties, extractor);
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.ssh.HostKeyAlgoSupported;
import org.springframework.cloud.config.server.ssh.HostKeyAndAlgoBothExist;
import org.springframework.cloud.config.server.ssh.KnownHostsFileIsValid;
import org.springframework.cloud.config.server.ssh.PrivateKeyIsValid;
import org.springframework.validation.annotation.Validated;
/**
* @author Dylan Roberts
*/
@ConfigurationProperties("spring.cloud.config.server.git")
@Validated
@PrivateKeyIsValid
@HostKeyAndAlgoBothExist
@HostKeyAlgoSupported
@KnownHostsFileIsValid
public class MultipleJGitEnvironmentProperties extends JGitEnvironmentProperties {
/**
* Map of repository identifier to location and other properties.
*/
private Map<String, PatternMatchingJGitEnvironmentProperties> repos = new LinkedHashMap<>();
public Map<String, PatternMatchingJGitEnvironmentProperties> getRepos() {
return this.repos;
}
public void setRepos(Map<String, PatternMatchingJGitEnvironmentProperties> repos) {
this.repos = repos;
}
/**
* A {@link JGitEnvironmentProperties} that matches patterns.
*/
public static class PatternMatchingJGitEnvironmentProperties extends JGitEnvironmentProperties {
/**
* Pattern to match on application name and profiles.
*/
private String[] pattern = new String[0];
/**
* Name of repository (same as map key by default).
*/
private String name;
public PatternMatchingJGitEnvironmentProperties() {
}
public PatternMatchingJGitEnvironmentProperties(String uri) {
setUri(uri);
}
public String[] getPattern() {
return this.pattern;
}
public void setPattern(String[] pattern) {
this.pattern = pattern;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -1,350 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* {@link EnvironmentRepository} that based on one or more git repositories. Can be
* configured just like a single {@link JGitEnvironmentRepository}, for the "default"
* properties, and then additional repositories can be registered by name. The simplest
* form of the registration is just a map from name to uri (plus credentials if needed),
* where each app has its own git repository. As well as a name you can provide a pattern
* that matches on the application name (or even a list of patterns). Each sub-repository
* additionally can have its own search paths (subdirectories inside the top level of the
* repository).
*
* @author Andy Chan (iceycake)
* @author Dave Syer
* @author Gareth Clay
*
*/
public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
/**
* Map of repository identifier to location and other properties.
*/
private Map<String, PatternMatchingJGitEnvironmentRepository> repos = new LinkedHashMap<>();
private Map<String, JGitEnvironmentRepository> placeholders = new LinkedHashMap<>();
private final ObservationRegistry observationRegistry;
public MultipleJGitEnvironmentRepository(ConfigurableEnvironment environment,
MultipleJGitEnvironmentProperties properties, ObservationRegistry observationRegistry) {
super(environment, properties, observationRegistry);
this.observationRegistry = observationRegistry;
properties.getRepos().forEach((name, props) -> this.repos.put(name,
new PatternMatchingJGitEnvironmentRepository(environment, props, this.observationRegistry)));
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
for (String name : this.repos.keySet()) {
PatternMatchingJGitEnvironmentRepository repo = this.repos.get(name);
repo.setEnvironment(getEnvironment());
if (!StringUtils.hasText(repo.getName())) {
repo.setName(name);
}
if (repo.getPattern() == null || repo.getPattern().length == 0) {
repo.setPattern(new String[] { name });
}
if (repo.getTransportConfigCallback() == null) {
repo.setTransportConfigCallback(getTransportConfigCallback());
}
if (getTimeout() != 0 && repo.getTimeout() == 0) {
repo.setTimeout(getTimeout());
}
if (getRefreshRate() != 0 && repo.getRefreshRate() == 0) {
repo.setRefreshRate(getRefreshRate());
}
String user = repo.getUsername();
String passphrase = repo.getPassphrase();
if (user == null) {
repo.setUsername(getUsername());
repo.setPassword(getPassword());
}
if (passphrase == null) {
repo.setPassphrase(getPassphrase());
}
if (isSkipSslValidation()) {
repo.setSkipSslValidation(true);
}
repo.afterPropertiesSet();
}
if (!getBasedir().exists() && !getBasedir().mkdirs()) {
throw new IllegalStateException("Basedir does not exist and can not be created: " + getBasedir());
}
if (!getBasedir().getParentFile().canWrite()) {
throw new IllegalStateException(
"Cannot write parent of basedir (please configure a writable location): " + getBasedir());
}
}
public Map<String, PatternMatchingJGitEnvironmentRepository> getRepos() {
return this.repos;
}
public void setRepos(Map<String, PatternMatchingJGitEnvironmentRepository> repos) {
this.repos.putAll(repos);
}
@Override
public Locations getLocations(String application, String profile, String label) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository, application, profile, label)) {
try {
Environment source = candidate.findOne(application, profile, label, false);
if (source != null) {
return candidate.getLocations(application, profile, label);
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Cannot retrieve resource locations from " + candidate.getUri()
+ ", cause: (" + e.getClass().getSimpleName() + ") " + e.getMessage(), e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile, label);
if (candidate == this) {
return super.getLocations(application, profile, label);
}
return candidate.getLocations(application, profile, label);
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository, application, profile, label)) {
try {
if (label == null) {
label = candidate.getDefaultLabel();
}
Environment source = candidate.findOne(application, profile, label, includeOrigin);
if (source != null) {
return source;
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Cannot load configuration from " + candidate.getUri() + ", cause: ("
+ e.getClass().getSimpleName() + ") " + e.getMessage(), e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile, label);
if (label == null) {
label = candidate.getDefaultLabel();
}
try {
return findOneFromCandidate(candidate, application, profile, label, includeOrigin);
}
catch (Exception e) {
if (MultipleJGitEnvironmentProperties.MAIN_LABEL.equals(label) && isTryMasterBranch()) {
logger.info("Cannot find Environment with default label " + getDefaultLabel(), e);
logger.info("Will try to find Environment master label instead.");
candidate = getRepository(this, application, profile, MultipleJGitEnvironmentProperties.MASTER_LABEL);
return findOneFromCandidate(candidate, application, profile,
MultipleJGitEnvironmentProperties.MASTER_LABEL, includeOrigin);
}
throw e;
}
}
private Environment findOneFromCandidate(JGitEnvironmentRepository candidate, String application, String profile,
String label, boolean includeOrigin) {
if (candidate == this) {
return super.findOne(application, profile, label, includeOrigin);
}
return candidate.findOne(application, profile, label, includeOrigin);
}
private List<JGitEnvironmentRepository> getRepositories(JGitEnvironmentRepository repository, String application,
String profile, String label) {
List<JGitEnvironmentRepository> list = new ArrayList<>();
String[] profiles = profile == null ? new String[] { null }
: StringUtils.commaDelimitedListToStringArray(profile);
for (int i = profiles.length; i-- > 0;) {
list.add(getRepository(repository, application, profiles[i], label));
}
return list;
}
JGitEnvironmentRepository getRepository(JGitEnvironmentRepository repository, String application, String profile,
String label) {
if (!repository.getUri().contains("{")) {
return repository;
}
String key = repository.getUri();
// cover the case where label is in the uri, but no label was sent with the
// request
if (key.contains("{label}") && label == null) {
label = repository.getDefaultLabel();
}
if (application != null) {
key = key.replace("{application}", application);
}
if (profile != null) {
key = key.replace("{profile}", profile);
}
if (label != null) {
key = key.replace("{label}", label);
}
if (!this.placeholders.containsKey(key)) {
this.placeholders.put(key, getRepository(repository, key));
}
return this.placeholders.get(key);
}
private JGitEnvironmentRepository getRepository(JGitEnvironmentRepository source, String uri) {
JGitEnvironmentRepository repository = new JGitEnvironmentRepository(null, new JGitEnvironmentProperties(),
observationRegistry);
File basedir = repository.getBasedir();
BeanUtils.copyProperties(source, repository);
repository.setUri(uri);
repository.setBasedir(new File(source.getBasedir(), basedir.getName()));
return repository;
}
@Override
public void setOrder(int order) {
super.setOrder(order);
}
/**
* A {@link JGitEnvironmentProperties} that matches patterns.
*/
public static class PatternMatchingJGitEnvironmentRepository extends JGitEnvironmentRepository {
/**
* Pattern to match on application name and profiles.
*/
private String[] pattern = new String[0];
/**
* Name of repository (same as map key by default).
*/
private String name;
private final ObservationRegistry observationRegistry;
public PatternMatchingJGitEnvironmentRepository(ObservationRegistry observationRegistry) {
super(null, new JGitEnvironmentProperties(), observationRegistry);
this.observationRegistry = observationRegistry;
}
public PatternMatchingJGitEnvironmentRepository(ConfigurableEnvironment environment,
MultipleJGitEnvironmentProperties.PatternMatchingJGitEnvironmentProperties properties,
ObservationRegistry observationRegistry) {
super(environment, properties, observationRegistry);
this.observationRegistry = observationRegistry;
this.setPattern(properties.getPattern());
this.name = properties.getName();
}
public boolean matches(String application, String profile, String label) {
if (this.pattern == null || this.pattern.length == 0) {
return false;
}
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
for (int i = profiles.length; i-- > 0;) {
if (PatternMatchUtils.simpleMatch(this.pattern, application + "/" + profiles[i])) {
return true;
}
}
return false;
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
if (this.pattern == null || this.pattern.length == 0) {
return null;
}
if (PatternMatchUtils.simpleMatch(this.pattern, application + "/" + profile)) {
return super.findOne(application, profile, label, includeOrigin);
}
return null;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String[] getPattern() {
return this.pattern;
}
public void setPattern(String[] pattern) {
Collection<String> patterns = new ArrayList<>();
List<String> otherProfiles = new ArrayList<>();
for (String p : pattern) {
if (p != null) {
if (!p.contains("/")) {
// Match any profile
patterns.add(p + "/*");
}
if (!p.endsWith("*")) {
// If user supplies only one profile, allow others
otherProfiles.add(p + ",*");
}
}
patterns.add(p);
}
patterns.addAll(otherProfiles);
if (!patterns.contains(null)) {
// Make sure they are unique
patterns = new LinkedHashSet<>(patterns);
}
this.pattern = patterns.toArray(new String[0]);
}
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import java.util.Optional;
import io.micrometer.observation.ObservationRegistry;
import org.eclipse.jgit.transport.HttpTransport;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.cloud.config.server.support.GitCredentialsProviderFactory;
import org.springframework.cloud.config.server.support.TransportConfigCallbackFactory;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* @author Dylan Roberts
*/
public class MultipleJGitEnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<MultipleJGitEnvironmentRepository, MultipleJGitEnvironmentProperties> {
private ConfigurableEnvironment environment;
private ConfigServerProperties server;
private Optional<ConfigurableHttpConnectionFactory> connectionFactory;
private final TransportConfigCallbackFactory transportConfigCallbackFactory;
private final GitCredentialsProviderFactory gitCredentialsProviderFactory;
@Deprecated
public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server,
TransportConfigCallbackFactory transportConfigCallbackFactory) {
this(environment, server, Optional.empty(), transportConfigCallbackFactory,
new GitCredentialsProviderFactory());
}
@Deprecated
public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<ConfigurableHttpConnectionFactory> connectionFactory,
TransportConfigCallbackFactory transportConfigCallbackFactory) {
this(environment, server, connectionFactory, transportConfigCallbackFactory,
new GitCredentialsProviderFactory());
}
public MultipleJGitEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<ConfigurableHttpConnectionFactory> connectionFactory,
TransportConfigCallbackFactory transportConfigCallbackFactory,
GitCredentialsProviderFactory gitCredentialsProviderFactory) {
this.environment = environment;
this.server = server;
this.connectionFactory = connectionFactory;
this.transportConfigCallbackFactory = transportConfigCallbackFactory;
this.gitCredentialsProviderFactory = gitCredentialsProviderFactory;
}
@Override
public MultipleJGitEnvironmentRepository build(MultipleJGitEnvironmentProperties environmentProperties)
throws Exception {
if (this.connectionFactory.isPresent()) {
HttpTransport.setConnectionFactory(this.connectionFactory.get());
this.connectionFactory.get().addConfiguration(environmentProperties);
}
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment,
environmentProperties, ObservationRegistry.NOOP);
repository.setTransportConfigCallback(transportConfigCallbackFactory.build(environmentProperties));
if (this.server.getDefaultLabel() != null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
repository.setGitCredentialsProviderFactory(gitCredentialsProviderFactory);
repository.getRepos()
.forEach((name, repo) -> repo.setGitCredentialsProviderFactory(gitCredentialsProviderFactory));
return repository;
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.server.support.EnvironmentRepositoryProperties;
import org.springframework.core.Ordered;
/**
* @author Dylan Roberts
*/
@ConfigurationProperties("spring.cloud.config.server.native")
public class NativeEnvironmentProperties implements EnvironmentRepositoryProperties {
/**
* Flag to determine how to handle exceptions during decryption (default false).
*/
private Boolean failOnError = false;
/**
* Flag to determine whether label locations should be added.
*/
private Boolean addLabelLocations = true;
private String defaultLabel = "master";
/**
* Locations to search for configuration files. Defaults to the same as a Spring Boot
* app so [classpath:/,classpath:/config/,file:./,file:./config/].
*/
private String[] searchLocations = new String[0];
/**
* Version string to be reported for native repository.
*/
private String version;
private int order = Ordered.LOWEST_PRECEDENCE;
public Boolean getFailOnError() {
return this.failOnError;
}
public void setFailOnError(Boolean failOnError) {
this.failOnError = failOnError;
}
public Boolean getAddLabelLocations() {
return this.addLabelLocations;
}
public void setAddLabelLocations(Boolean addLabelLocations) {
this.addLabelLocations = addLabelLocations;
}
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
public String[] getSearchLocations() {
return this.searchLocations;
}
public void setSearchLocations(String[] searchLocations) {
this.searchLocations = searchLocations;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public int getOrder() {
return this.order;
}
@Override
public void setOrder(int order) {
this.order = order;
}
}

View File

@@ -1,410 +0,0 @@
/*
* Copyright 2013-2019 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.config.server.environment;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.context.config.ConfigDataEnvironmentUpdateListener;
import org.springframework.boot.context.config.ConfigDataLocation;
import org.springframework.boot.context.config.ConfigDataResource;
import org.springframework.boot.context.config.StandardConfigDataResource;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.StringUtils;
/**
* Simple implementation of {@link EnvironmentRepository} that uses a SpringApplication
* and configuration files located through the normal protocols. The resulting Environment
* is composed of property sources located using the application name as the config file
* stem (spring.config.name) and the environment name as a Spring profile.
*
* @author Dave Syer
* @author Roy Clarkson
* @author Venil Noronha
* @author Daniel Lavoie
*/
public class NativeEnvironmentRepository implements EnvironmentRepository, SearchPathLocator, Ordered {
private static final String[] DEFAULT_LOCATIONS = new String[] { "optional:classpath:/",
"optional:classpath:/config/", "optional:file:./", "optional:file:./config/" };
static final Pattern RESOURCE_PATTERN = Pattern.compile("Config resource '(.*?)' via location '(.*)'");
private static Log logger = LogFactory.getLog(NativeEnvironmentRepository.class);
private String defaultLabel;
/**
* Locations to search for configuration files. Defaults to the same as a Spring Boot
* app so [classpath:/,classpath:/config/,file:./,file:./config/].
*/
private String[] searchLocations;
/**
* Flag to determine how to handle exceptions during decryption (default false).
*/
private boolean failOnError;
/**
* Flag to determine whether label locations should be added.
*/
private boolean addLabelLocations;
/**
* Version string to be reported for native repository.
*/
private String version;
private ConfigurableEnvironment environment;
private int order;
private final ObservationRegistry observationRegistry;
public NativeEnvironmentRepository(ConfigurableEnvironment environment, NativeEnvironmentProperties properties,
ObservationRegistry observationRegistry) {
this.environment = environment;
this.addLabelLocations = properties.getAddLabelLocations();
this.defaultLabel = properties.getDefaultLabel();
this.failOnError = properties.getFailOnError();
this.order = properties.getOrder();
this.observationRegistry = observationRegistry;
setSearchLocations(properties.getSearchLocations());
this.version = properties.getVersion();
}
public boolean isFailOnError() {
return this.failOnError;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
public boolean isAddLabelLocations() {
return this.addLabelLocations;
}
public void setAddLabelLocations(boolean addLabelLocations) {
this.addLabelLocations = addLabelLocations;
}
public String getDefaultLabel() {
return this.defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
@Override
public Environment findOne(String config, String profile, String label) {
return findOne(config, profile, label, false);
}
@Override
public Environment findOne(String config, String profile, String label, boolean includeOrigin) {
try {
ConfigurableEnvironment environment = getEnvironment(config, profile, label);
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Map<org.springframework.core.env.PropertySource<?>, PropertySourceConfigData> propertySourceToConfigData = new HashMap<>();
ConfigDataEnvironmentPostProcessor.applyTo(environment, resourceLoader, null,
StringUtils.commaDelimitedListToSet(profile), new ConfigDataEnvironmentUpdateListener() {
@Override
public void onPropertySourceAdded(org.springframework.core.env.PropertySource<?> propertySource,
ConfigDataLocation location, ConfigDataResource resource) {
propertySourceToConfigData.put(propertySource,
new PropertySourceConfigData(location, resource));
}
});
environment.getPropertySources().remove("config-data-setup");
return clean(ObservationEnvironmentRepositoryWrapper
.wrap(this.observationRegistry, new PassthruEnvironmentRepository(environment))
.findOne(config, profile, label, includeOrigin), propertySourceToConfigData);
}
catch (Exception e) {
String msg = String.format("Could not construct context for config=%s profile=%s label=%s includeOrigin=%b",
config, profile, label, includeOrigin);
String completeMessage = NestedExceptionUtils.buildMessage(msg,
NestedExceptionUtils.getMostSpecificCause(e));
throw new FailedToConstructEnvironmentException(completeMessage, e);
}
}
@Override
public Locations getLocations(String application, String profile, String label) {
String[] locations = this.searchLocations;
if (this.searchLocations == null || this.searchLocations.length == 0) {
locations = DEFAULT_LOCATIONS;
}
Collection<String> output = new LinkedHashSet<String>();
if (label == null) {
label = this.defaultLabel;
}
for (String location : locations) {
String[] profiles = new String[] { profile };
if (profile != null) {
profiles = StringUtils.commaDelimitedListToStringArray(profile);
}
String[] apps = new String[] { application };
if (application != null) {
apps = StringUtils.commaDelimitedListToStringArray(application);
}
for (String prof : profiles) {
for (String app : apps) {
String value = location;
if (application != null) {
value = value.replace("{application}", app);
}
if (prof != null) {
value = value.replace("{profile}", prof);
}
if (label != null) {
value = value.replace("{label}", label);
}
if (!value.endsWith("/")) {
value = value + "/";
}
if (isDirectory(value)) {
output.add(value);
}
}
}
}
if (this.addLabelLocations) {
for (String location : locations) {
if (StringUtils.hasText(label)) {
String labelled = location + label.trim() + "/";
if (isDirectory(labelled)) {
output.add(labelled);
}
}
}
}
return new Locations(application, profile, label, this.version, output.toArray(new String[0]));
}
private ConfigurableEnvironment getEnvironment(String application, String profile, String label) {
ConfigurableEnvironment environment = new StandardEnvironment();
Map<String, Object> map = new HashMap<>();
map.put("spring.profiles.active", profile);
String config = application;
if (!config.equals("application")) {
config = "application," + config;
}
map.put("spring.config.name", config);
// map.put("encrypt.failOnError=" + this.failOnError);
map.put("spring.config.location",
StringUtils.arrayToDelimitedString(getLocations(application, profile, label).getLocations(), ";"));
// globally ignore config files that are not found
map.put("spring.config.on-not-found", "IGNORE");
environment.getPropertySources().addFirst(new MapPropertySource("config-data-setup", map));
return environment;
}
protected Environment clean(Environment env) {
return clean(env, Collections.emptyMap());
}
protected Environment clean(Environment env,
Map<org.springframework.core.env.PropertySource<?>, PropertySourceConfigData> propertySourceToConfigData) {
Environment result = new Environment(env.getName(), env.getProfiles(), env.getLabel(), this.version,
env.getState());
for (PropertySource source : env.getPropertySources()) {
String originalName = source.getName();
String name = originalName;
if (this.environment.getPropertySources().contains(name)) {
continue;
}
String[] locations = null;
PropertySourceConfigData configData = propertySourceToConfigData.get(source.getOriginalPropertySource());
// try and get information directly from ConfigData
if (configData != null && configData.resource instanceof StandardConfigDataResource) {
StandardConfigDataResource configDataResource = (StandardConfigDataResource) configData.resource;
// use StandardConfigDataResource as that format is expected still
name = configDataResource.toString();
locations = configDataLocations(configData.location.split());
}
else {
// if not, try and parse
Matcher matcher = RESOURCE_PATTERN.matcher(name);
if (matcher.find()) {
name = matcher.group(1);
locations = new String[] { matcher.group(2) };
}
}
name = name.replace("\\", "/"); // change windows path '\' into '/'
name = name.replaceAll("\\[(?=\\w:)", "[/"); // change [D:/path] into
// [/D:/path]
name = name.replace("applicationConfig: [", "");
name = name.replace("file [", "file:");
name = name.replace("class path resource [", "classpath:/");
if (name.indexOf('[') < 0) {
// only remove if there isn't a matching left bracket
name = name.replace("]", "");
}
if (this.searchLocations != null) {
boolean matches = matchesLocation(locations, name, result);
if (!matches) {
// Don't include this one: it wasn't matched by our search locations
if (logger.isDebugEnabled()) {
logger.debug("Not adding property source: " + originalName);
}
continue;
}
}
logger.info("Adding property source: " + originalName);
if (originalName.contains("document #")) {
// this is a multi-document file, use originalName for uniqueness.
result.add(new PropertySource(originalName, source.getSource()));
}
else {
// many other file tests rely on the mangled name
result.add(new PropertySource(name, source.getSource()));
}
}
return result;
}
private String[] configDataLocations(ConfigDataLocation[] locations) {
String[] stringLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
stringLocations[i] = locations[i].toString();
}
return stringLocations;
}
private boolean matchesLocation(String[] locations, String name, Environment result) {
boolean matches = false;
String normal = name;
if (normal.startsWith("file:")) {
normal = StringUtils.cleanPath(new File(normal.substring("file:".length())).getAbsolutePath());
}
String profile = result.getProfiles() == null ? null
: StringUtils.arrayToCommaDelimitedString(result.getProfiles());
for (String pattern : getLocations(result.getName(), profile, result.getLabel()).getLocations()) {
if (!pattern.contains(":")) {
pattern = "file:" + pattern;
}
if (pattern.startsWith("optional:")) {
pattern = pattern.substring("optional:".length());
}
if (pattern.startsWith("file:")) {
pattern = StringUtils.cleanPath(new File(pattern.substring("file:".length())).getAbsolutePath()) + "/";
}
final String finalPattern = pattern;
if (logger.isTraceEnabled()) {
logger.trace("Testing pattern: " + finalPattern + " with property source: " + name);
}
if (normal.startsWith(finalPattern)) {
matches = true;
break;
}
if (locations != null) {
matches = Arrays.stream(locations).map(this::cleanFileLocation)
.anyMatch(location -> location.startsWith(finalPattern));
if (matches) {
break;
}
}
}
return matches;
}
private String cleanFileLocation(String location) {
if (location.startsWith("file:")) {
return StringUtils.cleanPath(new File(location.substring("file:".length())).getAbsolutePath()) + "/";
}
return location;
}
public String[] getSearchLocations() {
return this.searchLocations;
}
public void setSearchLocations(String... locations) {
this.searchLocations = locations;
if (locations != null) {
for (int i = 0; i < locations.length; i++) {
String location = locations[i];
if (isDirectory(location) && !location.endsWith("/")) {
location = location + "/";
}
locations[i] = location;
}
}
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
private boolean isDirectory(String location) {
return !location.contains("{") && !location.endsWith(".properties") && !location.endsWith(".yml")
&& !location.endsWith(".yaml");
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
private final class PropertySourceConfigData {
private final ConfigDataLocation location;
private final ConfigDataResource resource;
private PropertySourceConfigData(ConfigDataLocation location, ConfigDataResource resource) {
this.location = location;
this.resource = resource;
}
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2018-2019 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.config.server.environment;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.cloud.config.server.config.ConfigServerProperties;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* @author Dylan Roberts
*/
public class NativeEnvironmentRepositoryFactory
implements EnvironmentRepositoryFactory<NativeEnvironmentRepository, NativeEnvironmentProperties> {
private ConfigurableEnvironment environment;
private ConfigServerProperties properties;
private final ObservationRegistry observationRegistry;
public NativeEnvironmentRepositoryFactory(ConfigurableEnvironment environment, ConfigServerProperties properties,
ObservationRegistry observationRegistry) {
this.environment = environment;
this.properties = properties;
this.observationRegistry = observationRegistry;
}
@Override
public NativeEnvironmentRepository build(NativeEnvironmentProperties environmentProperties) {
NativeEnvironmentRepository repository = new NativeEnvironmentRepository(this.environment,
environmentProperties, this.observationRegistry);
if (this.properties.getDefaultLabel() != null) {
repository.setDefaultLabel(this.properties.getDefaultLabel());
}
return repository;
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.environment;
/**
* @author Dave Syer
*
*/
@SuppressWarnings("serial")
public class NoSuchLabelException extends RepositoryException {
public NoSuchLabelException(String string) {
super(string);
}
public NoSuchLabelException(String string, Exception e) {
super(string, e);
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2015-2019 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.config.server.environment;
/**
* @author Dave Syer
*
*/
@SuppressWarnings("serial")
public class NoSuchRepositoryException extends RepositoryException {
public NoSuchRepositoryException(String string) {
super(string);
}
public NoSuchRepositoryException(String string, Exception e) {
super(string, e);
}
}

View File

@@ -1,61 +0,0 @@
/*
* 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.config.server.environment;
import io.micrometer.observation.Observation;
/**
* {@link Observation.Context} for Spring Cloud Config.
*
* @author Marcin Grzejszczak
* @since 4.0.0
*/
public class ObservationEnvironmentRepositoryContext extends Observation.Context {
private final Class<? extends EnvironmentRepository> clazz;
private final String application;
private final String profile;
private final String label;
public ObservationEnvironmentRepositoryContext(Class<? extends EnvironmentRepository> clazz, String application,
String profile, String label) {
this.clazz = clazz;
this.application = application;
this.profile = profile;
this.label = label;
}
public Class<? extends EnvironmentRepository> getEnvironmentRepositoryClass() {
return this.clazz;
}
public String getApplication() {
return this.application;
}
public String getProfile() {
return this.profile;
}
public String getLabel() {
return this.label;
}
}

View File

@@ -1,70 +0,0 @@
/*
* 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.config.server.environment;
import io.micrometer.common.KeyValues;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import org.springframework.util.StringUtils;
/**
* Default provider of key values for {@link ObservationEnvironmentRepositoryContext}.
*
* @author Marcin Grzejszczak
* @since 4.0.0
*/
class ObservationEnvironmentRepositoryObservationConvention
implements ObservationConvention<ObservationEnvironmentRepositoryContext> {
@Override
public KeyValues getLowCardinalityKeyValues(ObservationEnvironmentRepositoryContext context) {
KeyValues keyValues = KeyValues.empty();
keyValues = appendIfPresent(keyValues, DocumentedConfigObservation.LowCardinalityTags.ENVIRONMENT_CLASS,
context.getEnvironmentRepositoryClass().getName());
keyValues = appendIfPresent(keyValues, DocumentedConfigObservation.LowCardinalityTags.LABEL,
context.getLabel());
keyValues = appendIfPresent(keyValues, DocumentedConfigObservation.LowCardinalityTags.PROFILE,
context.getProfile());
return appendIfPresent(keyValues, DocumentedConfigObservation.LowCardinalityTags.APPLICATION,
context.getApplication());
}
private KeyValues appendIfPresent(KeyValues keyValues, KeyName profile, String value) {
if (StringUtils.hasText(value)) {
keyValues = keyValues.and(profile.withValue(value));
}
return keyValues;
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof ObservationEnvironmentRepositoryContext;
}
@Override
public String getName() {
return "spring.cloud.config.environment.find";
}
@Override
public String getContextualName(ObservationEnvironmentRepositoryContext context) {
return "env find";
}
}

View File

@@ -1,86 +0,0 @@
/*
* 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.config.server.environment;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.cloud.config.environment.Environment;
/**
* Wraps a {@link EnvironmentRepository} execution with observation.
*
* @author Marcin Grzejszczak
* @since 4.0.0
*/
public final class ObservationEnvironmentRepositoryWrapper implements EnvironmentRepository {
private static final ObservationEnvironmentRepositoryObservationConvention CONVENTION = new ObservationEnvironmentRepositoryObservationConvention();
private final ObservationRegistry registry;
private final EnvironmentRepository delegate;
/**
* Creates an instance of {@link ObservationEnvironmentRepositoryWrapper}.
* @param registry observation registry
* @param delegate environment repository delegate
*/
private ObservationEnvironmentRepositoryWrapper(ObservationRegistry registry, EnvironmentRepository delegate) {
this.registry = registry;
this.delegate = delegate;
}
/**
* Wraps the {@link EnvironmentRepository} in an observation representation.
* @param observationRegistry observation registry
* @param delegate repository to wrap
* @return wrapped repository
*/
public static EnvironmentRepository wrap(ObservationRegistry observationRegistry, EnvironmentRepository delegate) {
if (delegate instanceof ObservationEnvironmentRepositoryWrapper) {
return delegate;
}
return new ObservationEnvironmentRepositoryWrapper(observationRegistry, delegate);
}
@Override
public Environment findOne(String application, String profile, String label) {
ObservationEnvironmentRepositoryContext context = new ObservationEnvironmentRepositoryContext(
this.delegate.getClass(), application, profile, label);
return DocumentedConfigObservation.ENVIRONMENT_REPOSITORY
.observation(null, CONVENTION, () -> context, this.registry)
.observe(() -> this.delegate.findOne(application, profile, label));
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
ObservationEnvironmentRepositoryContext context = new ObservationEnvironmentRepositoryContext(
this.delegate.getClass(), application, profile, label);
return DocumentedConfigObservation.ENVIRONMENT_REPOSITORY
.observation(null, CONVENTION, () -> context, this.registry)
.observe(() -> this.delegate.findOne(application, profile, label, includeOrigin));
}
/**
* Returns the actual delegate.
* @return delegate
*/
public EnvironmentRepository getDelegate() {
return delegate;
}
}

Some files were not shown because too many files have changed in this diff Show More