Docker Project
在本节中,我们发布 springcloud/spring-cloud-contract
Docker 镜像,其中包含一个项目,该项目生成测试,并在 EXPLICIT
模式下针对正在运行的应用程序运行这些测试。
In this section, we publish a springcloud/spring-cloud-contract
Docker image
that contains a project that generates tests and runs them in EXPLICIT
mode
against a running application.
`EXPLICIT`模式意味着从合同生成的测试发送真实的请求,而不是模拟的请求。 |
The |
我们还发布 spring-cloud/spring-cloud-contract-stub-runner
Docker 镜像,它启动 Stub Runner 的独立版本。
We also publish a spring-cloud/spring-cloud-contract-stub-runner
Docker image
that starts the standalone version of Stub Runner.
A Short Introduction to Maven, JARs, and Binary Storage
由于非 JVM 项目可以使用 Docker 镜像,因此最好解释 Spring Cloud Contract 默认打包方式背后的基本概念。
Since non-JVM projects can use the Docker image, it is good to explain the basic terms behind Spring Cloud Contract packaging defaults.
下列部分的定义取自 Maven Glossary:
Parts of the following definitions were taken from the Maven Glossary:
-
@{12}:Maven 以项目进行思考。项目是您构建的一切。这些项目遵循明确的 @{10} 定义。项目可以依赖于其他项目——在这种情况下,后者被称为 @{11}。一个项目可能由多个子项目组成。但是,这些子项目仍然被平等地视为项目。
-
Project
: Maven thinks in terms of projects. Projects are all you build. Those projects follow a well defined “Project Object Model”. Projects can depend on other projects — in that case, the latter are called “dependencies”. A project may consistent of several subprojects. However, these subprojects are still treated equally as projects. -
@{13}:构件是由项目生成或使用的内容。Maven 为项目生成的构件示例包括 JAR 文件和源和二进制分发包。每个构件都由一个组 ID 和一个在组内唯一的构件 ID 唯一标识。
-
Artifact
: An artifact is something that is either produced or used by a project. Examples of artifacts produced by Maven for a project include JAR files and source and binary distributions. Each artifact is uniquely identified by a group ID and an artifact ID that is unique within a group. -
@{14}:JAR 代表 Java ARchive(Java 归档)。其格式基于 ZIP 文件格式。Spring Cloud Contract 使用 JAR 文件打包契约和生成的存根。
-
JAR
: JAR stands for Java ARchive. Its format is based on the ZIP file format. Spring Cloud Contract packages the contracts and generated stubs in a JAR file. -
@{15}:组 ID 是项目的通用唯一标识符。尽管这通常只是项目名称(例如 @{16}),但使用完全限定的包名称来将其与具有相似名称的其他项目区分开来很有帮助(例如 @{17})。通常,当发布到构件管理器时,@{18} 会被斜杠分隔并构成 URL 的一部分。例如,对于组 ID @{19} 和构件 ID @{20},结果将为 @{21}。
-
GroupId
: A group ID is a universally unique identifier for a project. While this is often just the project name (for example,commons-collections
), it is helpful to use a fully-qualified package name to distinguish it from other projects with a similar name (for example,org.apache.maven
). Typically, when published to the Artifact Manager, theGroupId
gets slash separated and forms part of the URL. For example, for a group ID ofcom.example
and an artifact ID ofapplication
, the result would be/com/example/application/
. -
@{22}:Maven 依赖项表示形式如下:@{23}。分类符是一个额外的后缀,传递给依赖项——例如 @{24} 或 @{25}。相同的依赖项(例如 @{26})可以生成多个彼此之间通过分类符进行区分的构件。
-
Classifier
: The Maven dependency notation looks as follows:groupId:artifactId:version:classifier
. The classifier is an additional suffix passed to the dependency — for example,stubs
orsources
. The same dependency (for example,com.example:application
) can produce multiple artifacts that differ from each other with the classifier. -
@{27}:当您生成二进制文件、源文件或包时,您希望其他人可以下载、引用或重复使用它们。在 JVM 世界中,那些构件通常是 JAR 文件。对于 Ruby,那些构件是 gem 文件。对于 Docker,那些构件是 Docker 映像。您可以将那些构件存储在管理器中。此类管理器的示例包括 @{28} 和 @{29}。
-
Artifact manager
: When you generate binaries, sources, or packages, you would like them to be available for others to download, reference, or reuse. In the case of the JVM world, those artifacts are generally JARs. For Ruby, those artifacts are gems. For Docker, those artifacts are Docker images. You can store those artifacts in a manager. Examples of such managers include Artifactory and Nexus.
Generating Tests on the Producer Side
镜像在 /contracts
文件夹下搜索合约。测试运行结果可在 /spring-cloud-contract/build
文件夹中找到(这对调试很有用)。
The image searches for contracts under the /contracts
folder.
The output from running the tests is available in the
/spring-cloud-contract/build
folder (useful for debugging
purposes).
您可以挂载合约并传递环境变量。然后镜像会执行以下操作:
You can mount your contracts and pass the environment variables. The image then:
-
Generates the contract tests
-
针对提供的 URL 运行测试
-
Runs the tests against the provided URL
-
Generates the WireMock stubs
-
将存根发布到构件管理器(可选——默认启用)
-
Publishes the stubs to a Artifact Manager (optional — turned on by default)
Environment Variables
Docker 镜像要求一些环境变量指向您正在运行的应用程序、构件管理器实例等。以下列表描述了环境变量:
The Docker image requires some environment variables to point to your running application, to the Artifact manager instance, and so on. The following list describes the environment variables:
Name |
Description |
Default |
ADDITIONAL_FLAGS |
(Docker Image only) Additional flags to be passed to the Gradle build |
|
DEBUG |
(Docker Image only) Applicable for Docker Image - turns on debug mode for the Gradle build |
false |
EXTERNAL_CONTRACTS_ARTIFACT_ID |
Artifact ID of the project with contracts |
|
EXTERNAL_CONTRACTS_CLASSIFIER |
Classifier of the project with contracts |
|
EXTERNAL_CONTRACTS_GROUP_ID |
Group ID of the project with contracts |
com.example |
EXTERNAL_CONTRACTS_PATH |
Path to contracts for the given project, inside the project with contracts. Defaults to slash-separated |
|
EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_PASSWORD |
(optional) Password if the |
|
EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL |
URL of your Artifact Manager. It defaults to the value of |
|
EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_USERNAME |
(optional) Username if the |
|
EXTERNAL_CONTRACTS_VERSION |
Version of the project with contracts. Defautls to an equivalent of picking the latest |
+ |
EXTERNAL_CONTRACTS_WORK_OFFLINE |
If set to |
false |
FAIL_ON_NO_CONTRACTS |
Should the build fail if there are no contracts present? |
false |
MESSAGING_TYPE |
Type of messaging. Can be either [rabbit] or [kafka]. |
|
PRODUCER_STUBS_CLASSIFIER |
Archive classifier used for generated producer stubs |
stubs |
PROJECT_GROUP |
Your project’s group ID |
com.example |
PROJECT_NAME |
Your project’s artifact id |
example |
PROJECT_VERSION |
Your project’s version |
0.0.1-SNAPSHOT |
PUBLISH_ARTIFACTS |
If set to |
true |
PUBLISH_ARTIFACTS_OFFLINE |
If set to |
false |
PUBLISH_STUBS_TO_SCM |
If set to |
false |
REPO_ALLOW_INSECURE_PROTOCOL |
(optional) If <true> allows to publish artifacts to Artifact Manager over insecure HTTP |
false |
REPO_WITH_BINARIES_PASSWORD |
(optional) Password when the Artifact Manager is secured |
password |
REPO_WITH_BINARIES_URL |
URL of your Artifact Manager (defaults to the default URL of Artifactory when running locally) |
[role="bare"]http://localhost:8081/artifactory/libs-release-local |
REPO_WITH_BINARIES_USERNAME |
(optional) Username when the Artifact Manager is secured |
admin |
STANDALONE_PROTOCOL |
For standalone version, which additional protocol should be added |
在运行测试时使用以下环境变量:
The following environment variables are used when tests are run:
本文概述了用于连接到应用程序以触发消息的配置设置。它定义了必需的配置参数,如连接和读取超时以及消息传递类型,并提供了每种配置的示例值。这些设置允许应用程序集成到消息传递系统中,以实现事件驱动的通信。
Name |
Description |
Default |
APPLICATION_BASE_URL |
URL at which the application is running. |
|
APPLICATION_PASSWORD |
Optional password to access the application. |
|
APPLICATION_USERNAME |
Optional username to access the application. |
|
MESSAGING_TRIGGER_CONNECT_TIMEOUT |
Timeout to connect to the application to trigger a message. |
5000 |
MESSAGING_TRIGGER_READ_TIMEOUT |
Timeout to read the response from the application to trigger a message. |
5000 |
MESSAGING_TYPE |
Defines the messaging type when dealing with message based contracts. |
|
MESSAGING_TYPE |
Type of messaging. Can be either [rabbit] or [kafka]. |
|
SPRING_KAFKA_BOOTSTRAP_SERVERS |
For Kafka - brokers addresses. |
|
SPRING_RABBITMQ_ADDRESSES |
For RabbitMQ - brokers addresses. |
Customizing the gradle build
通过在运行容器时将自定义构建文件挂载为卷,您可以提供一个定制的 gradle.build
在容器中运行:
You can provide a customized gradle.build
to be run in the container by mounting your customized build file as a volume when running the container:
$ docker run -v <absolute-path-of-your-custom-file>:/spring-cloud-contract/build.gradle springcloud/spring-cloud-contract:<version>
Example of Usage via HTTP
本节中,我们将探讨一个简单的 MVC 应用。为了开始,请克隆以下 git 存储库,并通过运行以下命令 cd 到结果目录:
In this section, we explore a simple MVC application. To get started, clone the following git repository and cd to the resulting directory, by running the following commands:
$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
合同在 /contracts
文件夹中。
The contracts are available in the /contracts
folder.
由于我们要运行测试,因此,我们可以运行以下命令:
Since we want to run tests, we can run the following command:
$ npm test
但是,出于学习目的,我们将它分成如下部分:
However, for learning purposes, we split it into pieces, as follows:
# 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"
# Run 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/" springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
# Kill app
$ pkill -f "node app"
通过 bash 脚本,将发生以下情况:
Through bash scripts, the following happens:
-
基础设施(MongoDb 和 Artifactory)已经设置好。在真实的场景中,你可以使用模拟数据库运行 NodeJS 应用程序。在这个示例中,我们想要展示如何在很短的时间内从 Spring Cloud Contract 中受益。
-
The infrastructure (MongoDb and Artifactory) is set up. In a real-life scenario, you would run the NodeJS application with a mocked database. In this example, we want to show how we can benefit from Spring Cloud Contract in very little time.
-
由于这些限制,合同也代表有状态的情况。
-
第一个请求是
POST
,它会导致数据插入到数据库中。 -
The first request is a
POST
that causes data to get inserted into the database. -
第二个请求是
GET
,它返回了一个包含 1 个先前插入元素的数据列表。 -
The second request is a
GET
that returns a list of data with 1 previously inserted element.
-
-
Due to those constraints, the contracts also represent the stateful situation.
-
第一个请求是
POST
,它会导致数据插入到数据库中。 -
The first request is a
POST
that causes data to get inserted into the database. -
第二个请求是
GET
,它返回了一个包含 1 个先前插入元素的数据列表。 -
The second request is a
GET
that returns a list of data with 1 previously inserted element.
-
-
NodeJS 应用程序已启动(在端口
3000
上)。 -
The NodeJS application is started (on port
3000
). -
合同测试是通过 Docker 生成的,并且测试针对正在运行的应用程序运行。
-
合同取自
/contracts
文件夹。 -
The contracts are taken from
/contracts
folder. -
测试的输出可在
node_modules/spring-cloud-contract/output
下获取。 -
The output of the test is available under
node_modules/spring-cloud-contract/output
.
-
-
The contract tests are generated through Docker, and tests are run against the running application.
-
合同取自
/contracts
文件夹。 -
The contracts are taken from
/contracts
folder. -
测试的输出可在
node_modules/spring-cloud-contract/output
下获取。 -
The output of the test is available under
node_modules/spring-cloud-contract/output
.
-
-
存根已上传至 Artifactory。你可以在 [role="bare"][role="bare"]http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/ 中找到它们。存根位于 [role="bare"][role="bare"]http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/bookstore-0.0.1.RELEASE-stubs.jar。
-
The stubs are uploaded to Artifactory. You can find them in [role="bare"]http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/. The stubs are at [role="bare"]http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/bookstore-0.0.1.RELEASE-stubs.jar.
Example of Usage via Messaging
如果您想通过Docker 镜像 (例如,对于混合语言应用程序) 将 Spring Cloud Contract 与消息传递一起使用,那么您必须满足以下先决条件:
If you want to use Spring Cloud Contract with messaging via the Docker images (e.g. in case of polyglot applications) then you’ll have to have the following prerequisites met:
-
在生成测试之前,中间件(例如 RabbitMQ 或 Kafka)必须正在运行
-
Middleware (e.g. RabbitMQ or Kafka) must be running before generating tests
-
你的合同需要使用参数
triggerMessage(…​)
调用方法String
,该参数等于合同的label
。 -
Your contract needs to call a method
triggerMessage(…)
with aString
parameter that is equal to the contract’slabel
. -
你的应用程序需要有一个 HTTP 终结点,我们可以通过它触发消息
-
该端点在产品中不可用(可以通过环境变量启用)
-
That endpoint should not be available on production (could be enabled via an environment variable)
-
-
Your application needs to have a HTTP endpoint via which we can trigger a message
-
该端点在产品中不可用(可以通过环境变量启用)
-
That endpoint should not be available on production (could be enabled via an environment variable)
-
Example of a Messaging Contract
合同需要调用 triggerMessage(…)
方法。对于 Docker 镜像中所有测试的基本类中已提供了该方法,它会向生产者的 HTTP 端点发送请求。在下面您可以找到此类合同的示例。
The contract needs to call a triggerMessage(…)
method. That method is already provided in the base class for all tests in the docker image and will send out a request to the HTTP endpoint on the producer side. Below you can find examples of such contracts.
-
Groovy
-
YAML
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description 'Send a pong message in response to a ping message'
label 'ping_pong'
input {
// You have to provide the `triggerMessage` method with the `label`
// as a String parameter of the method
triggeredBy('triggerMessage("ping_pong")')
}
outputMessage {
sentTo('output')
body([
message: 'pong'
])
}
metadata(
[amqp:
[
outputMessage: [
connectToBroker: [
declareQueueWithName: "queue"
],
messageProperties: [
receivedRoutingKey: '#'
]
]
]
])
}
description: 'Send a pong message in response to a ping message'
label: 'ping_pong'
input:
# You have to provide the `triggerMessage` method with the `label`
# as a String parameter of the method
triggeredBy: 'triggerMessage("ping_pong")'
outputMessage:
sentTo: 'output'
body:
message: 'pong'
metadata:
amqp:
outputMessage:
connectToBroker:
declareQueueWithName: "queue"
messageProperties:
receivedRoutingKey: '#'
HTTP Endpoint to Trigger a Message
为什么需要开发这样的端点?Spring Cloud Contract 需要生成各种语言的代码 (正如它在 Java 中所做的那样) 以便能够触发生产代码,该代码会向代理发送消息。如果未生成此类代码,那么无论如何,我们都需要能够触发消息,而实现它的方法是提供一个用户将使用他们选择的语言准备的 HTTP 端点。
Why is there need to develop such an endpoint? Spring Cloud Contract would have to generate code in various languages (as it does in Java) to make it possible to trigger production code that sends a message to a broker. If such code is not generated then we need to be able to trigger the message anyways, and the way to do it is to provide an HTTP endpoint that the user will prepare in the language of their choosing.
端点必须具有以下配置:
The endpoint must have the following configuration:
-
URL:
/springcloudcontract/{label}
,其中label
可以是任意文本 -
URL:
/springcloudcontract/{label}
wherelabel
can be any text -
Method:
POST
-
根据 @{13} 将生成一条消息,该消息将根据合同定义发送到给定的目的地
-
Basing on the
label
will generate a message that will be sent to a given destination according to the contract definition
ниже представлен пример такой конечной точки. Если вы заинтересованы в предоставлении примера на своем языке, не стесняйтесь создать проблему в Spring Cloud Contract repository at Github.
Below you have an example of such an endpoint. If you’re interested in providing an example in your language don’t hesitate to file an issue in the Spring Cloud Contract repository at Github.
#!/usr/bin/env python
from flask import Flask
from flask import jsonify
import pika
import os
app = Flask(__name__)
# Production code that sends a message to RabbitMQ
def send_message(cmd):
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.basic_publish(
exchange='output',
routing_key='#',
body=cmd,
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
))
connection.close()
return " [x] Sent via Rabbit: %s" % cmd
# This should be ran in tests (shouldn't be publicly available)
if 'CONTRACT_TEST' in os.environ:
@app.route('/springcloudcontract/<label>', methods=['POST'])
def springcloudcontract(label):
if label == "ping_pong":
return send_message('{"message":"pong"}')
else:
raise ValueError('No such label expected.')
Running Message Tests on the Producer Side
现在,让我们根据合同生成测试来测试生产者端。我们将运行 bash 代码以启动带有附加合同的 Docker 镜像,但我们还将添加变量以使消息传递代码工作。在这种情况,让我们假设这些合同存储在 Git 存储库中。
Now, let’s generate tests from contracts to test the producer side. We will run bash code to start the Docker image with attached contracts, however we will also add variables for the messaging code to work. In this case let’s assume that the contracts are being stored in a Git repository.
#!/bin/bash
set -x
CURRENT_DIR="$( pwd )"
export SC_CONTRACT_DOCKER_VERSION="${SC_CONTRACT_DOCKER_VERSION:-4.0.1-SNAPSHOT}"
export APP_IP="$( ./whats_my_ip.sh )"
export APP_PORT="${APP_PORT:-8000}"
export APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
export PROJECT_GROUP="${PROJECT_GROUP:-group}"
export PROJECT_NAME="${PROJECT_NAME:-application}"
export PROJECT_VERSION="${PROJECT_VERSION:-0.0.1-SNAPSHOT}"
export PRODUCER_STUBS_CLASSIFIER="${PRODUCER_STUBS_CLASSIFIER:-stubs}"
export FAIL_ON_NO_CONTRACTS="${FAIL_ON_NO_CONTRACTS:-false}"
# In our Python app we want to enable the HTTP endpoint
export CONTRACT_TEST="true"
# In the Verifier docker container we want to add support for RabbitMQ
export MESSAGING_TYPE="rabbit"
# Let's start the infrastructure (e.g. via Docker Compose)
yes | docker-compose kill || echo "Nothing running"
docker-compose up -d
echo "SC Contract Version [${SC_CONTRACT_DOCKER_VERSION}]"
echo "Application URL [${APPLICATION_BASE_URL}]"
echo "Project Version [${PROJECT_VERSION}]"
# Let's run python app
gunicorn -w 4 --bind 0.0.0.0 main:app &
APP_PID=$!
# Generate and run tests
docker run --rm \
--name verifier \
# For the image to find the RabbitMQ running in another container
-e "SPRING_RABBITMQ_ADDRESSES=${APP_IP}:5672" \
# We need to tell the container what messaging middleware we will use
-e "MESSAGING_TYPE=${MESSAGING_TYPE}" \
-e "PUBLISH_STUBS_TO_SCM=false" \
-e "PUBLISH_ARTIFACTS=false" \
-e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" \
-e "PROJECT_NAME=${PROJECT_NAME}" \
-e "PROJECT_GROUP=${PROJECT_GROUP}" \
-e "PROJECT_VERSION=${PROJECT_VERSION}" \
-e "EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \
-e "EXTERNAL_CONTRACTS_ARTIFACT_ID=${PROJECT_NAME}" \
-e "EXTERNAL_CONTRACTS_GROUP_ID=${PROJECT_GROUP}" \
-e "EXTERNAL_CONTRACTS_VERSION=${PROJECT_VERSION}" \
-v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
kill $APP_PID
yes | docker-compose kill
将发生以下情况:
What will happen is:
-
测试将从从 Git 中获取的合同生成
-
Tests will be generated from contracts taken from Git
-
在合同中,我们在称为
declareQueueWithName
的元数据中提供了一个条目,该条目将导致在 RabbitMQ 中创建名称为 before 的队列,并将发送触发该消息的请求 -
In the contract we’ve provided an entry in metadata called
declareQueueWithName
that will lead to creation of a queue in RabbitMQ with the given name before the request to trigger the message is sent -
通过
triggerMessage("ping_pong")
方法调用,将对 Python 应用程序的/springcloudcontract/ping_pong
终结点发出 POST 请求 -
Via the
triggerMessage("ping_pong")
method call a POST request to the Python application to the/springcloudcontract/ping_pong
endpoint will be made -
Python 应用程序将通过 RabbitMQ 生成并发送一个 '{"message":"pong"}' 的 JSON,到一个称为
output
的交换 -
The Python application will generate and send a ’{"message":"pong"}'` JSON via RabbitMQ to an exchange called
output
-
生成测试将轮询发送到
output
交换的消息 -
The generated test will poll for a message sent to the
output
exchange -
收到消息后,将断言其内容
-
Once the message was received will assert its contents
在测试通过后,我们知道消息是从 Python 应用正确发送到 RabbitMQ。
After the tests have passed we know that the message was properly sent from the Python app to RabbitMQ.
Running Stubs on the Consumer Side
本节介绍如何在使用者侧使用 Docker 来获取和运行存根。
This section describes how to use Docker on the consumer side to fetch and run stubs.
我们发布了一个 spring-cloud/spring-cloud-contract-stub-runner
Docker 镜像,它启动独立版本的 Stub Runner。
We publish a spring-cloud/spring-cloud-contract-stub-runner
Docker image
that starts the standalone version of Stub Runner.
Security
Поскольку Docker-образ Spring Cloud Contract Stub Runner использует автономную версию Stub Runner, необходимо учитывать те же соображения безопасности. Дополнительные сведения об этом вы можете прочитать в in this section of the documentation.
Since the Spring Cloud Contract Stub Runner Docker Image uses the standalone version of Stub Runner the same security considerations need to be taken. You can read more about those in this section of the documentation.
Environment Variables
Вы можете запустить Docker-образ и передать любой из common properties for JUnit and Springв виде переменных окружения. В соответствии с соглашением все буквы должны быть заглавными. Точка (.
) должна быть заменена символами подчеркивания (_
). Например, свойство stubrunner.repositoryRoot
должно быть представлено в виде переменной окружения STUBRUNNER_REPOSITORY_ROOT
.
You can run the docker image and pass any of the common properties for JUnit and Spring
as environment variables. The convention is that all the
letters should be upper case.
The dot (.
) should be replaced with underscore (_
) characters. For example,
the stubrunner.repositoryRoot
property should be represented
as a STUBRUNNER_REPOSITORY_ROOT
environment variable.
除了那些变量外,您还可以设置以下变量:
In addition to those variables you can set the following ones:
-
MESSAGING_TYPE
- 您正在使用什么类型的消息系统(目前支持rabbit
、kafka
) -
MESSAGING_TYPE
- what type of messaging system are you using (currently supported arerabbit
,kafka
) -
ADDITIONAL_OPTS
- 任何您希望传递给应用程序的其他属性 -
ADDITIONAL_OPTS
- any additional properties that you would like to pass to the application
Example of Usage
我们需要使用 [docker-server-side] 步骤中创建的存根。假设我们要在端口 9876
上运行存根。你可以克隆存储库并更改为以下命令中指示的目录,看到 NodeJS 代码:
We want to use the stubs created in this [docker-server-side] step.
Assume that we want to run the stubs on port 9876
. You can see the NodeJS code
by cloning the repository and changing to the directory indicated in the following commands:
$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
现在,我们可以通过运行以下命令,使用存根运行 Stub Runner 启动应用程序:
Now we can run the Stub Runner Boot application with the stubs, by running the following commands:
# 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}" \
-e "STUBRUNNER_STUBS_MODE=REMOTE" \
-p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" \
-p "9876:9876" \
springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}"
在运行上述命令时,
When the preceding commands run,
-
独立的 Stub Runner 应用程序正在启动。
-
A standalone Stub Runner application gets started.
-
它使用坐标
com.example:bookstore:0.0.1.RELEASE:stubs
将存根下载到端口9876
。 -
It downloads the stub with coordinates
com.example:bookstore:0.0.1.RELEASE:stubs
on port9876
. -
它从在
http://192.168.0.100:8081/artifactory/libs-release-local
运行的 Artifactory 下载。 -
It gets downloads from Artifactory running at
http://192.168.0.100:8081/artifactory/libs-release-local
. -
一段时间后,Stub Runner 将在端口
8083
运行。 -
After a while, Stub Runner is running on port
8083
. -
存根在端口
9876
运行。 -
The stubs are running at port
9876
.
我们在服务器端构建了一个有状态存根。我们可以使用 curl 断言存根已经正确设置。为此,请运行以下命令:
On the server side, we built a stateful stub. We can use curl to assert that the stubs are setup properly. To do so, run the following commands:
# let's run 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
如果您要在主机上的本地环境中使用您已经构建的存根,则应设置`-e STUBRUNNER_STUBS_MODE=LOCAL`环境变量并挂载您本地 m2 的卷(-v "${HOME}/.m2/:/home/scc/.m2:ro"
)。
If you want use the stubs that you have built locally, on your host,
you should set the -e STUBRUNNER_STUBS_MODE=LOCAL
environment variable and mount
the volume of your local m2 (-v "${HOME}/.m2/:/home/scc/.m2:ro"
).
Example of Usage with Messaging
要使消息传递正常工作,只需使用 kafka
或 rabbit
值传递 MESSAGING_TYPE
环境变量即可。这会设置 Stub Runner Boot Docker 映像,其中包含连接到代理所需的依赖项。
In order to make messaging work it’s enough to pass the MESSAGING_TYPE
environment variable with kafka
or rabbit
values. This will lead to setting up
the Stub Runner Boot Docker image with dependencies required to connect to the broker.
要设置连接属性,你可以查看 Spring Cloud Stream 属性页面以设置正确的环境变量。
In order to set the connection properties you can check out Spring Cloud Stream properties page to set proper environment variables.
-
Spring Boot Integration properties
-
您可以搜索
spring.rabbitmq.xxx
或spring.kafka.xxx
属性 -
You can search for
spring.rabbitmq.xxx
orspring.kafka.xxx
properties
-
你将设置的最常见的属性是运行中心件的位置。如果设置属性的属性称为 spring.rabbitmq.addresses
或 spring.kafka.bootstrap-servers
,则分别应将环境变量命名为 SPRING_RABBITMQ_ADDRESSES
和 SPRING_KAFKA_BOOTSTRAP_SERVERS
。
The most common property you would set is the location of the running middlewara.
If a property to set it is called spring.rabbitmq.addresses
or spring.kafka.bootstrap-servers
then you should name the environment variable SPRING_RABBITMQ_ADDRESSES
and SPRING_KAFKA_BOOTSTRAP_SERVERS
respectively.
Running Contract Tests against Existing Middleware
针对现有中心件运行你的契约测试是合情合理的。一些测试框架可能会给你错误的肯定结果——在你的构建中通过测试,而在生产中通信失败。
There is legitimate reason to run your contract tests against existing middleware. Some testing frameworks might give you false positive results - the test within your build passes whereas on production the communication fails.
在 Spring Cloud Contract Docker 映像中,我们提供了一个连接到现有中间件的选项。如前面对比部分所述,我们默认支持 Kafka 和 RabbitMQ。但是,我们可以通过 Apache Camel Components 来支持其他中间件。我们来看一下以下几个用例。
In Spring Cloud Contract docker images we give an option to connect to existing middleware. As presented in previous subsections we do support Kafka and RabbitMQ out of the box. However, via Apache Camel Components we can support other middleware too. Let’s take a look at the following examples of usage.
Spring Cloud Contract Docker and running Middleware
要连接到任意中心件,我们将在契约部分中利用 standalone
元数据项。
In order to connect to arbitrary middleware, we’ll leverage the standalone
metadata entry
in the contract section.
description: 'Send a pong message in response to a ping message'
label: 'standalone_ping_pong' 1
input:
triggeredBy: 'triggerMessage("ping_pong")' 2
outputMessage:
sentTo: 'rabbitmq:output' 3
body: 4
message: 'pong'
metadata:
standalone: 5
setup: 6
options: rabbitmq:output?queue=output&routingKey=(7)
outputMessage: 8
additionalOptions: routingKey=#&queue=output 9
1 | 我们能够通过 Stub Runner 触发消息的标签 |
2 | Label by which we’ll be able to trigger the message via Stub Runner |
3 | 在前面的消息示例中,我们需要触发正在运行的应用程序中的 HTTP 终结点,以使它根据提供的协议发送一条消息 |
4 | As in the previous messaging examples we’ll need to trigger the HTTP endpoint in the running application to make it send a message according to the provided protocol |
5 | protocol:destination 根据 Apache Camel 的请求 |
6 | protocol:destination as requested by Apache Camel |
7 | Output message body |
8 | Standalone metadata entry |
9 | 设置部分将包含有关如何在对正在运行的应用程序的 HTTP 终结点进行实际调用之前准备运行契约测试的信息 |
10 | Setup part will contain information about how to prepare for running contract tests before the actual call to HTTP endpoint of the running application is made |
11 | 要在设置阶段调用的 Apache Camel URI。在这种情况下,我们将尝试在 output 交换处轮询一条消息,并由于具有 queue=output 和 routingKey= ,将设置一个名为 output 的队列并将其绑定到具有路由密钥 的 output 交换。 |
12 | Apache Camel URI to be called in the setup phase. In this case we will try to poll for a message at the output exchange and due to to having the queue=output and routingKey= a queue with name output will be set and bound to the output exchange with routing key
|
13 | 要附加到 protocol:destination 的附加选项(更具技术性)来自点 (3) - 将以以下格式 rabbitmq:output?routingKey=#&queue=output 组合在一起。 |
14 | Additional options (more technical ones) to be appended to the protocol:destination from point (3) - together will be combined in the following format rabbitmq:output?routingKey=#&queue=output . |
对于契约测试的通过,在我们通常在多语言环境中传递消息、运行应用程序和运行中心件的情况下,我们需要它。这一次,我们将为 Spring Cloud Contract Docker 映像设置不同的环境变量。
For the contract tests to pass we will need as usual in case of messaging in polyglot environment a running application and running middleware. This time we will have different environment variables set for the Spring Cloud Contract Docker image.
#!/bin/bash
set -x
# Setup
# Run the middleware
docker-compose up -d rabbitmq 1
# Run the python application
gunicorn -w 4 --bind 0.0.0.0 main:app & 2
APP_PID=$!
docker run --rm \
--name verifier \
-e "STANDALONE_PROTOCOL=rabbitmq" \ 3
-e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ 4
-e "PUBLISH_STUBS_TO_SCM=false" \
-e "PUBLISH_ARTIFACTS=false" \
-e "APPLICATION_BASE_URL=172.18.0.1" \
-e "PROJECT_NAME=application" \
-e "PROJECT_GROUP=group" \
-e "EXTERNAL_CONTRACTS_ARTIFACT_ID=application" \
-e "EXTERNAL_CONTRACTS_GROUP_ID=group" \
-e "EXTERNAL_CONTRACTS_VERSION=0.0.1-SNAPSHOT" \
-v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
# Teardown
kill $APP_PID
yes | docker-compose kill
1 | 我们需要先运行中间件 |
2 | We need to have the middleware running first |
3 | 应用程序需要启动并运行 |
4 | The application needs to be up and running |
5 | 通过 STANDALONE_PROTOCOL 环境变量,我们将获取 Apache Camel Component。我们将获取的制品是 org.apache.camel.springboot:camel-${STANDALONE_PROTOCOL}-starter 。换句话说,STANDALONE_PROTOCOL 与 Camel 组件匹配。 |
6 | Via the STANDALONE_PROTOCOL environment variable we will fetch a Apache Camel Component. The artifact that we will fetch is org.apache.camel.springboot:camel-${STANDALONE_PROTOCOL}-starter . In other words STANDALONE_PROTOCOL is matching Camel’s component. |
7 | 我们通过 Camel 的 Spring Boot 启动程序机制设置地址(我们可能正在设置证书)。 Apache Camel’s RabbitMQ Spring Boot Auto-Configuration 的示例 |
8 | We’re setting addresses (we could be setting credentials) via Camel’s Spring Boot Starter mechanisms. Example for Apache Camel’s RabbitMQ Spring Boot Auto-Configuration |
Stub Runner Docker and running Middleware
要针对正在运行的中心件触发存根消息,我们可以以下面的方式运行 Stub Runner Docker 映像。
In order to trigger a stub message against running middleware, we can run Stub Runner Docker image in the following manner.
用法示例
Example of usage
$ docker run \
-e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ 1
-e "STUBRUNNER_IDS=group:application:0.0.1-SNAPSHOT" \ 2
-e "STUBRUNNER_REPOSITORY_ROOT=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \ 3
-e ADDITIONAL_OPTS="--thin.properties.dependencies.rabbitmq=org.apache.camel.springboot:camel-rabbitmq-starter:3.4.0" \ 4
-e "STUBRUNNER_STUBS_MODE=REMOTE" \ 5
-v "${HOME}/.m2/:/home/scc/.m2:ro" \ 6
-p 8750:8750 \ 7
springcloud/spring-cloud-contract-stub-runner:3.0.4-SNAPSHOT 8
1 | 我们通过 Apache Camel’s Spring Boot Auto-Configuration 注入 RabbitMQ 的地址 |
2 | We’re injecting the address of RabbitMQ via Apache Camel’s Spring Boot Auto-Configuration |
3 | 我们告诉 Stub Runner 下载哪些 Stub |
4 | We’re telling Stub Runner which stubs to download |
5 | 我们为 Stub 提供外部位置(Git 存储库) |
6 | We’re providing an external location for our stubs (Git repository) |
7 | 我们通过 ADDITIONAL_OPTS=--thin.properties.dependencies.XXX=GROUP:ARTIFACT:VERSION 属性告诉 Stub Runner 在运行时获取哪个附加依赖项。在这种情况下,我们想要获取 camel-rabbitmq-starter ,因此 XXX 是一个随机字符串,我们希望获取版本为 3.4.0 的 org.apache.camel.springboot:camel-rabbitmq-starter 制品。 |
8 | Via the ADDITIONAL_OPTS=--thin.properties.dependencies.XXX=GROUP:ARTIFACT:VERSION property we’re telling Stub Runner which additional dependency to fetch at runtime. In this case we want to fetch camel-rabbitmq-starter so XXX is a random string and we want to fetch org.apache.camel.springboot:camel-rabbitmq-starter artifact in version 3.4.0 . |
9 | 由于我们正在使用 Git,需要设置抓取 Stub 的远程选项 |
10 | Since we’re using Git, the remote option of fetching stubs needs to be set |
11 | 为了加快 Stub Runner 的启动速度,我们将其本地 Maven 存储库 .m2 作为卷附加。如果您没有填充,则可以考虑通过 :rw 设置写入权限而不是只读 :ro 。 |
12 | So that we speed up launching of Stub Runner, we’re attaching our local Maven repository .m2 as a volume. If you don’t have it populated you can consider setting the write permissions via :rw instead read only :ro . |
13 | 我们公开 Stub Runner 正在运行的端口 8750 。 |
14 | We expose the port 8750 at which Stub Runner is running. |
15 | Stub Runner Docker 镜像的坐标。 |
16 | Coordinates of the Stub Runner Docker image. |
过了一会儿,你将在控制台中看到以下文本,这意味着 Stub Runner 已经准备好接受请求。
After a while you’ll notice the following text in your console, which means that Stub Runner is ready to accept requests.
o.a.c.impl.engine.AbstractCamelContext : Apache Camel 3.4.3 (camel-1) started in 0.007 seconds
o.s.c.c.s.server.StubRunnerBoot : Started StubRunnerBoot in 14.483 seconds (JVM running for 18.666)
o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
要获取触发器列表,你可以向 localhost:8750/triggers
端点发送 HTTP GET 请求。要触发存根消息,你可以向 localhost:8750/triggers/standalone_ping_pong
发送 HTTP POST 请求。在控制台中,你将看到:
To get the list of triggers you can send an HTTP GET request to localhost:8750/triggers
endpoint. To trigger a stub message, you can send a HTTP POST request to localhost:8750/triggers/standalone_ping_pong
. In the console you’ll see:
o.s.c.c.v.m.camel.CamelStubMessages : Will send a message to URI [rabbitmq:output?routingKey=#&queue=output]
如果你查看 RabbitMQ 管理控制台,你将看到 output
队列中有 1 条消息可用。
If you check the RabbitMQ management console, you’ll see that there’s 1 message available in the output
queue.