Docker Project

在本节中,我们发布 springcloud/spring-cloud-contract Docker 镜像,其中包含一个项目,该项目生成测试,并在 EXPLICIT 模式下针对正在运行的应用程序运行这些测试。

`EXPLICIT`模式意味着从合同生成的测试发送真实的请求,而不是模拟的请求。

我们还发布 spring-cloud/spring-cloud-contract-stub-runner Docker 镜像,它启动 Stub Runner 的独立版本。

A Short Introduction to Maven, JARs, and Binary Storage

由于非 JVM 项目可以使用 Docker 镜像,因此最好解释 Spring Cloud Contract 默认打包方式背后的基本概念。

下列部分的定义取自 Maven Glossary

  • @{12}:Maven 以项目进行思考。项目是您构建的一切。这些项目遵循明确的 @{10} 定义。项目可以依赖于其他项目——在这种情况下,后者被称为 @{11}。一个项目可能由多个子项目组成。但是,这些子项目仍然被平等地视为项目。

  • @{13}:构件是由项目生成或使用的内容。Maven 为项目生成的构件示例包括 JAR 文件和源和二进制分发包。每个构件都由一个组 ID 和一个在组内唯一的构件 ID 唯一标识。

  • @{14}:JAR 代表 Java ARchive(Java 归档)。其格式基于 ZIP 文件格式。Spring Cloud Contract 使用 JAR 文件打包契约和生成的存根。

  • @{15}:组 ID 是项目的通用唯一标识符。尽管这通常只是项目名称(例如 @{16}),但使用完全限定的包名称来将其与具有相似名称的其他项目区分开来很有帮助(例如 @{17})。通常,当发布到构件管理器时,@{18} 会被斜杠分隔并构成 URL 的一部分。例如,对于组 ID @{19} 和构件 ID @{20},结果将为 @{21}。

  • @{22}:Maven 依赖项表示形式如下:@{23}。分类符是一个额外的后缀,传递给依赖项——例如 @{24} 或 @{25}。相同的依赖项(例如 @{26})可以生成多个彼此之间通过分类符进行区分的构件。

  • @{27}:当您生成二进制文件、源文件或包时,您希望其他人可以下载、引用或重复使用它们。在 JVM 世界中,那些构件通常是 JAR 文件。对于 Ruby,那些构件是 gem 文件。对于 Docker,那些构件是 Docker 映像。您可以将那些构件存储在管理器中。此类管理器的示例包括 @{28} 和 @{29}。

Generating Tests on the Producer Side

镜像在 /contracts 文件夹下搜索合约。测试运行结果可在 /spring-cloud-contract/build 文件夹中找到(这对调试很有用)。

您可以挂载合约并传递环境变量。然后镜像会执行以下操作:

  • Generates the contract tests

  • 针对提供的 URL 运行测试

  • Generates the WireMock stubs

  • 将存根发布到构件管理器(可选——默认启用)

Environment Variables

Docker 镜像要求一些环境变量指向您正在运行的应用程序、构件管理器实例等。以下列表描述了环境变量:

Table 1. Docker environment variables

Name

Description

Default

ADDITIONAL_FLAGS

(仅 Docker 映像)传递给 Gradle 构建的其他标志

DEBUG

(仅限 Docker 映像)适用于 Docker 映像 - 为 Gradle 构建开启调试模式

false

EXTERNAL_CONTRACTS_ARTIFACT_ID

带有合同的项目工件 ID

EXTERNAL_CONTRACTS_CLASSIFIER

带有合同的项目分类器

EXTERNAL_CONTRACTS_GROUP_ID

带有合同的项目组 ID

com.example

EXTERNAL_CONTRACTS_PATH

给定项目中合同的路径,在带有合同的项目内部。默认为用斜杠分隔 EXTERNAL_CONTRACTS_GROUP_ID 拼接 /EXTERNAL_CONTRACTS_ARTIFACT_ID. For example, for group id `cat-server-side.dog 和工件 ID fish,结果是合同路径的 cat/dog/fish

EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_PASSWORD

(可选项)如果 EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL 需要身份验证,则提供密码。它默认设置为 REPO_WITH_BINARIES_PASSWORD,如果没有设置,则默认设置为 `password

EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL

