Docker images (#527)

with this change we're adding generation of 2 docker images - verifier and stub runner
we also added a project `spring-cloud-contract-stub-runner-boot` which contains a far jar with stub runner boot

fixes gh-526, gh-516
This commit is contained in:
Marcin Grzejszczak
2018-01-24 17:07:22 +01:00
committed by GitHub
parent a5bdb32b7c
commit 37d7e8cfd3
24 changed files with 1652 additions and 1 deletions

View File

@@ -1547,11 +1547,15 @@ IMPORTANT: You need to have all the necessary Groovy plugins
Intellij IDEA having both Eclipse Groovy Compiler Plugin & GMavenPlus Intellij Plugin
results in properly imported project.
IMPORTANT: Spring Cloud Contract builds Docker images. Remember to
have Docker installed.
=== Project structure
Here you can find the Spring Cloud Contract folder structure
```
├── docker
├── samples
├── scripts
├── spring-cloud-contract-dependencies
@@ -1564,6 +1568,7 @@ Here you can find the Spring Cloud Contract folder structure
└── tests
```
- `docker` - folder contains docker images
- `samples` - folder contains test samples together with standalone ones used also to build documentation
- `scripts` - contains scripts to build and test `Spring Cloud Contract` with Maven, Gradle and standalone projects
- `spring-cloud-contract-dependencies` - contains Spring Cloud Contract BOM

40
docker/pom.xml Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-parent</artifactId>
<version>1.2.3.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-docker-parent</artifactId>
<packaging>pom</packaging>
<version>1.2.3.BUILD-SNAPSHOT</version>
<name>Spring Cloud Contract Docker Parent</name>
<description>Spring Cloud Contract Docker Parent</description>
<properties>
<docker.registry.organization>spring-cloud</docker.registry.organization>
</properties>
<modules>
<module>spring-cloud-contract-docker</module>
<module>spring-cloud-contract-stub-runner-docker</module>
</modules>
<build>
<plugins>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,12 @@
FROM anapsix/alpine-java:8_jdk
MAINTAINER Marcin Grzejszczak <mgrzejszczak@pivotal.io>
COPY project /spring-cloud-contract/
WORKDIR /spring-cloud-contract/
# Let's copy the downloaded deps to .m2 and the gradle cache
COPY target/maven_dependencies /root/.m2/repository/
COPY target/gradle_dependencies /root/.gradle/
# Let's download gradle wrapper if for some reason it hasn't been downloaded
RUN ./gradlew clean build --stacktrace -x copyOutput || echo "Expected to fail"
CMD ["./build.sh"]

View File

@@ -0,0 +1,22 @@
#!/bin/bash
set -e
WRAPPER_VERSION="4.4.1"
GRADLE_BIN_DIR="gradle-${WRAPPER_VERSION}-bin"
GRADLE_WRAPPER_DIR="${HOME}/.gradle/wrapper/dists/${GRADLE_BIN_DIR}"
CURRENT_DIR="$( pwd )"
GRADLE_OUTPUT_DIR="${CURRENT_DIR}/target/gradle_dependencies/"
pushd project
rm -rf .gradle
./gradlew wrapper --gradle-version "${WRAPPER_VERSION}"
./gradlew clean build -g "${GRADLE_OUTPUT_DIR}" -x copyOutput || echo "Expected to fail the build"
if [ -d "${GRADLE_WRAPPER_DIR}" ]; then
echo "Copying Gradle Wrapper version [${WRAPPER_VERSION}]"
mkdir -p "${GRADLE_OUTPUT_DIR}/wrapper/dists/"
cp -r "${GRADLE_WRAPPER_DIR}" "${GRADLE_OUTPUT_DIR}/wrapper/dists/"
else
echo "Gradle Wrapper [${GRADLE_WRAPPER_DIR}] not found. Will not copy it"
fi
rm -rf build
popd

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-docker-parent</artifactId>
<version>1.2.3.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-docker</artifactId>
<packaging>pom</packaging>
<version>1.2.3.BUILD-SNAPSHOT</version>
<name>Spring Cloud Contract Docker</name>
<description>Spring Cloud Contract Docker</description>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-ant</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-groovydoc</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-gradle-plugin</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.spring.gradle</groupId>
<artifactId>dependency-management-plugin</artifactId>
<version>1.0.4.RELEASE</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/maven_dependencies</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<useRepositoryLayout>true</useRepositoryLayout>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>gradle</id>
<phase>package</phase>
<configuration>
<executable>./get_dependencies.sh</executable>
</configuration>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.7</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>${docker.registry.organization}/spring-cloud-contract</repository>
<tag>${project.version}</tag>
<username>${env.DOCKER_HUB_USERNAME}</username>
<password>${env.DOCKER_HUB_PASSWORD}</password>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,114 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
if (!"${verifierVersion}".contains("RELEASE")) {
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
maven { url "http://repo.spring.io/release" }
}
}
dependencies {
classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE"
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifierVersion}"
}
}
group = getProp("PROJECT_GROUP") ?: 'com.example'
version = getProp("PROJECT_VERSION") ?: '0.0.1-SNAPSHOT'
repositories {
mavenLocal()
mavenCentral()
if (!"${verifierVersion}".contains("RELEASE")) {
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
maven { url "http://repo.spring.io/release" }
}
}
apply plugin: 'groovy'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'spring-cloud-contract'
apply plugin: 'maven-publish'
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${verifierVersion}"
}
}
dependencies {
testCompile("org.springframework.cloud:spring-cloud-starter-contract-verifier")
}
test {
testLogging {
exceptionFormat = 'full'
afterSuite { desc, result ->
if (!desc.parent) {
println "Results: (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
if (result.testCount == 0) {
throw new IllegalStateException("No tests were found. Failing the build")
}
}
}
}
}
// tests - contracts
contracts {
baseClassForTests = "contracts.RestBase"
testMode = "EXPLICIT"
contractsDslDir = new File("/contracts")
}
task wrapper(type: Wrapper) {
gradleVersion = '4.4.1'
}
task cleanOutput(type: Delete) {
def dirName = "/spring-cloud-contract-output"
file(dirName).list().each {
f -> delete "${dirName}/${f}"
}
}
task copyOutput(type: Copy) {
dependsOn("cleanOutput")
from 'build'
into '/spring-cloud-contract-output'
}
test.finalizedBy("copyOutput")
publishing {
repositories {
maven {
url getProp('REPO_WITH_BINARIES_URL') ?: 'http://localhost:8081/artifactory/libs-release-local'
credentials {
username getProp('REPO_WITH_BINARIES_USERNAME') ?: 'admin'
password getProp('REPO_WITH_BINARIES_PASSWORD') ?: 'password'
}
}
}
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
// explicitly disable artifacts publication
String publishArtifacts = getProp("PUBLISH_ARTIFACTS") ?: "true"
boolean publishEnabled = Boolean.parseBoolean(publishArtifacts)
publish.setEnabled(publishEnabled)
project.tasks.findAll { it.name.startsWith('publish') }*.setEnabled(publishEnabled)
String getProp(String propName) {
return hasProperty(propName) ?
(getProperty(propName) ?: System.properties[propName]) : System.properties[propName] ?:
System.getenv(propName)
}

View File

@@ -0,0 +1,7 @@
#!/bin/bash
export PROJECT_NAME="${PROJECT_NAME:-example}"
echo "Setting project name to [${PROJECT_NAME}]"
echo "rootProject.name='${PROJECT_NAME}'" >> settings.gradle
echo "Running the build"
./gradlew clean build publish --stacktrace

View File

@@ -0,0 +1,3 @@
org.gradle.daemon=false
verifierVersion=1.2.3.BUILD-SNAPSHOT
bootVersion=1.5.9.RELEASE

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip

View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,41 @@
package contracts;
import io.restassured.RestAssured;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.runner.RunWith;
import org.junit.validator.ValidateWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.StringUtils;
/**
* @author Marcin Grzejszczak
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RestBase.Config.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public abstract class RestBase {
@Value("${APPLICATION_BASE_URL}") String url;
@Value("${APPLICATION_USERNAME:}") String username;
@Value("${APPLICATION_PASSWORD:}") String password;
@Before
public void setup() {
RestAssured.baseURI = this.url;
if (StringUtils.hasText(this.username)) {
RestAssured.authentication =
RestAssured.basic(this.username, this.password);
}
}
@Configuration
@EnableAutoConfiguration
protected static class Config {
}
}

View File

@@ -0,0 +1,9 @@
FROM anapsix/alpine-java:8_jdk
MAINTAINER Marcin Grzejszczak <mgrzejszczak@pivotal.io>
ENV SERVER_PORT 8083
VOLUME /tmp
ADD target/libs/stub-runner-boot.jar stub-runner-boot.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/stub-runner-boot.jar"]

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-docker-parent</artifactId>
<version>1.2.3.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-stub-runner-docker</artifactId>
<packaging>pom</packaging>
<version>1.2.3.BUILD-SNAPSHOT</version>
<name>Spring Cloud Contract Stub Runner Docker</name>
<description>Spring Cloud Contract Stub Runner Docker</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner-boot</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-contract-stub-runner-boot</artifactId>
<version>${project.version}</version>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<destFileName>stub-runner-boot.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.7</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>${docker.registry.organization}/spring-cloud-contract-stub-runner</repository>
<tag>${project.version}</tag>
<username>${env.DOCKER_HUB_USERNAME}</username>
<password>${env.DOCKER_HUB_PASSWORD}</password>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -5,11 +5,15 @@ IMPORTANT: You need to have all the necessary Groovy plugins
Intellij IDEA having both Eclipse Groovy Compiler Plugin & GMavenPlus Intellij Plugin
results in properly imported project.
IMPORTANT: Spring Cloud Contract builds Docker images. Remember to
have Docker installed.
=== Project structure
Here you can find the Spring Cloud Contract folder structure
```
├── docker
├── samples
├── scripts
├── spring-cloud-contract-dependencies
@@ -22,6 +26,7 @@ Here you can find the Spring Cloud Contract folder structure
└── tests
```
- `docker` - folder contains docker images
- `samples` - folder contains test samples together with standalone ones used also to build documentation
- `scripts` - contains scripts to build and test `Spring Cloud Contract` with Maven, Gradle and standalone projects
- `spring-cloud-contract-dependencies` - contains Spring Cloud Contract BOM

View File

@@ -1,9 +1,10 @@
== Spring Cloud Contract Verifier Setup
You can set up Spring Cloud Contract Verifier in either of two ways
You can set up Spring Cloud Contract Verifier in the following ways:
* <<gradle-project,As a Gradle project>>
* <<maven-project,As a Maven project>>
* <<docker-project,As a Docker project>>
[[gradle-project]]
=== Gradle Project
@@ -898,3 +899,162 @@ More details about WireMock scenarios can be found at
http://wiremock.org/stateful-behaviour.html[http://wiremock.org/stateful-behaviour.html]
Spring Cloud Contract Verifier also generates tests with a guaranteed order of execution.
[[docker-project]]
=== Docker Project
We're publishing a `spring-cloud/spring-cloud-contract` Docker image
that contains a project that will generate tests and execute them in `EXPLICIT` mode
against a running application.
TIP: The `EXPLICIT` mode means that the tests generated from contracts will send
real requests and not the mocked ones.
==== Short intro to Maven, JARs and Binary storage
Since the Docker image can be used by non JVM projects, it's good to
explain the basic terms behind Spring Cloud Contract packaging defaults.
Part of the following definitions were taken from the https://maven.apache.org/glossary.html[Maven Glossary]
- `Project`: Maven thinks in terms of projects. Everything that you
will build are projects. Those projects follow a well defined
“Project Object Model”. Projects can depend on other projects,
in which case the latter are called “dependencies”. A project may
consistent of several subprojects, however these subprojects are still
treated equally as projects.
- `Artifact`: An artifact is something that is either produced or used
by a project. Examples of artifacts produced by Maven for a project
include: JARs, source and binary distributions. Each artifact
is uniquely identified by a group id and an artifact ID which is
unique within a group.
- `JAR`: JAR stands for Java ARchive. It's a format based on
the ZIP file format. Spring Cloud Contract packages the contracts and generated
stubs in a JAR file.
- `GroupId`: A group ID is a universally unique identifier for a project.
While this is often just the project name (eg. commons-collections),
it is helpful to use a fully-qualified package name to distinguish it
from other projects with a similar name (eg. org.apache.maven).
Typically, when published to the Artifact Manager, the `GroupId` will get
slash separated and form part of the URL. E.g. for group id `com.example`
and artifact id `application` would be `/com/example/application/`.
- `Classifier`: The Maven dependency notation looks as follows:
`groupId:artifactId:version:classifier`. The classifier is additional suffix
passed to the dependency. E.g. `stubs`, `sources`. The same dependency
e.g. `com.example:application` can produce multiple artifacts that
differ from each other with the classifier.
- `Artifact manager`: When you generate binaries / sources / packages, you would
like them to be available for others to download / reference or reuse. In case
of the JVM world those artifacts would be JARs, for Ruby these are gems
and for Docker those would be Docker images. You can store those artifacts
in a manager. Examples of such managers can be https://jfrog.com/artifactory/[Artifactory]
or http://www.sonatype.org/nexus/[Nexus].
==== How it works
The image searches for contracts under the `/contracts` folder.
The output from running the tests will be available under
`/spring-cloud-contract/build` folder (it's useful for debugging
purposes).
It's enough for you to mount your contracts, pass the environment variables
and the image will:
- generate the contract tests
- execute the tests against the provided URL
- generate the http://wiremock.org[WireMock] stubs
- (optional - turned on by default) publish the stubs to a Artifact Manager
===== Environment Variables
The Docker image requires some environment variables to point to
your running application, to the Artifact manager instance etc.
- `PROJECT_GROUP` - your project's group id. Defaults to `com.example`.
- `PROJECT_VERSION` - your project's version. Defaults to `0.0.1-SNAPSHOT`
- `PROJECT_NAME` - artifact id. Defaults to `example`
- `REPO_WITH_BINARIES_URL` - URL of your Artifact Manager. Defaults to `http://localhost:8081/artifactory/libs-release-local`
which is the default URL of https://jfrog.com/artifactory/[Artifactory] running locally
- `REPO_WITH_BINARIES_USERNAME` - (optional) username when the Artifact Manager is secured
- `REPO_WITH_BINARIES_PASSWORD` - (optional) password when the Artifact Manager is secured
- `PUBLISH_ARTIFACTS` - if set to `true` then will publish artifact to binary storage. Defaults to `true`.
These environment variables are used when tests are executed:
- `APPLICATION_BASE_URL` - url against which tests should be executed.
Remember that it has to be accessible from the Docker container (e.g. `localhost`
will not work)
- `APPLICATION_USERNAME` - (optional) username for basic authentication to your application
- `APPLICATION_PASSWORD` - (optional) password for basic authentication to your application
==== Example of usage
Let's take a look at a simple MVC application
```bash
$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
```
The contracts are available under `/contracts` folder.
[[docker-server-side]]
==== Server side (nodejs)
Since we want to run tests, we could just execute:
```bash
$ npm test
```
however, for learning purposes, let's split it into pieces:
```bash
# Stop docker infra (nodejs, artifactory)
$ ./stop_infra.sh
# Start docker infra (nodejs, artifactory)
$ ./setup_infra.sh
# Kill & Run app
$ pkill -f "node app"
$ nohup node app &
# Prepare environment variables
$ SC_CONTRACT_DOCKER_VERSION="..."
$ APP_IP="192.168.0.100"
$ APP_PORT="3000"
$ ARTIFACTORY_PORT="8081"
$ APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
$ ARTIFACTORY_URL="http://${APP_IP}:${ARTIFACTORY_PORT}/artifactory/libs-release-local"
$ CURRENT_DIR="$( pwd )"
$ CURRENT_FOLDER_NAME=${PWD##*/}
$ PROJECT_VERSION="0.0.1.RELEASE"
# Execute contract tests
$ docker run --rm -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" -e "PUBLISH_ARTIFACTS=true" -e "PROJECT_NAME=${CURRENT_FOLDER_NAME}" -e "REPO_WITH_BINARIES_URL=${ARTIFACTORY_URL}" -e "PROJECT_VERSION=${PROJECT_VERSION}" -v "${CURRENT_DIR}/contracts/:/contracts:ro" -v "${CURRENT_DIR}/node_modules/spring-cloud-contract/output:/spring-cloud-contract-output/" spring-cloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
# Kill app
$ pkill -f "node app"
```
What will happen is that via bash scripts:
- infrastructure will be set up (MongoDb, Artifactory).
In real life scenario you would just run the NodeJS application
with mocked database. In this example we want to show how we can
benefit from Spring Cloud Contract in no time.
- due to those constraints the contracts also represent the
stateful situation
** first request is a `POST` that causes data to get inserted to the database
** second request is a `GET` that returns a list of data with 1 previously inserted element
- the NodeJS application will be started (on port `3000`)
- contract tests will be generated via Docker and tests
will be executed against the running application
** the contracts will be taken from `/contracts` folder.
** the output of the test execution is available under
`node_modules/spring-cloud-contract/output`.
- the stubs will be uploaded to Artifactory. You can check them out
under http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/ .
The stubs will be here http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/bookstore-0.0.1.RELEASE-stubs.jar.
To see how the client side looks like check out the <<stubrunner-docker>> section.

View File

@@ -112,3 +112,67 @@ IMPORTANT: Starting with version 1.0.4, you can provide a range of versions that
would like the Stub Runner to take into consideration. You can read more about the
https://wiki.eclipse.org/Aether/New_and_Noteworthy#Version_Ranges[Aether versioning
ranges here].
[[stubrunner-docker]]
=== Stub Runner Docker
We're publishing a `spring-cloud/spring-cloud-contract-stub-runner` Docker image
that will start the standalone version of Stub Runner.
If you want to learn more about the basics of Maven, artifact ids,
group ids, classifiers and Artifact Managers, just click here <<docker-project>>.
==== How to use it
Just execute the docker image. You can pass any of the <<common-properties-junit-spring>>
as environment variables. The convention is that all the
letters should be upper case. The camel case notation should
and the dot (`.`) should be separated via underscore (`_`). E.g.
the `stubrunner.repositoryRoot` property should be represented
as a `STUBRUNNER_REPOSITORY_ROOT` environment variable.
==== Example of client side usage in a non JVM project
We'd like to use the stubs created in this <<docker-server-side>> step.
Let's assume that we want to run the stubs on port `9876`. The NodeJS code
is available here:
```bash
$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
```
Let's run the Stub Runner Boot application with the stubs.
```bash
# Provide the Spring Cloud Contract Docker version
$ SC_CONTRACT_DOCKER_VERSION="..."
# The IP at which the app is running and Docker container can reach it
$ APP_IP="192.168.0.100"
# Spring Cloud Contract Stub Runner properties
$ STUBRUNNER_PORT="8083"
# Stub coordinates 'groupId:artifactId:version:classifier:port'
$ STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876"
$ STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local"
# Run the docker with Stub Runner Boot
$ docker run --rm -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" -e "STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" -p "9876:9876" spring-cloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}"
```
What's happening is that
- a standalone Stub Runner application got started
- it downloaded the stub with coordinates `com.example:bookstore:0.0.1.RELEASE:stubs` on port `9876`
- it got downloaded from Artifactory running at `http://192.168.0.100:8081/artifactory/libs-release-local`
- after a while Stub Runner will be running on port `8083`
- and the stubs will be running at port `9876`
On the server side we built a stateful stub. Let's use curl to assert
that the stubs are setup properly.
```bash
# let's execute the first request (no response is returned)
$ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books
# Now time for the second request
$ curl -X GET http://localhost:9876/api/books
# You will receive contents of the JSON
```

