Merge pull request #1 from spring-cloud/master

Update from original
This commit is contained in:
Ryan Lynch
2016-09-07 10:35:02 -07:00
committed by GitHub
5 changed files with 191 additions and 40 deletions

View File

@@ -3,6 +3,7 @@
:githubmaster: {github}/tree/master
:docslink: {githubmaster}/docs/src/main/asciidoc
:toc:
:nofooter:
include::intro.adoc[]
@@ -31,8 +32,8 @@ public class ConfigServer {
Like all Spring Boot apps it runs on port 8080 by default, but you
can switch it to the conventional port 8888 in various ways. The
easiest, which also sets a default configuration repository,
is by launching it with `spring.config.name=configserver` (there
easiest, which also sets a default configuration repository,
is by launching it with `spring.config.name=configserver` (there
is a `configserver.yml` in the Config Server jar). Another is
to use your own `application.properties`, e.g.
@@ -46,12 +47,12 @@ spring.cloud.config.server.git.uri: file://${user.home}/config-repo
where `${user.home}/config-repo` is a git repository containing
YAML and properties files.
NOTE: in Windows you need an extra "/" in the file URL if it is
NOTE: in Windows you need an extra "/" in the file URL if it is
absolute with a drive prefix, e.g. `file:///${user.home}/config-repo`.
[TIP]
====
Here's a recipe for creating the git repository in the example
Here's a recipe for creating the git repository in the example
above:
----
@@ -66,7 +67,7 @@ $ git commit -m "Add application.properties"
====
WARNING: using the local filesystem for your git repository is
intended for testing only. Use a server to host your
intended for testing only. Use a server to host your
configuration repositories in production.
WARNING: the initial clone of your configuration repository will
@@ -87,7 +88,7 @@ Server? The strategy that governs this behaviour is the
* `{application}` maps to "spring.application.name" on the client side;
* `{profile}` maps to "spring.profiles.active" on the client (comma separated list); and
* `{profile}` maps to "spring.profiles.active" on the client (comma separated list); and
* `{label}` which is a server side feature labelling a "versioned" set of config files.
@@ -112,7 +113,7 @@ spring:
----
(as usual with a Spring Boot application, these properties could also
be set as environment variables or command line arguments).
be set as environment variables or command line arguments).
If the repository is file-based, the server will create an
`Environment` from `application.yml` (shared between all clients), and
@@ -265,7 +266,7 @@ In this example the server searches for config files in the top level
and in the "foo/" sub-directory and also any sub-directory whose name
begins with "bar".
By default the server clones remote repositories when configuration
By default the server clones remote repositories when configuration
is first requested. The server can be configured to clone the repositories
at startup. For example at the top level:
@@ -289,11 +290,11 @@ spring:
team-c:
pattern: team-c-*
uri: http://git/team-a/config-repo.git
----
In this example the server clones team-a's config-repo on startup before it
accepts any requests. All other repositories will not be cloned until
In this example the server clones team-a's config-repo on startup before it
accepts any requests. All other repositories will not be cloned until
configuration from the repository is requested.
NOTE: Setting a repository to be cloned when the Config Server starts up can
@@ -305,7 +306,7 @@ not detect an error until an application requests configuration from that
configuration source.
To use HTTP basic authentication on the remote repository add the
"username" and "password" properties separately (not in the URL),
"username" and "password" properties separately (not in the URL),
e.g.
[source,yaml]
@@ -352,8 +353,8 @@ spring:
----
searches the repository for files in the same name as the directory
(as well as the top level). Wildcards are also valid in a search
path with placeholders (any matching directory is included in the
(as well as the top level). Wildcards are also valid in a search
path with placeholders (any matching directory is included in the
search).
==== Version Control Backend Filesystem Use
@@ -367,7 +368,7 @@ Git, but just loads the config files from the local classpath or file
system (any static URL you want to point to with
"spring.cloud.config.server.native.searchLocations"). To use the
native profile just launch the Config Server with
"spring.profiles.active=native".
"spring.profiles.active=native".
NOTE: Remember to use the `file:` prefix for file resources (the
default without a prefix is usually the classpath). Just as with any
@@ -383,7 +384,7 @@ property sources present in the server are removed before being sent
to the client.
TIP: A filesystem backend is great for getting started quickly and
for testing. To use it in production you need to be sure that the
for testing. To use it in production you need to be sure that the
file system is reliable, and shared across all instances of the
Config Server.
@@ -622,7 +623,7 @@ You can disable the Health Indicator by setting `spring.cloud.config.server.heal
You are free to secure your Config Server in any way that makes sense
to you (from physical network security to OAuth2 bearer
tokens), and Spring Security and Spring Boot make it easy to do pretty
much anything.
much anything.
To use the default Spring Boot configured HTTP Basic security, just
include Spring Security on the classpath (e.g. through
@@ -699,14 +700,14 @@ are special characters ('+' is particularly tricky).
Take the encrypted value and add the `{cipher}` prefix before you put
it in the YAML or properties file, and before you commit and push it
to a remote, potentially insecure store.
to a remote, potentially insecure store.
The `/encrypt` and `/decrypt` endpoints also both accept paths of the
form `/*/{name}/{profiles}` which can be used to control cryptography
per application (name) and profile when clients call into the main
Environment resource.
NOTE: to control the cryptography in this granular way you must also
NOTE: to control the cryptography in this granular way you must also
provide a `@Bean` of type `TextEncryptorLocator` that creates a
different encryptor per name and profiles. The one that is provided
by default does not do this (so all encryptions use the same key).
@@ -747,8 +748,8 @@ PEM-encoded text value (in `encrypt.key`), or via a keystore (e.g. as
created by the `keytool` utility that comes with the JDK). The
keystore properties are `encrypt.keyStore.\*` with `*` equal to
* `location` (a `Resource` location),
* `password` (to unlock the keystore) and
* `location` (a `Resource` location),
* `password` (to unlock the keystore) and
* `alias` (to identify which key in the store is to be
used).
@@ -1082,7 +1083,7 @@ an Exception.
=== Config Client Retry
If you expect that the config server may occasionally be unavailable when
your app starts, you can ask it to keep trying after a failure. First you need
your app starts, you can ask it to keep trying after a failure. First you need
to set `spring.cloud.config.failFast=true`, and then you need to add
`spring-retry` and `spring-boot-starter-aop` to your classpath. The default
behaviour is to retry 6 times with an initial backoff interval of 1000ms and an
@@ -1105,10 +1106,10 @@ All of them can be overridden by setting `spring.cloud.config.\*`
(where `*` is "name", "profile" or "label"). The "label" is useful for
rolling back to previous versions of configuration; with the default
Config Server implementation it can be a git label, branch name or
commit id. Label can also be provided as a comma-separated list, in
commit id. Label can also be provided as a comma-separated list, in
which case the items in the list are tried on-by-one until one succeeds.
This can be useful when working on a feature branch, for instance,
when you might want to align the config label with your branch, but
when you might want to align the config label with your branch, but
make it optional (e.g. `spring.cloud.config.label=myfeature,develop`).
=== Security

35
mvnw vendored
View File

@@ -46,15 +46,6 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then
fi
VERSION=$(awk '/<parent/ {l=4}; (l-- > 0) {$0=$0} 1' `dirname $0`/pom.xml| grep '<version' | head -1 | sed -e 's/.*>\(.*\)<.*/\1/')
if echo $VERSION | egrep -q 'M|RC'; then
echo Activating \"milestone\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone"
else
echo Deactivating \"milestone\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
@@ -66,27 +57,27 @@ case "`uname`" in
#
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
# for the new JDKs provided by Oracle.
#
#
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
#
# Oracle JDKs
#
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
fi
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
#
@@ -228,16 +219,30 @@ concat_lines() {
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# Provide a "standardized" way to retrieve the CLI args that will
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
VERSION=$(exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
-Dexec.executable="echo" -Dexec.args='${project.version}' ${WRAPPER_LAUNCHER} -q --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec )
if echo $VERSION | egrep -q 'M|RC'; then
echo Activating \"milestone\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone"
else
echo Deactivating \"milestone\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
fi
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@"

View File

@@ -24,14 +24,18 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
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.PullCommand;
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.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
@@ -55,6 +59,7 @@ import com.jcraft.jsch.Session;
* @author Dave Syer
* @author Roy Clarkson
* @author Marcos Barbero
* @author Daniel Lavoie
*/
public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
implements EnvironmentRepository, SearchPathLocator, InitializingBean {
@@ -166,6 +171,14 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
Ref ref = checkout(git, label);
if (shouldPull(git, ref)) {
pull(git, label, ref);
if (!isClean(git)) {
logger.warn("The local repository is dirty. Reseting it to origin/"
+ label + ".");
fetch(git, label, "origin");
resetHard(git, label, "refs/remotes/origin/" + label);
}
}
return ref;
}
@@ -264,6 +277,36 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
return isBranch(git, label) && !isLocalBranch(git, label);
}
private void fetch(Git git, String label, String remote) {
FetchCommand fetch = git.fetch().setRemote(remote);
setTimeout(fetch);
try {
if (hasText(getUsername())) {
setCredentialsProvider(fetch);
}
fetch.call();
}
catch (Exception ex) {
this.logger.warn("Could not fetch remote for " + label + " remote: " + git
.getRepository().getConfig().getString("remote", "origin", "url"));
}
}
private void resetHard(Git git, String label, String ref) {
ResetCommand reset = git.reset();
reset.setRef(ref);
reset.setMode(ResetType.HARD);
try {
reset.call();
}
catch (Exception ex) {
this.logger.warn("Could not reset to remote for " + label + " (current ref="
+ ref + "), remote: " + git.getRepository().getConfig()
.getString("remote", "origin", "url"));
}
}
/**
* Assumes we are on a tracking branch (should be safe)
*/
@@ -369,6 +412,20 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
pull.setTimeout(this.timeout);
}
private boolean isClean(Git git) {
StatusCommand status = git.status();
try {
return status.call().isClean();
}
catch (Exception e) {
this.logger
.warn("Could not execute status command on local repository. Cause: ("
+ e.getClass().getSimpleName() + ") " + e.getMessage());
return false;
}
}
private void trackBranch(Git git, CheckoutCommand checkout, String label) {
checkout.setCreateBranch(true).setName(label)
.setUpstreamMode(SetupUpstreamMode.TRACK)

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.cloud.config.server.test;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
@@ -24,8 +27,22 @@ import java.io.IOException;
/**
* @author Dave Syer
* @author Daniel Lavoie
*/
public class ConfigServerTestUtils {
public static Repository prepareBareRemote() throws IOException {
// Create a folder in the temp folder that will act as the remote repository
File remoteDir = File.createTempFile("remote", "");
remoteDir.delete();
remoteDir.mkdirs();
// Create a bare repository
FileKey fileKey = FileKey.exact(remoteDir, FS.DETECTED);
Repository remoteRepo = fileKey.open(false);
remoteRepo.create(true);
return remoteRepo;
}
public static String prepareLocalRepo() throws IOException {
return prepareLocalRepo("./", "target/repos", "config-repo", "target/config");

View File

@@ -26,12 +26,21 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.util.FileUtils;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
@@ -48,6 +57,7 @@ import org.springframework.util.StreamUtils;
/**
* @author Dave Syer
* @author Roy Clarkson
* @author Daniel Lavoie
*/
public class JGitEnvironmentRepositoryIntegrationTests {
@@ -108,6 +118,67 @@ public class JGitEnvironmentRepositoryIntegrationTests {
environment.getPropertySources().get(0).getSource().get("foo"));
}
/**
* Tests a special use case where the remote repository has been updated with a forced
* push conflicting with the local repo of the Config Server. The Config Server has to
* reset hard on the new reference because a simple pull operation could result in a
* conflicting local repository.
*/
@Test
public void pullDirtyRepo() throws Exception {
ConfigServerTestUtils.prepareLocalRepo();
String uri = ConfigServerTestUtils.copyLocalRepo("config-copy");
// Create a remote bare repository.
Repository remote = ConfigServerTestUtils.prepareBareRemote();
Git git = Git.open(ResourceUtils.getFile(uri).getAbsoluteFile());
StoredConfig config = git.getRepository().getConfig();
config.setString("remote", "origin", "url",
remote.getDirectory().getAbsolutePath());
config.setString("remote", "origin", "fetch",
"+refs/heads/*:refs/remotes/origin/*");
config.save();
// Pushes the raw branch to remote repository.
git.push().call();
String commitToRevertBeforePull = git.log().setMaxCount(1).call().iterator()
.next().getName();
this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false)
.run("--spring.cloud.config.server.git.uri=" + uri);
JGitEnvironmentRepository repository = this.context
.getBean(JGitEnvironmentRepository.class);
// Fetches the repository for the first time.
repository.getLocations("bar", "test", "raw");
// Resets to the original commit.
git.reset().setMode(ResetType.HARD).setRef("master").call();
// Generate a conflicting commit who will be forced on the origin.
Path applicationFilePath = Paths
.get(ResourceUtils.getFile(uri).getAbsoluteFile() + "/application.yml");
Files.write(applicationFilePath,
Arrays.asList("info:", " foo: bar", "raw: false"),
StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING);
git.add().addFilepattern(".").call();
git.commit().setMessage("Conflicting commit.").call();
git.push().setForce(true).call();
// Reset to the raw branch.
git.reset().setMode(ResetType.HARD).setRef(commitToRevertBeforePull).call();
// Triggers the repository refresh.
repository.getLocations("bar", "test", "raw");
Assert.assertTrue("Local repository is not cleaned after retreiving resources.",
git.status().call().isClean());
}
@Test
public void nested() throws IOException {
String uri = ConfigServerTestUtils.prepareLocalRepo("another-config-repo");