工件管理器的 URL。它默认设置为 REPO_WITH_BINARIES_URL 环境变量的值,如果没有设置,则默认设置为 http://localhost:8081/artifactory/libs-release-local

EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_USERNAME

(可选项)如果 EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL 需要身份验证,则提供用户名。它默认设置为 REPO_WITH_BINARIES_USERNAME。如果没有设置,则默认设置为 admin

EXTERNAL_CONTRACTS_VERSION

带有合同的项目的版本。默认情况下等效于选择最新版本

+

EXTERNAL_CONTRACTS_WORK_OFFLINE

如果设置为 true,则从容器的 .m2 中检索带有合同的工件。将本地 .m2 作为卷装载在容器的 /root/.m2 路径中

false

FAIL_ON_NO_CONTRACTS

如果不存在合同,该生成应失败吗?

false

MESSAGING_TYPE

消息传递类型。可以是 [rabbit] 或 [kafka]。

PRODUCER_STUBS_CLASSIFIER

用于生成的生产者存根的存档分类器

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

如果设置为 true,则将工件发布到二进制存储中

true

PUBLISH_ARTIFACTS_OFFLINE

如果设置为 true,则将人工制品发布到本地 m2

false

PUBLISH_STUBS_TO_SCM

如果设置为 true,则将运行任务以将存根发布到 scm

false

REPO_ALLOW_INSECURE_PROTOCOL

(可选)如果为 <true>,则允许通过不安全的 HTTP 将人工制品发布到 Artifact Manager

false

REPO_WITH_BINARIES_PASSWORD

(可选)当 Artifact Manager 受保护时的密码

password

REPO_WITH_BINARIES_URL

本地区工管理器的 URL(在本地运行时默认使用 Artifactory 的默认 URL)

[role="bare"]http://localhost:8081/artifactory/libs-release-local

REPO_WITH_BINARIES_USERNAME

(可选)当 Artifact Manager 受保护时的用户名

admin

STANDALONE_PROTOCOL

对于独立版本,应添加哪种其他协议

在运行测试时使用以下环境变量:

本文概述了用于连接到应用程序以触发消息的配置设置。它定义了必需的配置参数,如连接和读取超时以及消息传递类型,并提供了每种配置的示例值。这些设置允许应用程序集成到消息传递系统中,以实现事件驱动的通信。

Table 2. Docker environment variables - read at runtime

Name

Description

Default

APPLICATION_BASE_URL

应用程序正在运行的 URL。

APPLICATION_PASSWORD

访问应用程序的可选密码。

APPLICATION_USERNAME

访问应用程序的可选用户名。

MESSAGING_TRIGGER_CONNECT_TIMEOUT

连接到触发消息的应用程序的超时时间。

5000

MESSAGING_TRIGGER_READ_TIMEOUT

读取触发消息的应用程序响应的超时时间。

5000

MESSAGING_TYPE

处理基于消息的合约时的消息类型定义。

MESSAGING_TYPE

消息传递类型。可以是 [rabbit] 或 [kafka]。

SPRING_KAFKA_BOOTSTRAP_SERVERS

对于 Kafka - 经纪地址。

SPRING_RABBITMQ_ADDRESSES

对于 RabbitMQ - 经纪地址。

Customizing the gradle build

通过在运行容器时将自定义构建文件挂载为卷,您可以提供一个定制的 gradle.build 在容器中运行:

$ 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 到结果目录:

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

合同在 /contracts 文件夹中。

由于我们要运行测试,因此,我们可以运行以下命令:

$ npm test

但是,出于学习目的,我们将它分成如下部分:

# 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 脚本,将发生以下情况:

  • 基础设施(MongoDb 和 Artifactory)已经设置好。在真实的场景中,你可以使用模拟数据库运行 NodeJS 应用程序。在这个示例中,我们想要展示如何在很短的时间内从 Spring Cloud Contract 中受益。

  • 由于这些限制,合同也代表有状态的情况。

    • 第一个请求是 POST,它会导致数据插入到数据库中。

    • 第二个请求是 GET,它返回了一个包含 1 个先前插入元素的数据列表。

  • NodeJS 应用程序已启动(在端口 3000 上)。

  • 合同测试是通过 Docker 生成的,并且测试针对正在运行的应用程序运行。

    • 合同取自 /contracts 文件夹。

    • 测试的输出可在 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。

