Skip to content

Commit 2184390

Browse files
Phil EndsleyPhil Endsley
Phil Endsley
authored and
Phil Endsley
committed
Step 11 - Publish pacts and verification results to Pact Broker
1 parent 2955ca5 commit 2184390

File tree

5 files changed

+294
-1
lines changed

5 files changed

+294
-1
lines changed

README.md

+194
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,197 @@ We can now run the Provider tests
961961

962962
BUILD SUCCESSFUL in 1s
963963
```
964+
[Broker collaboration Workflow](diagrams/workshop_step10_broker.svg)
965+
966+
We've been publishing our pacts from the consumer project by essentially sharing the file system with the provider. But this is not very manageable when you have multiple teams contributing to the code base, and pushing to CI. We can use a [Pact Broker](https://pactflow.io) to do this instead.
967+
968+
Using a broker simplifies the management of pacts and adds a number of useful features, including some safety enhancements for continuous delivery which we'll see shortly.
969+
970+
In this workshop we will be using the open source Pact broker.
971+
972+
### Running the Pact Broker with docker-compose
973+
974+
In the root directory, run:
975+
976+
```console
977+
docker-compose up
978+
```
979+
980+
### Publish contracts from consumer
981+
982+
First, in the consumer project we need to tell Pact about our broker.
983+
984+
In `consumer/build.gradle`:
985+
986+
```groovy
987+
...
988+
...
989+
990+
def getGitHash = { ->
991+
def stdout = new ByteArrayOutputStream()
992+
exec {
993+
commandLine 'git', 'rev-parse', '--short', 'HEAD'
994+
standardOutput = stdout
995+
}
996+
return stdout.toString().trim()
997+
}
998+
999+
def getGitBranch = { ->
1000+
def stdout = new ByteArrayOutputStream()
1001+
exec {
1002+
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
1003+
standardOutput = stdout
1004+
}
1005+
return stdout.toString().trim()
1006+
}
1007+
1008+
static def getOrDefault(env, defaultVal) {
1009+
def val = System.getenv(env)
1010+
if (val == null || val.isEmpty()) {
1011+
val = defaultVal
1012+
}
1013+
return val
1014+
}
1015+
1016+
pact {
1017+
publish {
1018+
pactDirectory = 'consumer/build/pacts'
1019+
pactBrokerUrl = 'http://localhost:8000/'
1020+
pactBrokerUsername = getOrDefault('PACT_BROKER_USERNAME', 'pact_workshop')
1021+
pactBrokerPassword = getOrDefault('PACT_BROKER_PASSWORD', 'pact_workshop')
1022+
tags = [getGitBranch(), 'test', 'prod']
1023+
consumerVersion = getGitHash()
1024+
}
1025+
}
1026+
```
1027+
1028+
Now run
1029+
1030+
```console
1031+
./gradlew consumer:test --tests *PactTest* pactPublish
1032+
1033+
> Task :consumer:pactPublish
1034+
Publishing 'FrontendApplication-ProductService.json' with tags step11, test, prod ... OK
1035+
1036+
BUILD SUCCESSFUL in 11s
1037+
1038+
```
1039+
*NOTE*: For real projects, you should only publish pacts from CI builds
1040+
1041+
Have a browse around the broker on http://localhost:8000 (with username/password: `pact_workshop`/`pact_workshop`) and see your newly published contract!
1042+
1043+
### Verify contracts on Provider
1044+
1045+
All we need to do for the provider is update where it finds its pacts, from local URLs, to one from a broker.
1046+
1047+
In `provider/src/test/java/au/com/dius/pactworkshop/provider/ProductPactProviderTest.java`:
1048+
1049+
```java
1050+
//replace
1051+
@PactFolder("pacts")
1052+
1053+
// with
1054+
@PactBroker(
1055+
host = "localhost",
1056+
port = "8000",
1057+
authentication = @PactBrokerAuth(username = "pact_workshop", password = "pact_workshop")
1058+
)
1059+
```
1060+
In `provider/build.gradle`:
1061+
1062+
```groovy
1063+
...
1064+
...
1065+
1066+
1067+
def getGitHash = { ->
1068+
def stdout = new ByteArrayOutputStream()
1069+
exec {
1070+
commandLine 'git', 'rev-parse', '--short', 'HEAD'
1071+
standardOutput = stdout
1072+
}
1073+
return stdout.toString().trim()
1074+
}
1075+
1076+
def getGitBranch = { ->
1077+
def stdout = new ByteArrayOutputStream()
1078+
exec {
1079+
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
1080+
standardOutput = stdout
1081+
}
1082+
return stdout.toString().trim()
1083+
}
1084+
1085+
test {
1086+
useJUnitPlatform()
1087+
1088+
if (System.getProperty('pactPublishResults') == 'true') {
1089+
systemProperty 'pact.provider.version', getGitHash()
1090+
systemProperty 'pact.provider.tag', getGitBranch()
1091+
systemProperty 'pact.verifier.publishResults', 'true'
1092+
}
1093+
}
1094+
```
1095+
1096+
Let's run the provider verification one last time after this change:
1097+
1098+
```console
1099+
./gradlew -DpactPublishResults=true provider:test --tests *Pact*Test
1100+
1101+
BUILD SUCCESSFUL in 16s
1102+
```
1103+
*NOTE*: For real projects, you should only publish verification results from CI builds
1104+
1105+
As part of this process, the results of the verification - the outcome (boolean) and the detailed information about the failures at the interaction level - are published to the Broker also.
1106+
1107+
This is one of the Broker's more powerful features. Referred to as [Verifications](https://docs.pact.io/pact_broker/advanced_topics/provider_verification_results), it allows providers to report back the status of a verification to the broker. You'll get a quick view of the status of each consumer and provider on a nice dashboard. But, it is much more important than this!
1108+
1109+
### Can I deploy?
1110+
1111+
With just a simple use of the `pact-broker` [can-i-deploy tool](https://docs.pact.io/pact_broker/advanced_topics/provider_verification_results) - the Broker will determine if a consumer or provider is safe to release to the specified environment.
1112+
1113+
You can run the `pact-broker can-i-deploy` checks as follows:
1114+
1115+
```console
1116+
docker run --rm --network host \
1117+
-e PACT_BROKER_BASE_URL=http://localhost:8000 \
1118+
-e PACT_BROKER_USERNAME=pact_workshop \
1119+
-e PACT_BROKER_PASSWORD=pact_workshop \
1120+
pactfoundation/pact-cli:latest \
1121+
broker can-i-deploy \
1122+
--pacticipant FrontendApplication \
1123+
--latest
1124+
1125+
1126+
Computer says yes \o/
1127+
1128+
CONSUMER | C.VERSION | PROVIDER | P.VERSION | SUCCESS?
1129+
--------------------|-----------|----------------|-----------|---------
1130+
FrontendApplication | 2955ca5 | ProductService | 2955ca5 | true
1131+
1132+
All required verification results are published and successful
1133+
1134+
1135+
----------------------------
1136+
1137+
docker run --rm --network host \
1138+
-e PACT_BROKER_BASE_URL=http://localhost:8000 \
1139+
-e PACT_BROKER_USERNAME=pact_workshop \
1140+
-e PACT_BROKER_PASSWORD=pact_workshop \
1141+
pactfoundation/pact-cli:latest \
1142+
broker can-i-deploy \
1143+
--pacticipant ProductService \
1144+
--latest
1145+
1146+
Computer says yes \o/
1147+
1148+
CONSUMER | C.VERSION | PROVIDER | P.VERSION | SUCCESS?
1149+
--------------------|-----------|----------------|-----------|---------
1150+
FrontendApplication | 2955ca5 | ProductService | 2955ca5 | true
1151+
1152+
All required verification results are published and successful
1153+
```
1154+
1155+
1156+
1157+
That's it - you're now a Pact pro. Go build 🔨

consumer/build.gradle

+41
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1+
buildscript {
2+
repositories {
3+
mavenCentral()
4+
}
5+
dependencies {
6+
classpath 'au.com.dius.pact.provider:gradle:4.1.7'
7+
}
8+
}
9+
110
plugins {
211
id 'org.springframework.boot' version '2.3.4.RELEASE'
312
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
13+
id 'au.com.dius.pact' version '4.1.6'
414
}
515

616
group = 'au.com.dius.pactworkshop'
717
version = '0.0.1-SNAPSHOT'
818
sourceCompatibility = '11'
919

20+
21+
1022
dependencies {
1123
implementation 'org.springframework.boot:spring-boot-starter'
1224
implementation 'org.springframework.boot:spring-boot-starter-web'
@@ -33,3 +45,32 @@ task copyPacts(type: Copy) {
3345
from('build/pacts/')
3446
into('../provider/src/test/resources/pacts/')
3547
}
48+
49+
def getGitHash = { ->
50+
def stdout = new ByteArrayOutputStream()
51+
exec {
52+
commandLine 'git', 'rev-parse', '--short', 'HEAD'
53+
standardOutput = stdout
54+
}
55+
return stdout.toString().trim()
56+
}
57+
58+
def getGitBranch = { ->
59+
def stdout = new ByteArrayOutputStream()
60+
exec {
61+
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
62+
standardOutput = stdout
63+
}
64+
return stdout.toString().trim()
65+
}
66+
67+
pact {
68+
publish {
69+
pactDirectory = 'consumer/build/pacts'
70+
pactBrokerUrl = 'http://localhost:8000/'
71+
pactBrokerUsername = 'pact_workshop'
72+
pactBrokerPassword = 'pact_workshop'
73+
tags = [getGitBranch(), 'test', 'prod']
74+
consumerVersion = getGitHash()
75+
}
76+
}

docker-compose.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: "3"
2+
3+
services:
4+
postgres:
5+
image: postgres
6+
healthcheck:
7+
test: psql postgres --command "select 1" -U postgres
8+
ports:
9+
- "5432:5432"
10+
environment:
11+
POSTGRES_USER: postgres
12+
POSTGRES_PASSWORD: password
13+
POSTGRES_DB: postgres
14+
15+
broker_app:
16+
image: pactfoundation/pact-broker
17+
links:
18+
- postgres
19+
ports:
20+
- 8000:9292
21+
environment:
22+
PACT_BROKER_BASIC_AUTH_USERNAME: pact_workshop
23+
PACT_BROKER_BASIC_AUTH_PASSWORD: pact_workshop
24+
PACT_BROKER_DATABASE_USERNAME: postgres
25+
PACT_BROKER_DATABASE_PASSWORD: password
26+
PACT_BROKER_DATABASE_HOST: postgres
27+
PACT_BROKER_DATABASE_NAME: postgres

provider/build.gradle

+24
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,30 @@ dependencies {
1717
testImplementation("au.com.dius.pact.provider:junit5:4.1.7")
1818
}
1919

20+
def getGitHash = { ->
21+
def stdout = new ByteArrayOutputStream()
22+
exec {
23+
commandLine 'git', 'rev-parse', '--short', 'HEAD'
24+
standardOutput = stdout
25+
}
26+
return stdout.toString().trim()
27+
}
28+
29+
def getGitBranch = { ->
30+
def stdout = new ByteArrayOutputStream()
31+
exec {
32+
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
33+
standardOutput = stdout
34+
}
35+
return stdout.toString().trim()
36+
}
37+
2038
test {
2139
useJUnitPlatform()
40+
41+
if (System.getProperty('pactPublishResults') == 'true') {
42+
systemProperty 'pact.provider.version', getGitHash()
43+
systemProperty 'pact.provider.tag', getGitBranch()
44+
systemProperty 'pact.verifier.publishResults', 'true'
45+
}
2246
}

provider/src/test/java/au/com/dius/pactworkshop/provider/ProductPactProviderTest.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider;
66
import au.com.dius.pact.provider.junitsupport.Provider;
77
import au.com.dius.pact.provider.junitsupport.State;
8+
import au.com.dius.pact.provider.junitsupport.loader.PactBroker;
9+
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerAuth;
810
import au.com.dius.pact.provider.junitsupport.loader.PactFolder;
911
import org.apache.http.HttpRequest;
1012
import org.junit.jupiter.api.BeforeEach;
@@ -24,7 +26,12 @@
2426
import static org.mockito.Mockito.when;
2527

2628
@Provider("ProductService")
27-
@PactFolder("pacts")
29+
//@PactFolder("pacts")
30+
@PactBroker(
31+
host = "localhost",
32+
port = "8000",
33+
authentication = @PactBrokerAuth(username = "pact_workshop", password = "pact_workshop")
34+
)
2835
@ExtendWith(SpringExtension.class)
2936
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
3037
public class ProductPactProviderTest {

0 commit comments

Comments
 (0)