View File

@@ -37,6 +37,7 @@
<modules>
<module>spring-cloud-contract-dependencies</module>
<module>docs</module>
<module>docker</module>
<module>spring-cloud-contract-shade</module>
<module>spring-cloud-contract-wiremock</module>
<module>spring-cloud-contract-verifier</module>
@@ -44,6 +45,7 @@
<module>spring-cloud-contract-stub-runner</module>
<module>spring-cloud-contract-starters</module>
<module>spring-cloud-contract-tools</module>
<module>spring-cloud-contract-stub-runner-boot</module>
<module>tests</module>
<module>samples</module>
</modules>

View File

@@ -50,6 +50,11 @@
<artifactId>spring-cloud-contract-stub-runner</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner-boot</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-shade</artifactId>

View File

@@ -0,0 +1 @@
/.apt_generated/

View File

@@ -0,0 +1,645 @@
=== Stub Runner Core
Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of
http://martinfowler.com/articles/consumerDrivenContracts.html[Consumer Driven Contracts].
Stub Runner allows you to automatically download the stubs of the provided dependencies (or pick those from the classpath), start WireMock servers for them and feed them with proper stub definitions.
For messaging, special stub routes are defined.
==== Retrieving stubs
You can pick the following options of acquiring stubs
- Aether based solution that downloads JARs with stubs from Artifactory / Nexus
- Classpath scanning solution that searches classpath via pattern to retrieve stubs
- Write your own implementation of the `org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder` for full customization
The latter example is described in the <<custom_stub_runner, Custom Stub Runner>> section.
===== Stub downloading
If you provide the `stubrunner.repositoryRoot` or `stubrunner.workOffline` flag will be set
to `true` then Stub Runner will connect to the given server and download the required jars.
It will then unpack the JAR to a temporary folder and reference those files in further
contract processing.
Example:
[source,java]
----
@AutoConfigureStubRunner(repositoryRoot="http://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095")
----
===== Classpath scanning
If you *DON'T* provide the `stubrunner.repositoryRoot` and `stubrunner.workOffline` flag will
be set to `false` (that's the default) then classpath will get scanned. Let's look at the
following example:
[source,java]
----
@AutoConfigureStubRunner(ids = {
"com.example:beer-api-producer:+:stubs:8095",
"com.example.foo:bar:1.0.0:superstubs:8096"
})
----
If you've added the dependencies to your classpath
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependency>
<groupId>com.example</groupId>
<artifactId>beer-api-producer-restdocs</artifactId>
<classifier>stubs</classifier>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.example.foo</groupId>
<artifactId>bar</artifactId>
<classifier>superstubs</classifier>
<version>1.0.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
----
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
testCompile("com.example:beer-api-producer-restdocs:0.0.1-SNAPSHOT:stubs") {
transitive = false
}
testCompile("com.example.foo:bar:1.0.0:superstubs") {
transitive = false
}
----
Then the following locations on your classpath will get scanned. For `com.example:beer-api-producer-restdocs`
- /META-INF/com.example/beer-api-producer-restdocs/**/*.*
- /contracts/com.example/beer-api-producer-restdocs/**/*.*
- /mappings/com.example/beer-api-producer-restdocs/**/*.*
and `com.example.foo:bar`
- /META-INF/com.example.foo/bar/**/*.*
- /contracts/com.example.foo/bar/**/*.*
- /mappings/com.example.foo/bar/**/*.*
TIP: As you can see you have to explicitly provide the group and artifact ids when packaging the
producer stubs.
The producer would setup the contracts like this:
[source,bash]
----
└── src
└── test
└── resources
└── contracts
   └── com.example
      └── beer-api-producer-restdocs
      └── nested
      └── contract3.groovy
----
To achieve proper stub packaging.
Or using the https://github.com/spring-cloud-samples/spring-cloud-contract-samples/blob/master/producer_with_restdocs/pom.xml[Maven `assembly` plugin] or
https://github.com/spring-cloud-samples/spring-cloud-contract-samples/blob/master/producer_with_restdocs/build.gradle[Gradle Jar] task you have to create the following
structure in your stubs jar.
[source,bash]
----
└── META-INF
└── com.example
└── beer-api-producer-restdocs
└── 2.0.0
├── contracts
│   └── nested
   │ └── contract2.groovy
   └── mappings
   └── mapping.json
----
By maintaining this structure classpath gets scanned and you can profit from the messaging /
HTTP stubs without the need to download artifacts.
==== Running stubs
===== Limitations
IMPORTANT: There might be a problem with StubRunner shutting down ports between tests. You might
have a situation in which you get port conflicts. As long as you use the same context across tests
everything works fine. But when the context are different (e.g. different stubs or different profiles)
then you have to either use `@DirtiesContext` to shut down the stub servers, or else run them on
different ports per test.
===== Running using main app
You can set the following options to the main class:
[source,groovy,indent=0]
----
-c, --classifier Suffix for the jar containing stubs (e.
g. 'stubs' if the stub jar would
have a 'stubs' classifier for stubs:
foobar-stubs ). Defaults to 'stubs'
(default: stubs)
--maxPort, --maxp <Integer> Maximum port value to be assigned to
the WireMock instance. Defaults to
15000 (default: 15000)
--minPort, --minp <Integer> Minimum port value to be assigned to
the WireMock instance. Defaults to
10000 (default: 10000)
-p, --password Password to user when connecting to
repository
--phost, --proxyHost Proxy host to use for repository
requests
--pport, --proxyPort [Integer] Proxy port to use for repository
requests
-r, --root Location of a Jar containing server
where you keep your stubs (e.g. http:
//nexus.
net/content/repositories/repository)
-s, --stubs Comma separated list of Ivy
representation of jars with stubs.
Eg. groupid:artifactid1,groupid2:
artifactid2:classifier
-u, --username Username to user when connecting to
repository
--wo, --workOffline Switch to work offline. Defaults to
'false'
----
===== HTTP Stubs
Stubs are defined in JSON documents, whose syntax is defined in http://wiremock.org/stubbing.html[WireMock documentation]
Example:
[source,javascript,indent=0]
----
{
"request": {
"method": "GET",
"url": "/ping"
},
"response": {
"status": 200,
"body": "pong",
"headers": {
"Content-Type": "text/plain"
}
}
}
----
===== Viewing registered mappings
Every stubbed collaborator exposes list of defined mappings under `__/admin/` endpoint.
You can also use the `mappingsOutputFolder` property to dump the mappings to files.
For annotation based approach it would look like this
[source,java]
----
@AutoConfigureStubRunner(ids="a.b.c:loanIssuance,a.b.c:fraudDetectionServer",
mappingsOutputFolder = "target/outputmappings/")
----
and for the JUnit approach like this:
[source,java]
----
@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule()
.repoRoot("http://some_url")
.downloadStub("a.b.c", "loanIssuance")
.downloadStub("a.b.c:fraudDetectionServer")
.withMappingsOutputFolder("target/outputmappings")
----
Then if you check out the folder `target/outputmappings` you would see the following structure
[source,bash]
----
.
├── fraudDetectionServer_13705
└── loanIssuance_12255
----
That means that there were two stubs registered. `fraudDetectionServer` was registered at port `13705`
and `loanIssuance` at port `12255`. If we take a look at one of the files we would see (for WireMock)
mappings available for the given server:
[source,json]
----
[{
"id" : "f9152eb9-bf77-4c38-8289-90be7d10d0d7",
"request" : {
"url" : "/name",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "fraudDetectionServer"
},
"uuid" : "f9152eb9-bf77-4c38-8289-90be7d10d0d7"
},
...
]
----
===== Messaging Stubs
Depending on the provided Stub Runner dependency and the DSL the messaging routes are automatically set up.
=== Stub Runner JUnit Rule
Stub Runner comes with a JUnit rule thanks to which you can very easily download and run stubs for given group and artifact id:
[source,java,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java[tags=classrule]
----
After that rule gets executed Stub Runner connects to your Maven repository and for the given list of dependencies tries to:
- download them
- cache them locally
- unzip them to a temporary folder
- start a WireMock server for each Maven dependency on a random port from the provided range of ports / provided port
- feed the WireMock server with all JSON files that are valid WireMock definitions
- can also send messages (remember to pass an implementation of `MessageVerifier` interface)
Stub Runner uses https://wiki.eclipse.org/Aether[Eclipse Aether] mechanism to download the Maven dependencies.
Check their https://wiki.eclipse.org/Aether[docs] for more information.
Since the `StubRunnerRule` implements the `StubFinder` it allows you to find the started stubs:
[source,groovy,indent=0]
----
include::src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java[lines=16..-1]
----
Example of usage in Spock tests:
[source,groovy,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleSpec.groovy[tags=classrule]
----
Example of usage in JUnit tests:
[source,java,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java[tags=test]
----
Check the *Common properties for JUnit and Spring* for more information on how to apply global configuration of Stub Runner.
IMPORTANT: To use the JUnit rule together with messaging you have to provide an implementation of the
`MessageVerifier` interface to the rule builder (e.g. `rule.messageVerifier(new MyMessageVerifier())`).
If you don't do this then whenever you try to send a message an exception will be thrown.
==== Maven settings
The stub downloader honors Maven settings for a different local repository folder.
Authentication details for repositories and profiles are currently not taken into account, so you need to specify it using the properties mentioned above.
==== Providing fixed ports
You can also run your stubs on fixed ports. You can do it in two different ways. One is to pass it in the properties, and the other via fluent API of
JUnit rule.
==== Fluent API
When using the `StubRunnerRule` you can add a stub to download and then pass the port for the last downloaded stub.
[source,java,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleCustomPortJUnitTest.java[tags=classrule_with_port]
----
You can see that for this example the following test is valid:
[source,java,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleCustomPortJUnitTest.java[tags=test_with_port]
----
==== Stub Runner with Spring
Sets up Spring configuration of the Stub Runner project.
By providing a list of stubs inside your configuration file the Stub Runner automatically downloads
and registers in WireMock the selected stubs.
If you want to find the URL of your stubbed dependency you can autowire the `StubFinder` interface and use
its methods as presented below:
[source,groovy,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=test]
----
for the following configuration file:
[source,yml,indent=0]
----
include::src/test/resources/application-test.yml[tags=test]
----
Instead of using the properties you can also use the properties inside the `@AutoConfigureStubRunner`.
Below you can find an example of achieving the same result by setting values on the annotation.
[source,groovy,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy[tags=autoconfigure]
----
Stub Runner Spring registers environment variables in the following manner
for every registered WireMock server. Example for Stub Runner ids
`com.example:foo`, `com.example:bar`.
- `stubrunner.runningstubs.foo.port`
- `stubrunner.runningstubs.bar.port`
Which you can reference in your code.
=== Stub Runner Spring Cloud
Stub Runner can integrate with Spring Cloud.
For real life examples you can check the
- https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/master/producer[producer app sample]
- https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/master/consumer_with_discovery[consumer app sample]
==== Stubbing Service Discovery
The most important feature of `Stub Runner Spring Cloud` is the fact that it's stubbing
- `DiscoveryClient`
- `Ribbon` `ServerList`
that means that regardless of the fact whether you're using Zookeeper, Consul, Eureka or anything else, you don't need that in your tests.
We're starting WireMock instances of your dependencies and we're telling your application whenever you're using `Feign`, load balanced `RestTemplate`
or `DiscoveryClient` directly, to call those stubbed servers instead of calling the real Service Discovery tool.
For example this test will pass
[source,groovy,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy[tags=test]
----
for the following configuration file
[source,yml,indent=0]
----
include::src/test/resources/application.yml[tags=ids]
----
===== Test profiles and service discovery
In your integration tests you typically don't want to call neither a discovery service (e.g. Eureka)
or Config Server. That's why you create an additional test configuration in which you want to disable
these features.
Due to certain limitations of https://github.com/spring-cloud/spring-cloud-commons/issues/156[`spring-cloud-commons`] to achieve this you have disable these properties
via a static block like presented below (example for Eureka)
[source,java]
----
//Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156
static {
System.setProperty("eureka.client.enabled", "false");
System.setProperty("spring.cloud.config.failFast", "false");
}
----
==== Additional Configuration
You can match the artifactId of the stub with the name of your app by using the `stubrunner.idsToServiceIds:` map.
You can disable Stub Runner Ribbon support by providing: `stubrunner.cloud.ribbon.enabled` equal to `false`
You can disable Stub Runner support by providing: `stubrunner.cloud.enabled` equal to `false`
TIP: By default all service discovery will be stubbed. That means that regardless of the fact if you have
an existing `DiscoveryClient` its results will be ignored. However, if you want to reuse it, just set
`stubrunner.cloud.delegate.enabled` to `true` and then your existing `DiscoveryClient` results will be
merged with the stubbed ones.
=== Stub Runner Boot Application
Spring Cloud Contract Stub Runner Boot is a Spring Boot application that exposes REST endpoints to
trigger the messaging labels and to access started WireMock servers.
One of the use-cases is to run some smoke (end to end) tests on a deployed application.
You can check out the https://github.com/spring-cloud/spring-cloud-pipelines[Spring Cloud Pipelines]
project for more information.
==== How to use it?
===== Stub Runner Server
Just add the
[source,groovy,indent=0]
----
compile "org.springframework.cloud:spring-cloud-starter-stub-runner"
----
Annotate a class with `@EnableStubRunnerServer`, build a fat-jar and you're ready to go!
For the properties check the *Stub Runner Spring* section.
===== Spring Cloud CLI
Starting from `1.4.0.RELEASE` version of the http://cloud.spring.io/spring-cloud-cli[Spring Cloud CLI]
project you can start Stub Runner Boot by executing `spring cloud stubrunner`.
In order to pass the configuration just create a `stubrunner.yml` file in the current working directory
or a subdirectory called `config` or in `~/.spring-cloud`. The file could look like this
(example for running stubs installed locally)
.stubrunner.yml
[source,yml,indent=0]
----
stubrunner:
workOffline: true
ids:
- com.example:beer-api-producer:+:9876
----
and then just call `spring cloud stubrunner` from your terminal window to start
the Stub Runner server. It will be available at port `8750`.
==== Endpoints
===== HTTP
- GET `/stubs` - returns a list of all running stubs in `ivy:integer` notation
- GET `/stubs/{ivy}` - returns a port for the given `ivy` notation (when calling the endpoint `ivy` can also be `artifactId` only)
===== Messaging
For Messaging
- GET `/triggers` - returns a list of all running labels in `ivy : [ label1, label2 ...]` notation
- POST `/triggers/{label}` - executes a trigger with `label`
- POST `/triggers/{ivy}/{label}` - executes a trigger with `label` for the given `ivy` notation (when calling the endpoint `ivy` can also be `artifactId` only)
==== Example
[source,groovy,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy[tags=boot_usage]
----
==== Stub Runner Boot with Service Discovery
One of the possibilities of using Stub Runner Boot is to use it as a feed of stubs for "smoke-tests". What does it mean?
Let's assume that you don't want to deploy 50 microservice to a test environment in order
to check if your application is working fine. You've already executed a suite of tests during the build process
but you would also like to ensure that the packaging of your application is fine. What you can do
is to deploy your application to an environment, start it and run a couple of tests on it to see if
it's working fine. We can call those tests smoke-tests since their idea is to check only a handful
of testing scenarios.
The problem with this approach is such that if you're doing microservices most likely you're
using a service discovery tool. Stub Runner Boot allows you to solve this issue by starting the
required stubs and register them in a service discovery tool. Let's take a look at an example of
such a setup with Eureka. Let's assume that Eureka was already running.
[source,java,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka]
----
As you can see we want to start a Stub Runner Boot server `@EnableStubRunnerServer`, enable Eureka client `@EnableEurekaClient`
and we want to have the stub runner feature turned on `@AutoConfigureStubRunner`.
Now let's assume that we want to start this application so that the stubs get automatically registered.
We can do it by running the app `java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar` where
`${SYSTEM_PROPS}` would contain the following list of properties
[source,bash,indent=0]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka_args]
----
That way your deployed application can send requests to started WireMock servers via the service
discovery. Most likely points 1-3 could be set by default in `application.yml` cause they are not
likely to change. That way you can provide only the list of stubs to download whenever you start
the Stub Runner Boot.
=== Stubs Per Consumer
There are cases in which 2 consumers of the same endpoint want to have 2 different responses.
TIP: This approach also allows you to immediately know which consumer is using which part of your API.
You can remove part of a response that your API produces and you can see which of your autogenerated tests
fails. If none fails then you can safely delete that part of the response cause nobody is using it.
Let's look at the following example for contract defined for the producer called `producer`.
There are 2 consumers: `foo-consumer` and `bar-consumer`.
*Consumer `foo-service`*
[source,groovy]
----
request {
url '/foo'
method GET()
}
response {
status 200
body(
foo: "foo"
}
}
----
*Consumer `bar-service`*
[source,groovy]
----
request {
url '/foo'
method GET()
}
response {
status 200
body(
bar: "bar"
}
}
----
You can't produce for the same request 2 different responses. That's why you can properly package the
contracts and then profit from the `stubsPerConsumer` feature.
On the producer side the consumers can have a folder that contains contracts related only to them.
By setting the `stubrunner.stubs-per-consumer` flag to `true` we no longer register all stubs but only those that
correspond to the consumer application's name. In other words we'll scan the path of every stub and
if it contains the subfolder with name of the consumer in the path only then will it get registered.
On the `foo` producer side the contracts would look like this
[source,bash]
----
.
└── contracts
├── bar-consumer
│   ├── bookReturnedForBar.groovy
│   └── shouldCallBar.groovy
└── foo-consumer
├── bookReturnedForFoo.groovy
└── shouldCallFoo.groovy
----
Being the `bar-consumer` consumer you can either set the `spring.application.name` or the `stubrunner.consumer-name` to `bar-consumer`
Or set the test as follows:
[source,groovy]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy[tags=test]
...
}
----
Then only the stubs registered under a path that contains the `bar-consumer` in its name (i.e. those from the
`src/test/resources/contracts/bar-consumer/some/contracts/...` folder) will be allowed to be referenced.
Or set the consumer name explicitly
[source,groovy]
----
include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy[tags=test]
...
}
----
Then only the stubs registered under a path that contains the `foo-consumer` in its name (i.e. those from the
`src/test/resources/contracts/foo-consumer/some/contracts/...` folder) will be allowed to be referenced.
You can check out https://github.com/spring-cloud/spring-cloud-contract/issues/224[issue 224] for more
information about the reasons behind this change.

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-parent</artifactId>
<version>1.2.3.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-stub-runner-boot</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner Boot</name>
<description>Spring Cloud Contract Stub Runner Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2017 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.stubrunner.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Marcin Grzejszczak
*/
@SpringBootApplication
@EnableStubRunnerServer
public class StubRunnerBoot {
public static void main(String[] args) {
SpringApplication.run(StubRunnerBoot.class, args);
}
}