Example of Usage via Messaging

如果您想通过Docker 镜像 (例如,对于混合语言应用程序) 将 Spring Cloud Contract 与消息传递一起使用,那么您必须满足以下先决条件:

  • 在生成测试之前,中间件(例如 RabbitMQ 或 Kafka)必须正在运行

  • 你的合同需要使用参数 triggerMessage(&#8230;&#8203;) 调用方法 String,该参数等于合同的 label

  • 你的应用程序需要有一个 HTTP 终结点,我们可以通过它触发消息

    • 该端点在产品中不可用(可以通过环境变量启用)

Example of a Messaging Contract

合同需要调用 triggerMessage(…​) 方法。对于 Docker 镜像中所有测试的基本类中已提供了该方法,它会向生产者的 HTTP 端点发送请求。在下面您可以找到此类合同的示例。

  • 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 端点。

端点必须具有以下配置:

  • URL:/springcloudcontract/{label},其中 label 可以是任意文本

  • Method: POST

  • 根据 @{13} 将生成一条消息,该消息将根据合同定义发送到给定的目的地

ниже представлен пример такой конечной точки. Если вы заинтересованы в предоставлении примера на своем языке, не стесняйтесь создать проблему в Spring Cloud Contract repository at Github.

Python
#!/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 存储库中。

#!/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

将发生以下情况:

  • 测试将从从 Git 中获取的合同生成

  • 在合同中,我们在称为 declareQueueWithName 的元数据中提供了一个条目,该条目将导致在 RabbitMQ 中创建名称为 before 的队列,并将发送触发该消息的请求

  • 通过 triggerMessage("ping_pong") 方法调用,将对 Python 应用程序的 /springcloudcontract/ping_pong 终结点发出 POST 请求

  • Python 应用程序将通过 RabbitMQ 生成并发送一个 '{"message":"pong"}' 的 JSON,到一个称为 output 的交换

  • 生成测试将轮询发送到 output 交换的消息

  • 收到消息后,将断言其内容

在测试通过后,我们知道消息是从 Python 应用正确发送到 RabbitMQ。

Running Stubs on the Consumer Side

本节介绍如何在使用者侧使用 Docker 来获取和运行存根。

我们发布了一个 spring-cloud/spring-cloud-contract-stub-runner Docker 镜像,它启动独立版本的 Stub Runner。

Security

Поскольку Docker-образ Spring Cloud Contract Stub Runner использует автономную версию Stub Runner, необходимо учитывать те же соображения безопасности. Дополнительные сведения об этом вы можете прочитать в in this section of the documentation.

Environment Variables

Вы можете запустить Docker-образ и передать любой из common properties for JUnit and Springв виде переменных окружения. В соответствии с соглашением все буквы должны быть заглавными. Точка (.) должна быть заменена символами подчеркивания (_). Например, свойство stubrunner.repositoryRoot должно быть представлено в виде переменной окружения STUBRUNNER_REPOSITORY_ROOT.

除了那些变量外,您还可以设置以下变量:

  • MESSAGING_TYPE - 您正在使用什么类型的消息系统(目前支持 rabbitkafka

  • ADDITIONAL_OPTS - 任何您希望传递给应用程序的其他属性

Example of Usage

我们需要使用 [docker-server-side] 步骤中创建的存根。假设我们要在端口 9876 上运行存根。你可以克隆存储库并更改为以下命令中指示的目录,看到 NodeJS 代码:

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

现在,我们可以通过运行以下命令,使用存根运行 Stub Runner 启动应用程序:

# 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}"

在运行上述命令时,

  • 独立的 Stub Runner 应用程序正在启动。

  • 它使用坐标 com.example:bookstore:0.0.1.RELEASE:stubs 将存根下载到端口 9876

  • 它从在 http://192.168.0.100:8081/artifactory/libs-release-local 运行的 Artifactory 下载。

  • 一段时间后,Stub Runner 将在端口 8083 运行。

  • 存根在端口 9876 运行。

我们在服务器端构建了一个有状态存根。我们可以使用 curl 断言存根已经正确设置。为此,请运行以下命令:

# 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")。

Example of Usage with Messaging

要使消息传递正常工作,只需使用 kafkarabbit 值传递 MESSAGING_TYPE 环境变量即可。这会设置 Stub Runner Boot Docker 映像,其中包含连接到代理所需的依赖项。

要设置连接属性,你可以查看 Spring Cloud Stream 属性页面以设置正确的环境变量。

你将设置的最常见的属性是运行中心件的位置。如果设置属性的属性称为 spring.rabbitmq.addressesspring.kafka.bootstrap-servers,则分别应将环境变量命名为 SPRING_RABBITMQ_ADDRESSESSPRING_KAFKA_BOOTSTRAP_SERVERS

Running Contract Tests against Existing Middleware

针对现有中心件运行你的契约测试是合情合理的。一些测试框架可能会给你错误的肯定结果——在你的构建中通过测试,而在生产中通信失败。

在 Spring Cloud Contract Docker 映像中,我们提供了一个连接到现有中间件的选项。如前面对比部分所述,我们默认支持 Kafka 和 RabbitMQ。但是,我们可以通过 Apache Camel Components 来支持其他中间件。我们来看一下以下几个用例。

Spring Cloud Contract Docker and running Middleware

要连接到任意中心件,我们将在契约部分中利用 standalone 元数据项。

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 在前面的消息示例中,我们需要触发正在运行的应用程序中的 HTTP 终结点,以使它根据提供的协议发送一条消息
3 protocol:destination 根据 Apache Camel 的请求
4 Output message body
5 Standalone metadata entry
6 设置部分将包含有关如何在对正在运行的应用程序的 HTTP 终结点进行实际调用之前准备运行契约测试的信息
7 要在设置阶段调用的 Apache Camel URI。在这种情况下,我们将尝试在 output 交换处轮询一条消息,并由于具有 queue=outputroutingKey=,将设置一个名为 output 的队列并将其绑定到具有路由密钥 output 交换。
8 要附加到 protocol:destination 的附加选项(更具技术性)来自点 (3) - 将以以下格式 rabbitmq:output?routingKey=#&amp;queue=output 组合在一起。

对于契约测试的通过,在我们通常在多语言环境中传递消息、运行应用程序和运行中心件的情况下,我们需要它。这一次,我们将为 Spring Cloud Contract Docker 映像设置不同的环境变量。

#!/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 应用程序需要启动并运行
3 通过 STANDALONE_PROTOCOL 环境变量,我们将获取 Apache Camel Component。我们将获取的制品是 org.apache.camel.springboot:camel-${STANDALONE_PROTOCOL}-starter。换句话说,STANDALONE_PROTOCOL 与 Camel 组件匹配。
4 我们通过 Camel 的 Spring Boot 启动程序机制设置地址(我们可能正在设置证书)。 Apache Camel’s RabbitMQ Spring Boot Auto-Configuration 的示例

Stub Runner Docker and running Middleware

要针对正在运行的中心件触发存根消息,我们可以以下面的方式运行 Stub Runner Docker 映像。

用法示例

$ 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 我们告诉 Stub Runner 下载哪些 Stub
3 我们为 Stub 提供外部位置(Git 存储库)
4 我们通过 ADDITIONAL_OPTS=--thin.properties.dependencies.XXX=GROUP:ARTIFACT:VERSION 属性告诉 Stub Runner 在运行时获取哪个附加依赖项。在这种情况下,我们想要获取 camel-rabbitmq-starter,因此 XXX 是一个随机字符串,我们希望获取版本为 3.4.0org.apache.camel.springboot:camel-rabbitmq-starter 制品。
5 由于我们正在使用 Git,需要设置抓取 Stub 的远程选项
6 为了加快 Stub Runner 的启动速度,我们将其本地 Maven 存储库 .m2 作为卷附加。如果您没有填充,则可以考虑通过 :rw 设置写入权限而不是只读 :ro
7 我们公开 Stub Runner 正在运行的端口 8750
8 Stub Runner Docker 镜像的坐标。

过了一会儿,你将在控制台中看到以下文本,这意味着 Stub Runner 已经准备好接受请求。

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 请求。在控制台中,你将看到:

o.s.c.c.v.m.camel.CamelStubMessages      : Will send a message to URI [rabbitmq:output?routingKey=#&queue=output]

如果你查看 RabbitMQ 管理控制台,你将看到 output 队列中有 1 条消息可用。