From b8d21d721a9620818164212719d7d137b988f6d9 Mon Sep 17 00:00:00 2001 From: Arne Limburg Date: Thu, 24 Aug 2023 18:45:12 +0200 Subject: [PATCH] feat: Add consumer driven contract testing --- .gitignore | 1 + ...r-Driven Contracts.postman_collection.json | 328 ++++++++++++++++++ README.md | 38 +- address-validation-service/.gitignore | 1 + address-validation-service/Jenkinsfile | 125 +++++++ address-validation-service/README.md | 3 + address-validation-service/pom.xml | 15 + .../address/infrastructure/CorsFilter.java | 7 +- .../address/AddressValidationServiceTest.java | 55 +++ billing-service/Jenkinsfile | 125 +++++++ billing-service/README.md | 3 + billing-service/bin/.gitignore | 1 + billing-service/bin/Dockerfile | 25 ++ billing-service/bin/Jenkinsfile | 125 +++++++ billing-service/bin/README.md | 3 + .../bin/deployment/base/deployment.yaml | 22 ++ .../bin/deployment/base/kustomization.yaml | 6 + .../bin/deployment/base/service.yaml | 14 + .../overlays/prod/kustomization.yaml | 19 + .../overlays/prod/patches/port-patch.yaml | 14 + .../overlays/test/kustomization.yaml | 12 + billing-service/bin/pom.xml | 237 +++++++++++++ .../application/AddressApplication.class | Bin 0 -> 1145 bytes .../address/application/AddressResource.class | Bin 0 -> 4537 bytes .../address/domain/Address$Builder.class | Bin 0 -> 2578 bytes .../sample/address/domain/Address.class | Bin 0 -> 4377 bytes .../address/domain/AddressLine$Adapter.class | Bin 0 -> 4799 bytes .../sample/address/domain/AddressLine.class | Bin 0 -> 6346 bytes .../address/domain/AddressRepository.class | Bin 0 -> 4499 bytes .../sample/address/domain/City$Adapter.class | Bin 0 -> 5024 bytes .../sample/address/domain/City.class | Bin 0 -> 6862 bytes .../address/domain/CityName$Adapter.class | Bin 0 -> 3245 bytes .../sample/address/domain/CityName.class | Bin 0 -> 3206 bytes .../domain/CustomerNumber$Adapter.class | Bin 0 -> 3347 bytes .../address/domain/CustomerNumber.class | Bin 0 -> 3312 bytes .../address/domain/HouseNumber$Adapter.class | Bin 0 -> 3351 bytes .../sample/address/domain/HouseNumber.class | Bin 0 -> 3553 bytes .../sample/address/domain/Location.class | Bin 0 -> 3043 bytes .../address/domain/Recipient$Adapter.class | Bin 0 -> 3329 bytes .../sample/address/domain/Recipient.class | Bin 0 -> 3531 bytes .../sample/address/domain/Street.class | Bin 0 -> 4693 bytes .../address/domain/StreetName$Adapter.class | Bin 0 -> 3340 bytes .../sample/address/domain/StreetName.class | Bin 0 -> 3541 bytes .../address/domain/ZipCode$Adapter.class | Bin 0 -> 3572 bytes .../sample/address/domain/ZipCode.class | Bin 0 -> 3995 bytes .../address/BillingAddressServiceTest.class | Bin 0 -> 3105 bytes .../address/JsonObjectComparision.class | Bin 0 -> 2697 bytes .../domain/TestAddressRepository.class | Bin 0 -> 1004 bytes .../customer-service-billing-service.json | 111 ++++++ .../de/openknowledge/sample/address/007.json | 8 + .../sample/address/0815-new.json | 8 + .../de/openknowledge/sample/address/0815.json | 8 + .../de/openknowledge/sample/address/0816.json | 8 + billing-service/pom.xml | 16 + .../address/infrastructure/CorsFilter.java | 7 +- .../ValidationExceptionHandler.java | 47 +++ .../address/BillingAddressServiceTest.java | 55 +++ .../sample/address/JsonObjectComparision.java | 41 +++ .../address/domain/TestAddressRepository.java | 25 ++ .../customer-service-billing-service.json | 111 ++++++ customer-service/.gitignore | 1 + customer-service/Jenkinsfile | 125 +++++++ customer-service/README.md | 3 + customer-service/pom.xml | 28 +- .../address/infrastructure/CorsFilter.java | 7 +- .../domain/BillingAddressRepositoryTest.java | 130 +++++++ .../domain/DeliveryAddressRepositoryTest.java | 166 +++++++++ .../sample/customer/CustomerServiceTest.java | 1 - delivery-db/Dockerfile | 2 + delivery-service/.gitignore | 1 + delivery-service/Jenkinsfile | 125 +++++++ delivery-service/README.md | 18 + delivery-service/namespaces.yaml | 13 + delivery-service/pom.xml | 79 ++++- .../sample/address/domain/Address.java | 73 ++-- .../address/domain/AddressRepository.java | 36 +- .../sample/address/domain/City.java | 8 + .../sample/address/domain/CustomerNumber.java | 8 +- .../sample/address/domain/HouseNumber.java | 3 +- .../sample/address/domain/Recipient.java | 6 +- .../sample/address/domain/Street.java | 9 + .../sample/address/domain/StreetName.java | 2 + .../sample/address/domain/ZipCode.java | 4 + .../address/infrastructure/CorsFilter.java | 7 +- .../main/resources/META-INF/persistence.xml | 15 + .../src/main/resources/sql/create.sql | 1 + .../address/DeliveryAddressServiceTest.java | 94 +++++ .../sample/address/JsonObjectComparision.java | 41 +++ .../domain/AddressValidationServiceTest.java | 112 ++++++ .../sample/infrastructure/CdiMock.java | 35 ++ .../infrastructure/DatabaseCleanup.java | 42 +++ .../sample/infrastructure/ScriptExecutor.java | 43 +++ .../src/test/resources/c3p0.properties | 1 + .../sample/address/007-invalid.json | 8 + .../sample/address/0815-new.json | 8 + .../de/openknowledge/sample/address/0815.json | 8 + .../sample/address/0816-invalid.json | 8 + .../de/openknowledge/sample/address/0816.json | 8 + .../de/openknowledge/sample/address/0817.json | 8 + delivery-service/src/test/resources/h2-ds.xml | 20 ++ .../src/test/resources/sql/data.sql | 2 + deployment/base/gogs/deployment.yaml | 47 +++ deployment/base/gogs/ingress.yaml | 19 + deployment/base/gogs/kustomization.yaml | 7 + deployment/base/gogs/service.yaml | 28 ++ deployment/base/jenkins/deployment.yaml | 29 ++ deployment/base/jenkins/ingress.yaml | 19 + deployment/base/jenkins/kustomization.yaml | 7 + deployment/base/jenkins/service.yaml | 14 + deployment/base/kustomization.yaml | 10 + deployment/base/namespaces.yaml | 13 + deployment/base/nginx/kustomization.yaml | 5 + deployment/base/pact/deployment.yaml | 35 ++ deployment/base/pact/ingress.yaml | 19 + deployment/base/pact/kustomization.yaml | 7 + deployment/base/pact/service.yaml | 14 + deployment/base/registry/deployment.yaml | 22 ++ deployment/base/registry/ingress.yaml | 19 + deployment/base/registry/kustomization.yaml | 7 + deployment/base/registry/service.yaml | 14 + deployment/cluster-config/kind-config.yml | 58 ++++ deployment/kustomization.yaml | 6 + deployment/setup/job.yaml | 12 + deployment/setup/kustomization.yaml | 5 + docker-compose.yaml | 16 + gogs/Dockerfile | 2 + gogs/app.ini | 15 + jenkins/Dockerfile | 18 + jenkins/executors.groovy | 2 + scripts/.gitignore | 2 + scripts/cleanJobs.groovy | 9 + scripts/cleanJobs.sh | 7 + scripts/jenkinsCurl.sh | 14 + scripts/push.sh | 36 ++ scripts/pushAll.sh | 11 + setup/Dockerfile | 9 + .../address-validation-service/config.xml | 58 ++++ setup/jenkins/billing-service/config.xml | 58 ++++ setup/jenkins/create-jobs.sh | 28 ++ setup/jenkins/customer-service/config.xml | 58 ++++ setup/jenkins/delivery-service/config.xml | 58 ++++ setup/pushToGogs.sh | 35 ++ setup/setup.sh | 50 +++ 143 files changed, 3692 insertions(+), 143 deletions(-) create mode 100644 Consumer-Driven Contracts.postman_collection.json create mode 100644 address-validation-service/.gitignore create mode 100644 address-validation-service/Jenkinsfile create mode 100644 address-validation-service/README.md create mode 100644 address-validation-service/src/test/java/de/openknowledge/sample/address/AddressValidationServiceTest.java create mode 100644 billing-service/Jenkinsfile create mode 100644 billing-service/README.md create mode 100644 billing-service/bin/.gitignore create mode 100644 billing-service/bin/Dockerfile create mode 100644 billing-service/bin/Jenkinsfile create mode 100644 billing-service/bin/README.md create mode 100644 billing-service/bin/deployment/base/deployment.yaml create mode 100644 billing-service/bin/deployment/base/kustomization.yaml create mode 100644 billing-service/bin/deployment/base/service.yaml create mode 100644 billing-service/bin/deployment/overlays/prod/kustomization.yaml create mode 100644 billing-service/bin/deployment/overlays/prod/patches/port-patch.yaml create mode 100644 billing-service/bin/deployment/overlays/test/kustomization.yaml create mode 100644 billing-service/bin/pom.xml create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/application/AddressApplication.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/application/AddressResource.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Address$Builder.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Address.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressLine$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressLine.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/City$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/City.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CityName$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CityName.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/HouseNumber$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Location.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Recipient$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Recipient.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Street.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/StreetName$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/StreetName.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/ZipCode$Adapter.class create mode 100644 billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/ZipCode.class create mode 100644 billing-service/bin/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.class create mode 100644 billing-service/bin/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.class create mode 100644 billing-service/bin/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.class create mode 100644 billing-service/bin/src/test/pacts/customer-service-billing-service.json create mode 100644 billing-service/bin/src/test/resources/de/openknowledge/sample/address/007.json create mode 100644 billing-service/bin/src/test/resources/de/openknowledge/sample/address/0815-new.json create mode 100644 billing-service/bin/src/test/resources/de/openknowledge/sample/address/0815.json create mode 100644 billing-service/bin/src/test/resources/de/openknowledge/sample/address/0816.json create mode 100644 billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/ValidationExceptionHandler.java create mode 100644 billing-service/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.java create mode 100644 billing-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java create mode 100644 billing-service/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.java create mode 100644 billing-service/src/test/pacts/customer-service-billing-service.json create mode 100644 customer-service/.gitignore create mode 100644 customer-service/Jenkinsfile create mode 100644 customer-service/README.md create mode 100644 customer-service/src/test/java/de/openknowledge/sample/address/domain/BillingAddressRepositoryTest.java create mode 100644 customer-service/src/test/java/de/openknowledge/sample/address/domain/DeliveryAddressRepositoryTest.java create mode 100644 delivery-db/Dockerfile create mode 100644 delivery-service/.gitignore create mode 100644 delivery-service/Jenkinsfile create mode 100644 delivery-service/README.md create mode 100644 delivery-service/namespaces.yaml create mode 100644 delivery-service/src/main/resources/META-INF/persistence.xml create mode 100644 delivery-service/src/main/resources/sql/create.sql create mode 100644 delivery-service/src/test/java/de/openknowledge/sample/address/DeliveryAddressServiceTest.java create mode 100644 delivery-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java create mode 100644 delivery-service/src/test/java/de/openknowledge/sample/address/domain/AddressValidationServiceTest.java create mode 100644 delivery-service/src/test/java/de/openknowledge/sample/infrastructure/CdiMock.java create mode 100644 delivery-service/src/test/java/de/openknowledge/sample/infrastructure/DatabaseCleanup.java create mode 100644 delivery-service/src/test/java/de/openknowledge/sample/infrastructure/ScriptExecutor.java create mode 100644 delivery-service/src/test/resources/c3p0.properties create mode 100644 delivery-service/src/test/resources/de/openknowledge/sample/address/007-invalid.json create mode 100644 delivery-service/src/test/resources/de/openknowledge/sample/address/0815-new.json create mode 100644 delivery-service/src/test/resources/de/openknowledge/sample/address/0815.json create mode 100644 delivery-service/src/test/resources/de/openknowledge/sample/address/0816-invalid.json create mode 100644 delivery-service/src/test/resources/de/openknowledge/sample/address/0816.json create mode 100644 delivery-service/src/test/resources/de/openknowledge/sample/address/0817.json create mode 100644 delivery-service/src/test/resources/h2-ds.xml create mode 100644 delivery-service/src/test/resources/sql/data.sql create mode 100644 deployment/base/gogs/deployment.yaml create mode 100644 deployment/base/gogs/ingress.yaml create mode 100644 deployment/base/gogs/kustomization.yaml create mode 100644 deployment/base/gogs/service.yaml create mode 100644 deployment/base/jenkins/deployment.yaml create mode 100644 deployment/base/jenkins/ingress.yaml create mode 100644 deployment/base/jenkins/kustomization.yaml create mode 100644 deployment/base/jenkins/service.yaml create mode 100644 deployment/base/kustomization.yaml create mode 100644 deployment/base/namespaces.yaml create mode 100644 deployment/base/nginx/kustomization.yaml create mode 100644 deployment/base/pact/deployment.yaml create mode 100644 deployment/base/pact/ingress.yaml create mode 100644 deployment/base/pact/kustomization.yaml create mode 100644 deployment/base/pact/service.yaml create mode 100644 deployment/base/registry/deployment.yaml create mode 100644 deployment/base/registry/ingress.yaml create mode 100644 deployment/base/registry/kustomization.yaml create mode 100644 deployment/base/registry/service.yaml create mode 100644 deployment/cluster-config/kind-config.yml create mode 100644 deployment/kustomization.yaml create mode 100644 deployment/setup/job.yaml create mode 100644 deployment/setup/kustomization.yaml create mode 100644 gogs/Dockerfile create mode 100644 gogs/app.ini create mode 100644 jenkins/Dockerfile create mode 100644 jenkins/executors.groovy create mode 100644 scripts/.gitignore create mode 100644 scripts/cleanJobs.groovy create mode 100755 scripts/cleanJobs.sh create mode 100755 scripts/jenkinsCurl.sh create mode 100755 scripts/push.sh create mode 100755 scripts/pushAll.sh create mode 100644 setup/Dockerfile create mode 100644 setup/jenkins/address-validation-service/config.xml create mode 100644 setup/jenkins/billing-service/config.xml create mode 100755 setup/jenkins/create-jobs.sh create mode 100644 setup/jenkins/customer-service/config.xml create mode 100644 setup/jenkins/delivery-service/config.xml create mode 100755 setup/pushToGogs.sh create mode 100755 setup/setup.sh diff --git a/.gitignore b/.gitignore index c1a3be6..9e68497 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /keycloak/* !/keycloak/keycloak.mv.db target/ +workspace/ *.iml diff --git a/Consumer-Driven Contracts.postman_collection.json b/Consumer-Driven Contracts.postman_collection.json new file mode 100644 index 0000000..279beea --- /dev/null +++ b/Consumer-Driven Contracts.postman_collection.json @@ -0,0 +1,328 @@ +{ + "info": { + "_postman_id": "38c24193-20db-400c-894c-93e289835152", + "name": "Consumer-Driven Contracts", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "27875178" + }, + "item": [ + { + "name": "Get Customers", + "request": { + "method": "GET", + "header": [], + "url": "localhost:30000/customers" + }, + "response": [] + }, + { + "name": "Get Customers (test)", + "request": { + "method": "GET", + "header": [], + "url": "localhost:31000/customers" + }, + "response": [] + }, + { + "name": "Get single Customer", + "request": { + "method": "GET", + "header": [], + "url": "localhost:30000/customers/007" + }, + "response": [] + }, + { + "name": "Get single Customer (test)", + "request": { + "method": "GET", + "header": [], + "url": "localhost:31000/customers/007" + }, + "response": [] + }, + { + "name": "Set Billing Address", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient\": \"Sherlock Holmes\",\n \"street\": {\n \"name\": \"Baker Street\",\n \"number\": \"221B\"\n },\n \"city\": \"London NW1 6XE\"\n}\n" + }, + "url": "localhost:30000/customers/007/billing-address" + }, + "response": [] + }, + { + "name": "Set Billing Address (test)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient\": \"James Bond\",\n \"street\": {\n \"name\": \"Baker Street\",\n \"number\": \"221B\"\n },\n \"city\": \"London NW1 6XE\"\n}\n" + }, + "url": "localhost:31000/customers/007/billing-address" + }, + "response": [] + }, + { + "name": "Set Delivery Address", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient\": \"Erika Mustermann\",\n \"street\": {\n \"name\": \"Test\",\n \"number\": \"1\"\n },\n \"city\": \"26122 Oldenburg\"\n}\n" + }, + "url": "localhost:30000/customers/007/delivery-address" + }, + "response": [] + }, + { + "name": "Set Delivery Address (test)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient\": \"Max Mustermann\",\n \"street\": {\n \"name\": \"Test\",\n \"number\": \"1\"\n },\n \"city\": \"26122 Oldenburg\"\n}\n" + }, + "url": "localhost:31000/customers/007/delivery-address" + }, + "response": [] + }, + { + "name": "Validate Address (test)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient\": \"Max Mustermann\",\n \"street\": {\n \"name\": \"Poststrasse\",\n \"number\": \"1\"\n },\n \"city\": \"26122 Oldenburg\"\n}\n" + }, + "url": "localhost:31003/valid-addresses" + }, + "response": [] + }, + { + "name": "Get Billing Address (test)", + "request": { + "method": "GET", + "header": [], + "url": "localhost:31001/billing-addresses/007" + }, + "response": [] + }, + { + "name": "Get Delivery Address (test)", + "request": { + "method": "GET", + "header": [], + "url": "http://localhost:31002/delivery-addresses/007" + }, + "response": [] + }, + { + "name": "Create Webhook for billing-service", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"provider\": {\n \"name\": \"billing-service\"\n },\n \"events\": [{\n \"name\": \"contract_content_changed\"\n }],\n \"request\": {\n \"method\": \"GET\",\n \"url\": \"http://jenkins-service:8080/generic-webhook-trigger/invoke?token=billing-service&stage=${pactbroker.consumerVersionTags}&verifyPacts=true\",\n \"headers\": {\n }\n }\n}" + }, + "url": "http://localhost:30050/webhooks" + }, + "response": [] + }, + { + "name": "Create Webhook for delivery-service", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"provider\": {\n \"name\": \"delivery-service\"\n },\n \"events\": [{\n \"name\": \"contract_content_changed\"\n }],\n \"request\": {\n \"method\": \"GET\",\n \"url\": \"http://jenkins-service:8080/generic-webhook-trigger/invoke?token=delivery-service&stage=${pactbroker.consumerVersionTags}&verifyPacts=true\",\n \"headers\": {\n }\n }\n}" + }, + "url": "http://localhost:30050/webhooks" + }, + "response": [] + }, + { + "name": "Create Webhook for address-validation-service", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"provider\": {\n \"name\": \"address-validation-service\"\n },\n \"events\": [{\n \"name\": \"contract_content_changed\"\n }],\n \"request\": {\n \"method\": \"GET\",\n \"url\": \"http://jenkins-service:8080/generic-webhook-trigger/invoke?token=address-validation-service&stage=${pactbroker.consumerVersionTags}&verifyPacts=true\",\n \"headers\": {\n }\n }\n}" + }, + "url": "http://localhost:30050/webhooks" + }, + "response": [] + }, + { + "name": "Create Verification Webhook for customer-service", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"consumer\": {\n \"name\": \"customer-service\"\n },\n \"events\": [{\n \"name\": \"provider_verification_published\"\n }],\n \"request\": {\n \"method\": \"GET\",\n \"url\": \"http://jenkins-service:8080/generic-webhook-trigger/invoke?token=customer-service&stage=${pactbroker.consumerVersionTags}&deployOnly=true&deploymentVersion=${pactbroker.consumerVersionNumber}\",\n \"headers\": {\n }\n }\n}" + }, + "url": "http://localhost:30050/webhooks" + }, + "response": [] + }, + { + "name": "Create Verification Webhook for delivery-service", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"consumer\": {\n \"name\": \"delivery-service\"\n },\n \"events\": [{\n \"name\": \"provider_verification_published\"\n }],\n \"request\": {\n \"method\": \"GET\",\n \"url\": \"http://jenkins-service:8080/generic-webhook-trigger/invoke?token=delivery-service&stage=${pactbroker.consumerVersionTags}&deployOnly=true&deploymentVersion=${pactbroker.consumerVersionNumber}\",\n \"headers\": {\n }\n }\n}" + }, + "url": "http://localhost:30050/webhooks" + }, + "response": [] + }, + { + "name": "Execute Verification Webhook for Customer Service", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [] + }, + "url": { + "raw": "http://localhost:9080/generic-webhook-trigger/invoke?token=customer-service&stage=pending-prod&deployOnly=true&deploymentVersion=1.2.1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "9080", + "path": [ + "generic-webhook-trigger", + "invoke" + ], + "query": [ + { + "key": "token", + "value": "customer-service" + }, + { + "key": "stage", + "value": "pending-prod" + }, + { + "key": "deployOnly", + "value": "true" + }, + { + "key": "deploymentVersion", + "value": "1.2.1" + } + ] + } + }, + "response": [] + }, + { + "name": "Set prod tag to Delivery Service", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": "http://localhost:30050/pacticipants/delivery-service/versions/1.2.0/tags/prod" + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 27853ff..e0645b8 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,3 @@ # Workshop API Design -Herzlich willkommen zum Workshop API Design. - -## Übungen - -### API Design - -- [OpenAPI](https://github.com/openknowledge/workshop-api-design/tree/openapi) -- [Mocking](https://github.com/openknowledge/workshop-api-design/tree/wiremock) -- [AsyncAPI](https://github.com/openknowledge/workshop-api-design/tree/asyncapi) - -### API Testing - -- [Pact](https://github.com/openknowledge/workshop-api-design/tree/pact-mock-server) -- [Pact Pipeline](https://github.com/openknowledge/workshop-api-design/tree/pact) - -### API Security - -- [JWT](https://github.com/openknowledge/workshop-api-design/tree/jwt) -- [OAuth2](https://github.com/openknowledge/workshop-api-design/tree/oauth2) -- [OAuth2 mit PKCE](https://github.com/openknowledge/workshop-api-design/tree/oauth2-pkce) - -### API Governance - -- [Linting](https://github.com/openknowledge/workshop-api-design/tree/linting) - -### API Management - -- [Rate Limiting](https://github.com/openknowledge/workshop-api-design/tree/rate-limiting) -- [Backstage](https://github.com/openknowledge/workshop-api-design/tree/backstage) - -### API Operation - -- [Observability](https://github.com/openknowledge/workshop-api-design/tree/observability) - -### API Evolution - -- [Versioning](https://github.com/openknowledge/workshop-api-design/tree/versioning) +Herzlich willkommen zur Übung Pact. diff --git a/address-validation-service/.gitignore b/address-validation-service/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/address-validation-service/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/address-validation-service/Jenkinsfile b/address-validation-service/Jenkinsfile new file mode 100644 index 0000000..4af7ff8 --- /dev/null +++ b/address-validation-service/Jenkinsfile @@ -0,0 +1,125 @@ +#!/usr/bin/env groovy +pipeline { + agent any + + options { + disableConcurrentBuilds() + } + + environment { + SNAPSHOT_VERSION = readMavenPom().getVersion() + LAST_COMMIT_MESSAGE = "${currentBuild.changeSets.size() == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items[currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length - 1].msg}" + PERFORM_RELEASE = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') && env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ')}" + RELEASE_VERSION = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') ? env.SNAPSHOT_VERSION.substring(0, env.SNAPSHOT_VERSION.lastIndexOf('-SNAPSHOT')) : SNAPSHOT_VERSION}" + VERSION = "${env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ') ? env.RELEASE_VERSION : env.SNAPSHOT_VERSION}" + } + + triggers { + pollSCM("* * * * *") + } + + stages { + stage ('Compile') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + echo "Building version ${env.VERSION}" + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn versions:set -DnewVersion=${env.RELEASE_VERSION} -B" + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.RELEASE_VERSION}/g' deployment/overlays/prod/kustomization.yaml" + } else { + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.GIT_COMMIT}/g' deployment/overlays/test/kustomization.yaml" + } + } + sh 'mvn clean test-compile -B' + } + } + stage ('Test') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh "mvn test -B" + } + } + stage ('Package') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh 'mvn package -DskipTests -B' + sh 'docker build -t address-validation .' + } + } + stage ('Push') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh """ + docker tag address-validation localhost:30010/address-validation:${env.VERSION} + docker tag address-validation localhost:30010/address-validation:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker tag address-validation localhost:30010/address-validation:${env.GIT_COMMIT} + docker push localhost:30010/address-validation:${env.VERSION} + docker push localhost:30010/address-validation:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker push localhost:30010/address-validation:${env.GIT_COMMIT} + """ + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh 'git config --global user.name "Jenkins"' + sh 'git config --global user.email "ci@openknowledge.de"' + sh "mvn scm:checkin -Dmessage='release of version ${env.RELEASE_VERSION}' -B" + sh "mvn scm:tag -Dtag=${env.RELEASE_VERSION} -B" + int nextRevision = Integer.parseInt(env.RELEASE_VERSION.substring(env.RELEASE_VERSION.lastIndexOf(".") + 1)) + 1 + nextVersion = RELEASE_VERSION.substring(0, env.RELEASE_VERSION.lastIndexOf(".")) + "." + nextRevision + "-SNAPSHOT" + sh "sed -i 's/${env.RELEASE_VERSION}/${nextVersion}/g' deployment/overlays/prod/kustomization.yaml" + sh "mvn versions:set scm:checkin -DnewVersion=${nextVersion} -Dmessage='update version to ${nextVersion}' -B" + } + } + } + } + stage ('Deploy') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn scm:checkout -DscmVersion=${env.RELEASE_VERSION} -DscmVersionType=tag -B" + sh 'kubectl apply -k deployment/overlays/prod' + } else { + sh 'kubectl apply -k deployment/overlays/test' + sh "sed -i 's/${env.GIT_COMMIT}/${env.SNAPSHOT_VERSION}/g' deployment/overlays/test/kustomization.yaml" + } + } + } + } + } +} diff --git a/address-validation-service/README.md b/address-validation-service/README.md new file mode 100644 index 0000000..2108dca --- /dev/null +++ b/address-validation-service/README.md @@ -0,0 +1,3 @@ +# cdc-address-validation-service + +Address Validation Service to show Consumer-Driven Contracts diff --git a/address-validation-service/pom.xml b/address-validation-service/pom.xml index 997656e..f54426f 100644 --- a/address-validation-service/pom.xml +++ b/address-validation-service/pom.xml @@ -16,6 +16,11 @@ 1.0.0-SNAPSHOT war + + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/address-validation-service.git + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/address-validation-service.git + + online-shop 11 @@ -106,6 +111,11 @@ org.mockito mockito-core 2.28.2 + + + au.com.dius.pact.provider + junit5 + 4.3.5 test @@ -135,6 +145,11 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M5 + + + ${project.version} + + org.apache.maven.plugins diff --git a/address-validation-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java b/address-validation-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java index d63c000..d214eb1 100644 --- a/address-validation-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java +++ b/address-validation-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java @@ -15,8 +15,6 @@ */ package de.openknowledge.sample.address.infrastructure; -import java.io.IOException; - import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; @@ -26,10 +24,7 @@ public class CorsFilter implements ContainerResponseFilter { @Override - public void filter( - ContainerRequestContext requestContext, - ContainerResponseContext responseContext) throws IOException { - + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true"); diff --git a/address-validation-service/src/test/java/de/openknowledge/sample/address/AddressValidationServiceTest.java b/address-validation-service/src/test/java/de/openknowledge/sample/address/AddressValidationServiceTest.java new file mode 100644 index 0000000..6b90980 --- /dev/null +++ b/address-validation-service/src/test/java/de/openknowledge/sample/address/AddressValidationServiceTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address; + +import org.apache.meecrowave.Meecrowave; +import org.apache.meecrowave.junit5.MonoMeecrowaveConfig; +import org.apache.meecrowave.testing.ConfigurationInject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; + +import au.com.dius.pact.provider.junit5.HttpTestTarget; +import au.com.dius.pact.provider.junit5.PactVerificationContext; +import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider; +import au.com.dius.pact.provider.junitsupport.Provider; +import au.com.dius.pact.provider.junitsupport.State; +import au.com.dius.pact.provider.junitsupport.loader.PactFolder; + +@Provider("address-validation-service") +@PactFolder("src/test/pacts") +@MonoMeecrowaveConfig +public class AddressValidationServiceTest { + + @ConfigurationInject + private Meecrowave.Builder config; + + @BeforeEach + public void setUp(PactVerificationContext context) { + context.setTarget(new HttpTestTarget("localhost", config.getHttpPort(), "/")); + } + + @TestTemplate + @ExtendWith(PactVerificationInvocationContextProvider.class) + void pactVerificationTestTemplate(PactVerificationContext context) { + context.verifyInteraction(); + } + + @State("Three customers") + public void setThreeCustomers() { + // nothing to do + } +} diff --git a/billing-service/Jenkinsfile b/billing-service/Jenkinsfile new file mode 100644 index 0000000..7916245 --- /dev/null +++ b/billing-service/Jenkinsfile @@ -0,0 +1,125 @@ +#!/usr/bin/env groovy +pipeline { + agent any + + options { + disableConcurrentBuilds() + } + + environment { + SNAPSHOT_VERSION = readMavenPom().getVersion() + LAST_COMMIT_MESSAGE = "${currentBuild.changeSets.size() == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items[currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length - 1].msg}" + PERFORM_RELEASE = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') && env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ')}" + RELEASE_VERSION = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') ? env.SNAPSHOT_VERSION.substring(0, env.SNAPSHOT_VERSION.lastIndexOf('-SNAPSHOT')) : SNAPSHOT_VERSION}" + VERSION = "${env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ') ? env.RELEASE_VERSION : env.SNAPSHOT_VERSION}" + } + + triggers { + pollSCM("* * * * *") + } + + stages { + stage ('Compile') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + echo "Building version ${env.VERSION}" + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn versions:set -DnewVersion=${env.RELEASE_VERSION} -B" + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.RELEASE_VERSION}/g' deployment/overlays/prod/kustomization.yaml" + } else { + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.GIT_COMMIT}/g' deployment/overlays/test/kustomization.yaml" + } + } + sh 'mvn clean test-compile -B' + } + } + stage ('Test') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh "mvn test -B" + } + } + stage ('Package') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh 'mvn package -Dmaven.test.skip=true -B' + sh 'docker build -t billing .' + } + } + stage ('Push') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh """ + docker tag billing localhost:30010/billing:${env.VERSION} + docker tag billing localhost:30010/billing:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker tag billing localhost:30010/billing:${env.GIT_COMMIT} + docker push localhost:30010/billing:${env.VERSION} + docker push localhost:30010/billing:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker push localhost:30010/billing:${env.GIT_COMMIT} + """ + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh 'git config --global user.name "Jenkins"' + sh 'git config --global user.email "ci@openknowledge.de"' + sh "mvn scm:checkin -Dmessage='release of version ${env.RELEASE_VERSION}' -B" + sh "mvn scm:tag -Dtag=${env.RELEASE_VERSION} -B" + int nextRevision = Integer.parseInt(env.RELEASE_VERSION.substring(env.RELEASE_VERSION.lastIndexOf(".") + 1)) + 1 + nextVersion = RELEASE_VERSION.substring(0, env.RELEASE_VERSION.lastIndexOf(".")) + "." + nextRevision + "-SNAPSHOT" + sh "sed -i 's/${env.RELEASE_VERSION}/${nextVersion}/g' deployment/overlays/prod/kustomization.yaml" + sh "mvn versions:set scm:checkin -DnewVersion=${nextVersion} -Dmessage='update version to ${nextVersion}' -B" + } + } + } + } + stage ('Deploy') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn scm:checkout -DscmVersion=${env.RELEASE_VERSION} -DscmVersionType=tag -B" + sh 'kubectl apply -k deployment/overlays/prod' + } else { + sh 'kubectl apply -k deployment/overlays/test' + sh "sed -i 's/${env.GIT_COMMIT}/${env.SNAPSHOT_VERSION}/g' deployment/overlays/test/kustomization.yaml" + } + } + } + } + } +} diff --git a/billing-service/README.md b/billing-service/README.md new file mode 100644 index 0000000..5e20406 --- /dev/null +++ b/billing-service/README.md @@ -0,0 +1,3 @@ +# cdc-billing-service + +Billing Service to show Consumer-Driven Contracts \ No newline at end of file diff --git a/billing-service/bin/.gitignore b/billing-service/bin/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/billing-service/bin/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/billing-service/bin/Dockerfile b/billing-service/bin/Dockerfile new file mode 100644 index 0000000..5d17849 --- /dev/null +++ b/billing-service/bin/Dockerfile @@ -0,0 +1,25 @@ +<<<<<<< HEAD +FROM maven:3.6.3-jdk-11-openj9 AS mvn + +WORKDIR /usr/src/online-shop +COPY pom.xml ./ +RUN mvn package dependency:go-offline # cache dependencies +COPY src ./src +RUN mvn clean package -Dservice.name=billing-service -DskipTests + +FROM openjdk:11 + +EXPOSE 8080 +COPY --from=mvn /usr/src/online-shop/target/runner/meecrowave-core-runner.jar /opt/meecrowave-runner.jar +COPY --from=mvn /usr/src/online-shop/target/billing-service.war /opt/billing-service.war + +ENTRYPOINT ["java", "--illegal-access=permit", "-Djava.net.preferIPv4Stack=true", "-jar", "/opt/meecrowave-runner.jar", "--webapp", "/opt/billing-service.war"] +======= +FROM openjdk:11-jre + +RUN wget https://repo.maven.apache.org/maven2/org/apache/meecrowave/meecrowave-core/1.2.13/meecrowave-core-1.2.13-runner.jar -O /opt/meecrowave-core-runner.jar +ADD target/billing-service.war /opt/billing-service.war + +EXPOSE 4001 +ENTRYPOINT ["java", "-Djava.net.preferIPv4Stack=true", "-jar", "/opt/meecrowave-core-runner.jar", "--host", "0.0.0.0", "--http", "4001", "--webapp", "/opt/billing-service.war"] +>>>>>>> ff12913... feat: Add consumer driven contract testing diff --git a/billing-service/bin/Jenkinsfile b/billing-service/bin/Jenkinsfile new file mode 100644 index 0000000..7916245 --- /dev/null +++ b/billing-service/bin/Jenkinsfile @@ -0,0 +1,125 @@ +#!/usr/bin/env groovy +pipeline { + agent any + + options { + disableConcurrentBuilds() + } + + environment { + SNAPSHOT_VERSION = readMavenPom().getVersion() + LAST_COMMIT_MESSAGE = "${currentBuild.changeSets.size() == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items[currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length - 1].msg}" + PERFORM_RELEASE = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') && env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ')}" + RELEASE_VERSION = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') ? env.SNAPSHOT_VERSION.substring(0, env.SNAPSHOT_VERSION.lastIndexOf('-SNAPSHOT')) : SNAPSHOT_VERSION}" + VERSION = "${env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ') ? env.RELEASE_VERSION : env.SNAPSHOT_VERSION}" + } + + triggers { + pollSCM("* * * * *") + } + + stages { + stage ('Compile') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + echo "Building version ${env.VERSION}" + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn versions:set -DnewVersion=${env.RELEASE_VERSION} -B" + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.RELEASE_VERSION}/g' deployment/overlays/prod/kustomization.yaml" + } else { + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.GIT_COMMIT}/g' deployment/overlays/test/kustomization.yaml" + } + } + sh 'mvn clean test-compile -B' + } + } + stage ('Test') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh "mvn test -B" + } + } + stage ('Package') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh 'mvn package -Dmaven.test.skip=true -B' + sh 'docker build -t billing .' + } + } + stage ('Push') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh """ + docker tag billing localhost:30010/billing:${env.VERSION} + docker tag billing localhost:30010/billing:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker tag billing localhost:30010/billing:${env.GIT_COMMIT} + docker push localhost:30010/billing:${env.VERSION} + docker push localhost:30010/billing:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker push localhost:30010/billing:${env.GIT_COMMIT} + """ + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh 'git config --global user.name "Jenkins"' + sh 'git config --global user.email "ci@openknowledge.de"' + sh "mvn scm:checkin -Dmessage='release of version ${env.RELEASE_VERSION}' -B" + sh "mvn scm:tag -Dtag=${env.RELEASE_VERSION} -B" + int nextRevision = Integer.parseInt(env.RELEASE_VERSION.substring(env.RELEASE_VERSION.lastIndexOf(".") + 1)) + 1 + nextVersion = RELEASE_VERSION.substring(0, env.RELEASE_VERSION.lastIndexOf(".")) + "." + nextRevision + "-SNAPSHOT" + sh "sed -i 's/${env.RELEASE_VERSION}/${nextVersion}/g' deployment/overlays/prod/kustomization.yaml" + sh "mvn versions:set scm:checkin -DnewVersion=${nextVersion} -Dmessage='update version to ${nextVersion}' -B" + } + } + } + } + stage ('Deploy') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn scm:checkout -DscmVersion=${env.RELEASE_VERSION} -DscmVersionType=tag -B" + sh 'kubectl apply -k deployment/overlays/prod' + } else { + sh 'kubectl apply -k deployment/overlays/test' + sh "sed -i 's/${env.GIT_COMMIT}/${env.SNAPSHOT_VERSION}/g' deployment/overlays/test/kustomization.yaml" + } + } + } + } + } +} diff --git a/billing-service/bin/README.md b/billing-service/bin/README.md new file mode 100644 index 0000000..5e20406 --- /dev/null +++ b/billing-service/bin/README.md @@ -0,0 +1,3 @@ +# cdc-billing-service + +Billing Service to show Consumer-Driven Contracts \ No newline at end of file diff --git a/billing-service/bin/deployment/base/deployment.yaml b/billing-service/bin/deployment/base/deployment.yaml new file mode 100644 index 0000000..3360e51 --- /dev/null +++ b/billing-service/bin/deployment/base/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: billing-deployment + labels: + app: billing-service +spec: + replicas: 1 + selector: + matchLabels: + app: billing-service + template: + metadata: + labels: + app: billing-service + spec: + containers: + - name: billing + image: billing:latest + ports: + - containerPort: 4001 + name: http diff --git a/billing-service/bin/deployment/base/kustomization.yaml b/billing-service/bin/deployment/base/kustomization.yaml new file mode 100644 index 0000000..5b98e94 --- /dev/null +++ b/billing-service/bin/deployment/base/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml diff --git a/billing-service/bin/deployment/base/service.yaml b/billing-service/bin/deployment/base/service.yaml new file mode 100644 index 0000000..e063702 --- /dev/null +++ b/billing-service/bin/deployment/base/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: billing-service +spec: + selector: + app: billing-service + type: NodePort + ports: + - protocol: TCP + port: 4001 + targetPort: 4001 + nodePort: 30070 + name: service diff --git a/billing-service/bin/deployment/overlays/prod/kustomization.yaml b/billing-service/bin/deployment/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..45820fa --- /dev/null +++ b/billing-service/bin/deployment/overlays/prod/kustomization.yaml @@ -0,0 +1,19 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: prod + +resources: + - ../../base + +patches: + - target: + version: v1 + kind: Service + name: billing-service + path: ./patches/port-patch.yaml + +images: + - name: billing + newName: localhost:30010/billing + newTag: 1.1.0-SNAPSHOT diff --git a/billing-service/bin/deployment/overlays/prod/patches/port-patch.yaml b/billing-service/bin/deployment/overlays/prod/patches/port-patch.yaml new file mode 100644 index 0000000..f5815ed --- /dev/null +++ b/billing-service/bin/deployment/overlays/prod/patches/port-patch.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: billing-service +spec: + selector: + app: billing-service + type: NodePort + ports: + - protocol: TCP + port: 4001 + targetPort: 4001 + nodePort: 31070 + name: service diff --git a/billing-service/bin/deployment/overlays/test/kustomization.yaml b/billing-service/bin/deployment/overlays/test/kustomization.yaml new file mode 100644 index 0000000..4a5e8ae --- /dev/null +++ b/billing-service/bin/deployment/overlays/test/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: test + +resources: + - ../../base + +images: + - name: billing + newName: localhost:30010/billing + newTag: 1.1.0-SNAPSHOT diff --git a/billing-service/bin/pom.xml b/billing-service/bin/pom.xml new file mode 100644 index 0000000..295d8d0 --- /dev/null +++ b/billing-service/bin/pom.xml @@ -0,0 +1,237 @@ + + + + + 4.0.0 + + de.openkonwledge.sample.shop + ${service.name} + 1.0.0-SNAPSHOT + war + + + online-shop +======= + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + de.openkonwledge.sample.shop + billing-service + 1.1.0-SNAPSHOT + Microservices – Service "Billing" + war + + + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/billing-service.git + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/billing-service.git + + + +>>>>>>> ff12913... feat: Add consumer driven contract testing + 11 + 11 + false + UTF-8 + 1.2.13 +<<<<<<< HEAD + 1.9.6 +======= +>>>>>>> ff12913... feat: Add consumer driven contract testing + 5.8.2 + + + + + + org.apache.meecrowave + meecrowave-specs-api + ${meecrowave.version} + provided + + +<<<<<<< HEAD + org.apache.meecrowave + meecrowave-core + ${meecrowave.version} + provided + + + org.apache.geronimo.specs + geronimo-validation_1.0_spec + 1.1 + + + org.eclipse.microprofile.config + microprofile-config-api + 1.4 + + + org.apache.geronimo.config + geronimo-config-impl + 1.2.2 + + + org.eclipse.microprofile.jwt + microprofile-jwt-auth-api + 1.2.1 + + + org.apache.geronimo + geronimo-jwt-auth + 1.0.4 + + + org.apache.deltaspike.modules + deltaspike-security-module-api + ${deltaspike.version} + compile + + + org.apache.deltaspike.modules + deltaspike-security-module-impl + ${deltaspike.version} + runtime + + +======= +>>>>>>> ff12913... feat: Add consumer driven contract testing + org.apache.commons + commons-lang3 + 3.9 + +<<<<<<< HEAD + +======= +>>>>>>> ff12913... feat: Add consumer driven contract testing + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.assertj + assertj-core + 3.22.0 + test + + +<<<<<<< HEAD + org.mockito + mockito-core + 2.28.2 +======= + au.com.dius.pact.provider + junit5 + 4.3.5 +>>>>>>> ff12913... feat: Add consumer driven contract testing + test + + + org.apache.meecrowave + meecrowave-junit + ${meecrowave.version} + test + +<<<<<<< HEAD + + rocks.limburg.cdimock + cdimock + 1.0.4 + test + + + com.tngtech.archunit + archunit + 1.0.1 + test + + + + + ${project.artifactId} +======= + + + + billing-service +>>>>>>> ff12913... feat: Add consumer driven contract testing + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 +<<<<<<< HEAD + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-runner + package + + copy + + + + + org.apache.meecrowave + meecrowave-core + ${meecrowave-version} + runner + + + ${project.build.directory}/runner + true + + + + + + maven-checkstyle-plugin + 3.2.0 + + true + ${project.basedir}/src/main/checkstyle/java.header + true + true + + + + compile + compile + + checkstyle + + + ${project.basedir}/src/main/checkstyle/configuration.xml + true + + + +======= + + + ${project.version} + + +>>>>>>> ff12913... feat: Add consumer driven contract testing + + + + diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/application/AddressApplication.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/application/AddressApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..99fb3fb8cc27cbbaf25a6af4fb9a899d28e2ad3c GIT binary patch literal 1145 zcmb_cO>fgc5S>laCUFC40~H_Tu=Lbk>`PA+Rf-UZNTDDaDskMbr^&YKU9-Ch%`Za& z3GVzT#Mmi{5fK8Z2hVE8&u?bu&G`F|&tC!H7#=mC#$e!`OfsQNav^3SNinfGnoDCp zYD~wNc#-8A6OpD4y-!4*Yb8Zc)+D_}thWp`pw3{cB+|l+lh>mQl!3vJDDzbk`~) zDIFI|$7Rp{hkwxtmF3ojvg21P8t{*x%DB7{VX}gV2g^mNZq%xv|Bj5H3Afs?4!0TX z><^Z6wGiy6n7htE89bS0BXmPSqr{*)uu|wV;Z)&Y2kT*?JcE-PU5z6K(W#v}i7#lj z7~K1FdrDmx?7p3vpfc>6nevLt>J5=T6^9YrOISO?au$ z2`4A>20Uc2S^0`V`<3#Z#&}2@f!`wAUZ7wby5z0F9kMmJ3-{>+*rlgVhzCCbb(m_u literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/application/AddressResource.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/application/AddressResource.class new file mode 100644 index 0000000000000000000000000000000000000000..a90286399b9bfbf4c649f86cb975dbeccab1c8de GIT binary patch literal 4537 zcmeHLTTdHD6h3PR#tVgnB)##_2~Abg%eohOixCn+1X*1I0w+=*$Gc;DlHHl@%&Y^y ztWqkq@BL9#&+K}Q3C1=RytF*voy*y8&dmAFw`ch4?>Bz{z!p3yL&<@LRzWSMT-09D z9<7C}CKD!J3Hdr=VTaW;jgyGgXc#J{b&V!T!~<%$6t(Sq?1*WZs(_VY)`7bjN<>Ac zcF=ms0^`7(f3UZ=d*r~p@5@eysk#IA`*=(ru|#Taq&me>e*jqwqV4KYDh!WVL-e@j zt%z+4Ax-9jwxA7R*FnR%O)j|Ea$u&q{=$LT9T}oi%ML7`Ikl^lRP%7pskEL8+>wlW zEF^)96CP!dBvBIAi1icVR+?Q#%xS`i1@P>yyk=L)*Ay*CK&>C7#YlnUk%l2$6D~ro z(5L7Wr`k+05#S+B>!FNP5@*q#lxmu=`O__>cfPNXvVaioSvBM#w$;mNJh zfAXg{TpNCR(7nAZ4Q(>{}^mdz{^E@g3$o8_!>;G z-`j0Yu$Y1)HxSVNKBn;PkSZEau-n0)%9yEVY232(7@HJR1^24w{7jms66v>l60pRU z0u!7#JUG6Fz}!IcV(F4#c3qz5Ir%MXu01l-kpeeu*a@cf*eE|&ydm0hg4K<1VayS_ zE-b&;GTQ zt5`6)9cKDrz%sMypU{M*WCO-0G-6^e3!H-*!EpBr|4EZ03EHdXfyMI5$tE2-D1|Rdh5X)|Q+12{( za=Kmy&w+B^z6@VEaBuK(QE`>w8wZxo3u75JtPo~*`7(TuckqJGW%v;h`#g7Gai43A zLT^iH4UYhC=J=n75_|}EU@mKwVID^-*q+0ui_aN+F2ExGzJtAutPkM+4oknR-2Dw! z{>(e*u_4@-vWLX+c|u71pMX*_!;;N-zmZ8g9X08{!jR~gopAa)ZlCQ7QTZY{sH5& B$MpaJ literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Address$Builder.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Address$Builder.class new file mode 100644 index 0000000000000000000000000000000000000000..3e3364f10b3e8faa7bc113ac3d8a14e9b2fc33b1 GIT binary patch literal 2578 zcmcImZEw>^5S|TblbAxGyxtpZ%Udd;_zQfYsJgpw=s1;vpdj6SK^uFLY-{hD-F2bA z3<)Ioem@E^Ylo5`Lw)r4;&`2zXP$j#W@rC>|N1QetV6d24F-3tiNaJ!6>f`N5ylwm z4AqX(FA|JL7+R5L35Ft$4O$z(Q@nMCE*R-ZCx_H~RcILcu06YW2Y(WVW@*xb!0ingnh zU5t68(@ZA9Nv(Kh^dQ01-r_-LYm7X`C=rH?G7;^F5%QH7dou#|N&$NlC3>ZRD?HX{ zxzdiO!bM~52n=6lq}ntXY1Z?4_dLg5P3V%DOfzj9FC23x6CMfW!wrys+}VtwHlu?~ zYGZlBmvzvpjsCJ)4f+O!)20H2JIw`mdl?3udsLhXfR025ygDY~(aeO5Q~BzC@JUAnod>&UjEu2yOYbl#$-bzKFwOz~Tb5-~@v+-3@;XgvC54 zor{BaafAABGfxL-wtUGlSlZB$NS+EK{d)4T>BiDBxchBq{b%Ee#z$K?B0;^u;GZgH z|4*DYoF~p-pB;X5)De?Lo>ac)7|ad)m}T&5clA&_*V^z4gXIJHF$f;(+(dX+`UEXZ zK9>6JD zd$cofwi4l71;VpRgdhF`0#+jY2tQ46@LbDX{9M6dy8@vJ7s#gp7lC_U!lKN>CDP?8 Nt(&yE4A%;a~| zc)!!mrQz`qp+R6#hl+E=+Rle{5Q`(wyd%OB0*!nyEfh9vwnQMpCV^HZAI`|M>JtK2 zA4fqbe7+wHgce60-wgs8vQWrC6S&y^Ud!O9r8oJyIA= z71LfAFfZchgO+r85iS#083pb;rKDPdH^{=R9|8=t^ak9ej`aJ&i>c7QlE}^1o3vay z>~ZRH$72dN^_g?bx}2`Nyf&6PHJduMBz0;wb=IjXxu$3$^;zijXoz6^c^~s84Td>W zGnrO19BWfTTV}%VOBK>sFbai79cDaAk2oD#Hnvf!?s;3L#xfh9PlJ}dDIMz!6dFp( zXn4}+<HEJr~)jK$iXC`whAj%Dm0A(mv=CkvO7V}Q12nO<) zQ-;3jjU$vxF_B^NoWsOq114Zkx+ba*BG0RwX83iAwI_n^I2YPMmL~4T6q@vsa_QlK zDKG}VoM6!6#lHpQyOPum%%L^`;^DKJZyPP>od`Q ziHB2y{HKU{x?=TJX-7$a6q3{UVa0T~(nh(7moK!&Xxg}0Q4Cw`PMyJd@;jRu8DeQu z|KBv9io!IT%5VwZT80boHi0XZwrK76&LIQi0YCbln7>y-ZC$Kho zy5Vrc!wagmMi97@S8w74MAIX*otz;_z>Hstrr_g4%ywRC$uXvg#4J)XC#@LBkXq_7 z-807u0&|t>K7n$3*zVBnbpn4)c6)rN7)qSe5K=8H)uFA2X6@10*v8Bmk@3)I`|X+T zU|VOS`g94tB;@9Q=Oqg~y(?ecF+!jqI|QC)HQjtIBWa-+JzMalkfL$Z8r3iP% zzSk^eRD9nK_Cq|6X;=fSWBf#%g90qTBHk6C2uty`1ZBLIamNK%#^yK7@^_5pHB6d;}k-5DKSOWO2BiiSP+jQV5f+*vv$z!nG8_WDd6Ahykl6B#=OI#NONO4k@B?b`Lrh_zq1~()N);eSptim zNzCM+^LYOW4=sVYdqN7kM&Q~?YmdO}hKhI%8Uz+jX*ac2x(?UL{Q7qi<*0bXBO0nC z6*03yNt$Z4AM?a~MT5rfkkg2VG1Ewtvha`%IBiC}eMa+ku6cW`c{|sFfLGUG=ns#;`*Mrpf)ww;dcimZc1|5#dfM7cW-MXjwmZLybXx6mdSZaf^K)7sm2z~p8w;&O6!B6vFZ`gPlE&&Rv}ou;Fs}5i7IM%+ z8y;|N(AM6;i7*D2sXb0P%}LO>p0wn6ZWr^kL1P=EUxdO^lcii^NS=z#1AL`Lg`t^@ z_yHzE)TWOzV_ih~ZSgS`a)b`244peX%i(!*uXMowiK+#{^z5`9$9%xzzGaqg9)&#h z1@7teFNWdtq`!=uE@hE(O6|sJhHh~dalONmLWLKYzqlMUvNS^bKO_4~%N%X8G&Rjt zx;g=lvz2-kbKY<)>&;fvInm*mum1(z?tA?5*mmCLZne#iM+i5fv`#;t2%4tAioEht zdl_e)2fn4AJ&*W)=Bl|1mso`?rX!{W!|Mp5Oej*pr9JDzO3(wOrE@b^8@o8%PD?7eH&m8)anxH$( z#i5lR;7tNSoMo{s!u%M}b9hG-pFn$&C}=!1O74$u!q1A+D))|xiBZW#RYB%O$ee!I z22h6^^Y9A1N?>`VciLp+w(7bjWWhBqJ6W>N^{z`~0!uxY*?Z%}9xu+?p)dp<_NLV% zbqU;Hu3dV7sqR9Cz+Rb(R~CqZf{EKr4c;Wsn9K>>`ZkkRBz#+rgb`?fb=P}%G{W-+ zy^~1j*5MX`pDXlaCp)GQ`x8a~n;C)Y-j=&+V#%xesoQ!wUg~h0z+W$?34AL&-Kvl7 z@2gCQ{DE+Ju~>j1-wb0Q^~FFkn`!(zF4!vv>29xB3h!M#l6&L4UBj4O-j=Qd6L^P{ zc1v*0bT~XFFuyG&*Bdc2hGVpUOyKLQh5~`w} C>NqR_ literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressLine.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressLine.class new file mode 100644 index 0000000000000000000000000000000000000000..4502764e2a394759f27cb12a839de60bc7b7c3bb GIT binary patch literal 6346 zcmeHL>vG#f6h7;u@r5)AA>~#si(9~kU~_4?G^K-^gh@I{+aVz{g__7EE0$=IS zY5Gja=3~}nO_w)Sz~#PFKYHA4^QLBg;PEDN-H>bDbX6?dN^UoV^|m>A#iDR^WpX*0(Uo_c84VblOFCwV1A>&&z1>=Y}@}>5NHCWjVN%L;rL|gwvfWy zAuv&^@1Vf4a#77$0<-<)t6`|Z5gtR4PM#W}FT+2*wWrv{D)jt<%%srauYfmlZy?(tBgtz!rKLTijqY7PTV z6A?6S9J3j(XIHUUwo+clgcFW1R7U|1F(mujCb?CsQ=w@jUA~7|;Wp@Iq>YWJc$s%d zh3ulkDMROW1`(b$daf6|5Xu(_9r-|qiKq>(H# zq|`3=GjxqJmxuS6pQ`Wz^A{gi$_YQ6lKq)wj#e$lRTt^vF>wr5>P5_X&9SUk>s9MS z%VTlIi@T1 zl*2sQWuai$jv&i8A`o1fK_BL#exI9;a?wReMk0^wh02fVDyFFvtL0%cOYCEY%vUOg zCyB)Vz$H{jl>!*eLsr8TSxQm|Ik4vhL?$BqaR=S6s1d=$5Jd>vR9-$}n zjx2sm?P;M(`JPsCx8<_HSdKr@%AGkAb0$VE7v&|H^VoCzVOxP2cwrizg9QRtYA4Sq z_7E~_&r0_rf0u{bHj(XN#%A_TFR^D$>1xA+R#4|5GR!0B;g<<9{CjBYC8BM4AK^&=>tV zLXrnKbh!FWB#rR-y6g%qFvDB+a2>Z>n9!Atqf&=%c$>gaIoxoxVjRUiPrBj^rNeZ` zPD|p^$$AOu1mwS~DNb+r4-xawW&@=(>BpAp-<9A4LY|CIsf$MM)Un0*850=}=Nbmb zU3x{d1&#{`oZU+*>+FvR{F%eqdE$HMtn8c{N30H8DheI`wXl7`T&fL^hk*p9*QMm) zvd6UMx(J^VSbN%`>h)gdNTAreD#8~7gWSZVMX&?a%SG>vkX@$Lpn68x?F1~ZT5=YE0tCwNzYEBHMX|FZxu z;?=3Q+C6>oxp6 zX_M<2Xb&^d-hp>dMXP0??PQ|82k#F->sxy<1MT|^G=K&)Q?}|Oe3*eCGY}@=W3)~I bKEa5waX{cEem=vPx{dcGjG_tn9B%y$q>1&5 literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..fa76402621f96d6e4dea2caa5fa74cf6defb55c1 GIT binary patch literal 4499 zcmeHLTW=dh6h4!rvAqpVnqIh+%QQf-3#>u#fN*3fvf8H7rhz0x>f>a0;!L|cvznO= z4L<-O-uN|0Ai+C-ga3gzvuiiD>}DN15gQ19iA#9tkNeHSRk;H zQ^ZVmo7)G^cxVYM2HTGwJ=h^o4Fc8ea@`_O>ydFo;7YK?Qn$A&x{}#Ua{@oRz4c%! z#X6a>D&hJ`mK>n4=}3B~Yqe3a#dRTtZ4;QUH}(lEbX3GEuuNe2^so=KR=Nt;NoD&x ziDpvqF^_1dl2pXZ3MFZ()j`Y?^ELHqdql>Ga2ziA(8G7pWvEhu^mMFbPK;`_XJUtN$2Q-c!1KY6>(Lf(3mHJ5ginWJ)iIl=|vfF@D)CV^Y^;I!lxk=Jr1c{#xJ zSbX7o467`G)j)+T-e+34``+e)JrdY(Y|hjYoHjhz8hEZV5G)oypv*fvnY1F|kL9s2 z0>y5)vY8hRl)4-YCBs$~-XZgUPtYt3y>^*H&zp!ZbLeFS>$P6ztzZ|ELvdoj?kytl zZGF1LGu)K48bLqXVgmZ*qB*A=x=!b%Dy$LsX@b1gKSfDOI|DXBX~F3&OCx46J^y$y zrkc)&efu=_t8kA%`)vET8S$)hW8v9VnGX3w;fmeukb5MVIwg%pR OhJRJG)P48_{C@!$dC!>u literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/City$Adapter.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/City$Adapter.class new file mode 100644 index 0000000000000000000000000000000000000000..7c646e8dce5a0fe62e547f517b454a2a0263efc6 GIT binary patch literal 5024 zcmdT|TW=dh6h0e5Y;O}%0x6er8B8e&1REq?z*VF)X;mX9k(y8m-X^;z_B6XQ%gn4v z9(d-3XZ{KjNbt_j;D;d2%sP%ujj~Cswmj@@cIMpYeCN!cfB*VB0Ib7j6_^QN!|14) zav__Kd7n39Y^n_9kyPI&7Z1T=H&u)ks0FZiHus*^N>|}pu<-5Ik{GC@k1>l>nu&y4 zp(M++I!G`zU$U^a-$Q0kGGvZG!!c`YCfpbnaVeE$2gpuoG+5g*VVIDy&?HWBLcb1C zql}2bp;jqrs+)Slc_1mLD^8He+NOtBz2bH5OxnmMqglLw7$| zs3Fo!Y0H%EPO#9}6eq~>R3^nZk<3>{$%B3J-5_WtOd`=4_x=rQ-s0wRjw(_Lb zXh#^Nps26b9Xq$xeDsV6zvZLfZ@=-NJ6qf;x42EjMIQ#*^Z?NPHmx+R9z(bMTO;h$pPiwcu0$G0LJBWjvyrRYl~IZoc*z z)2FD6jB~sjPlYiKR=vK(vZ7WP*y+l&E@_Yfdjb(lwUgNrU13)W`3Oa*9CDT6z(1wp zcBGv8Ph@+DHrLYUtrVplcm3I_AI9-;+eg%06_d-(Jo-!&xJz+#G?bs}+lykIkAK2v zo+ow`!3S7fAU6T@5Vc1qg*9LJO|hISx%o>a>as}mEO<_F^M+7`g?V@pUJ77&rE}JO z6-H{g?rgC`_18|G9-!WL`6hs+j!Qs~2Ki?&IBR>t1hCn;a@W`jV1c^^@;>!qE=U5{ zog~}kIT43&=wVZV*8->wzXQ1WFqc-O*p_`^1gX5?dPraOl7?aD)ak7%+zjC767A{f zigO@mnxPrMHE+ayH8kO+J=0o!GBm32ZUBEhpSt_wfGJ@TIebs$I>P&O2taY-V8nOl z^u8y$k`oHhIF4ATC@)%_(FAwr3enmb?8bG9+vT=&y^^4YQQ8LrHP=z^B!Kz0l&F1` zf)vpo2JpqD-5o$>kd`a(5s@C%qXKJmep3ikfyUVJPBBUgwu?H!HTAS%syk@x17$3{ z2Xy2^Crm*1fU?dU-7EC2e!I5bm)Z>qwl0kZ^@#5CP<1B(8+R!HJKgFlEP8P6(C!8}E*#iFap~ompZ3 zfS2H5sDdi^&tt%QQ1r}7mNu-TjjXacQZ8w=b9~cx_q2ce^QT_`U;%EGV1mG>I`*0& z6SDb`by(BqO%-vuBh|M7_glQFSr`Sp$$UTNS~q>=)-DUPSAr=5XWg!V$yRe?>mm0H zfhoyCPT<^Hf9j5jg>22^9E&1NpuCnuJ~JFg%8`h8ASCXXS~ChqN*xv?d}EuyM*Z&? zvuh(0Y|md-J})wtCf@$ z>M<#mpQI?ciY_2KrMR>tCz-dz_iXGxItG96`Bg^ix@Q*>>)4P zJm%6v_P1jd;`=uV&iS-G%Fwobmz*!uwR#ML&!d&hlv}U76ZH~_E!&WOOk;nRChPgR3 zcFj@Wwb`1LrS)-V7z4zwIKy^+pS7p{=J{fqR}oxz!cd(=R-JtT(KJ@pXRZGw(&yV4 z1N;VEPqeYTxozl=LUzzrDMN2*XAz18qC(NC@^7KDm@T_hGt4HAcF}4O@D>a17)*IL zcRe1tq%y>8kCw#$m25XUm>@)M7#@tU_KNL`CZ^w{=YHU7;jj=Y{%^CgMd6*O0j!|4Imbv%rf1pw6>g|eNcuE z2>E?{L_IB9`vrN=$o~mND}k9z;rWO?bp6iS!&L%*6tIV5E(;G`*J2R4qmtO;x3D8h zpkgzGYgO9r?W2u63`i$jPn*E0RVjJwE?Qid;B!n$Be%0Jb?Zv(n0J4b;6|n+pX#Lq zO9Up2D!~dmblQGHO4iehj%_$eoV2)qKX<|4e7L&Mb^8VJg$mW%L44#H9{!kh3G%INK`jOzHbva~Yk zb2OWSww{aj4$KWjdp8GdGZ(E1?;U_Pfi$Liem@88>l`$I50R_XQnDJql7q05gD?pn ezZi<-Y*w_L3w3 literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CityName$Adapter.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CityName$Adapter.class new file mode 100644 index 0000000000000000000000000000000000000000..f33e106fd8cd5f016040dc258e8288c0c084789f GIT binary patch literal 3245 zcmd5;>u%gc6h5{*k#c2~Ny1Ciscd#NZKngEsq`m96b343>6viUIT(tUZ(6Ge(;(Tw}`*epGac&L)`hMCXOfgp*qF z!sua!x&4-hy`wSm6q8IC5-LP;B1XtJQ;aUC9o1?_Q?;X7?M&wS=xUlX;X86Jul|9k*qHh9aq~GiBnm;WaO|wz;&tR4E=yg=xee zmDc${RL8P2F4aVzAQ$Ao@kNBN*Gh#}LXRhCjGXJfra7&!S_v)k!j@RI!=Dp-pHn%y zu}=92-EJnV{ZEMaF$K?Oizf@8Wq(cR=}D(+AJEBTqw~wsln2LXtaq(P%B8iQOBG^$ zbn$pEO2x`fqNOM^*Cg(gqBUWOm*-?{3WdEY=N6brErZ;Oe0!$}7F9XDT$!45|3B_* z5O5JR3ZV@T}F;j_UhXv^Jp9b5Q0(G)IEE3xoB6PDJ)Z80q)Zk4tVW zErSPxg|x{&gH_=>=6%W#PZkD;^YpvgZFEa`9&H=ITMT;Vdj>auD3y~r?yHHkl1#nl z8&%b5$=1Ne;KoAw0o1r4&bQUOF4=G^)# z9e7lCGe2)p5_J#Ix`*02_y}nBrEweR$*3YN(=(vmH`N{>{2jVKt%twD%AfSL18em3 zG62`<4`8KQhwwVA(~iL#@Ftzyp!E{%Zou2L^78M{dxqPgb&dP(9NfEeao>aYUjX-m zIk?}=!CeMUx^&13o7MYcxJ7S(Pv}1+tr^^gE$Bf^dta|_z&3nNdVC3A(U%U~{uhkc B2!sFt literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CityName.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CityName.class new file mode 100644 index 0000000000000000000000000000000000000000..27c822f4b0970d7320ff87386acf7fea59d75086 GIT binary patch literal 3206 zcmd^B-)|f>5FTIB`>~`ngtkyxzzvAp(f018PXtm$QCfo1RBF>m@aEf{+&S5`x3>41 z{2l#ckU)ZW{-~d=&cxYJy4N0^{h4OLwA1ynnvD;Y}1ZI=3dwX)7+q0@#JDr$GQ zv^-N09!iB#mp{&|^B0ku#iv}Vkv>K)D1gIRhM-mH2$eujMre#I4c~NRDsfc;&2z(w zTt&byxG17nkMzY=EMO8SKFQQZIHpurfb#nJvZ=0CSr=&%yXltciQV<3H?skyiLLOX$jkEDcfgo^Fp>; wC2VV@Y+t~a?_fLgl$WsGEn!=LRr03>U%@xtFY&mE4c!#)}FD>d~+_}x%~CdZ+`&5E_@w8kHPoW#8D=s zik^tMh!Tu+j_OS5pHfVw7+I0!DMlhm3|bo{x|H27tkW5q2SqkPb9*llxkD4c3WFOZ zPAb$idN_H4v171$S1Rdt8N9N+^N7L9zD_WJ0fY6YviFS9rVrQH>d!wax=N>WOn9ub zT&BWFt$1$qB*n~r%frFR40(caDhvtbB0dvS64{oz$^2QZ?CDhdLzQ$XGgViyRF_Qdwt;*lEKnUTp7hX?dX% zJe3NQAwMEg`9PG%(hDxtT%REqEP#>mFt)ScA|tCdif7q-Nz z!ucF#`Haob&2&O`SC$X~PL0mW{PXnV%Txr5EWgiIvO0nPi#uNmr9$Y# z^)+}EUSqJiJ$~AtDtC>116v{aVeq&~%P#JuyTYsZ+W_8TFu2+?xb;J!oXqf0&83xO_MUHbWv?cy!|^30 zMt!)&;FlI%{qn?fN=cW*WpJ%@=ZU^@W(Uz4?L14IK77dF@BgP4%6Y2uI!Yus)`f}j zK>8ecW4XMSa}_b_SWcC2g&_%D^IFwZM&o*MyxR$@$BRtfC4O%nDnFP=>rlD-5{)qN z?1I7Cp;Bn}Q(-L<^FL*<-vKQc1dB2sz-M$x-L?R}ph3A(Er8+D99?~-14mU4^z$60 zSpNZ9cVAlv9|6tQG}Hq<8P%>;dIq%nrrZOBzeE4=M))hN{YhVYuue}e18{?W0Bhws zgx6t%b`&ajlTL2ZdWCj3;cZ%Z`FH4jiQA)fh5K#;?p`DAd+`1X;C|46d(eQp3Y>K5 rkr%eg_s4LX-TA!Qd`^0N3188d9^CmC%bhgI literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.class new file mode 100644 index 0000000000000000000000000000000000000000..1db5627716d36e6dc8ad2e224a3e10f1caf1ad16 GIT binary patch literal 3312 zcmds3TW=Fb6h32;*j_>t0+f3@1k~E4UH5?}f{;)Yq_hDfNRisN@$QK|WOrseGwX(j z{tA8!QY*nbKMHYX*CuweRTjyB#Dmv6J7>OgneSZQy#41D0NjPJ2@M9D)G$Zj7>PnVC7JF%NT81aEdMc{hJ~K zbTY!2M>@@9BAnEUXGZrEOzmwRw0DQdV~i4ENGKE0z8D~1k1?EaDJ)$I$1a7XOY1z= zXt~mkr@}=;?g$JIGIC@ZjG`6#2!!6ouqJeAnM^Zn9M@*RBcZ&aKJtlAGa?@QU@k&2 zR>J&Zwd(Ia&+uL>GKZ!@h&#;%cO(q(UtdkoYzu}MbKahqpeuU6KXJaXFbk~N(! zmzL)$#yzPp4*3J3n!kwRSn`}pHPZXY1qE<8ix9LwE_y8oYY9CXp)s=Za;D>63#*pU zJQdc&DzEbxI{1uD(G7J>ewUsQ0rreei~RHS#!Vdj_P)rNkE%=6k`m~nPz=D6~BB7YgFrch+8{I!zu+G?}`5E=46;R^Z6P=p~ z?@ON?XJ+eqF;MxG`R{B(9A6xXB&Q?G+e)F?OoX*)o6u$O>l~8k#<<8bXpY~S@Z-!p zTXK~2KPbD6pPVdQ10o|$(hM4~2ut*~2n$pqTJ)t&t6Q|o=s)-ug5TGcU&2?fY1M$! z^js=-R$-0amrE%)17~SPR=_GfCkPGt_Mr&p;A_%zeyryL{ZG&i?YUTC&v%vfT!t&h zqFt>(yIG0$E%5(AJM{Eg1=`OQXeZ!0`PP6N@V$=`MXf`>AIRIE3f~)a12^9R=&l0# literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/HouseNumber$Adapter.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/HouseNumber$Adapter.class new file mode 100644 index 0000000000000000000000000000000000000000..285b16a3381df5a215ccecc3d05e0fbfa342f2aa GIT binary patch literal 3351 zcmd5;>u%gc6h5;J@vfVa5J!OUu+doRjHC_+e##j)St8V$?i1vj5RY} zk{3bZNsvH-|GWt=fjDDtQb)Dat+I$Jiap~!^Ub+@=knJ-zx@FKTkvfF9R@qrByldJ zil2*#h*OMpf$CW4pE68G7+aAS8O9<_4O$zgx{}?|rNzf(K16f-ekuxwCV&M7D-}p4 z)F^&3e2$4@uy{`@>9!bL+1NZ}u<$^q7(kD~@(bB*W3=hQRkrxcQ$_daY=S9IbY93z zIH?scj2>o~+wXbUI~XHRG0B7>p+Y3bVuXA>#pr_OQLTA2)jX;-U+1Yt%awLK7cLod zM__nTkg82Ea!TY4jl7Q0jL@}aGB31oT$>S3gz^^+k)PRgLBwO9%ta{0R+v4j)$ri7 zz-fqDgt*gOa7Qv>Z#JoYXQXPgtqHYDq>+hq+?E9zilnm6l!?=Z*Sy%+m1!!r(}0+F0tgu=)6i&&r#l}JCsvLXsq{<&*5BJ>v>s;)u-!Zn=|C=a*rPo=ZWQ_ z%v>`Mri`!oPc%R0ep4vyRVh5j#GEl;GqG*^B*CJp&Mz0bCUyUhJLgZP5V~+}3EqOY z8LVv#UNo=TSAE~ymIqYK>b7_ws9!Ny9q2@4hr&p|pMG3$V`&-e4d&H*`wXrL--aJj zws^`gIBZk#(ihT2;d$6?0Pi#Co$ndk__0(@=D4dS(n>P;zHfk42Pb>O!5I_!UAV#E z=NWqa*@;)wj(PHx!PUx_2m0KTmyJ_@^JTtt;WGw*znLzmMy&Z3P743NE=__DrO%6% zMkTL?EF#jr94X;SLqBH?f7Q*SKWLW1dzWYOV4AmE#ObwN3| z9fNn_JvzBY>jm0fhYx7w~ n-nZa3y#YR_|B$q1a0fP^2Qlq^5x)+b@FnT-HGD%~I&kM-NB1*% literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.class new file mode 100644 index 0000000000000000000000000000000000000000..b32bc1a5a7894848cd07561a2ad49357aed759c2 GIT binary patch literal 3553 zcmds4TW=gS6h7Xjdl>==Y09l!+|bLWg&9D7BB-KNt(qz&5)F~y%{y~;*UgMQ*q$Z* z9X#<%kU)ZWeiY((Wi}&;HJ3fBo&pp8?=DeAIy!gHNrAqfAH@ zJrNTTB^c=()q&FArkIQ{vLeeu>HeSh}kd?7#~QR*sGD8KX@XUS#d>zf`ol zPA8b~SZBFRg_BzG+~`4ynf;gty}dE=1mjc~GRj4KAV$bH5)9|~gcYA~$|tP&Z16;* zfMLpRn5@vi8SN`^*fWsrJ}@|kQzF*QVE{TcZf&ZM=Ja7rojYIPNC3x<4N z?eQULo>VT1)Xm1hRPmMmN#=iOf7U4Il_9*qz-)M7jnsDUFvh&h&L3nfp*`Z`s70W$A^~#q-9gB2d80p8;k4tVWErZW?&RUVFymlrc zEpT_pV870(vv1Lb*BIP?c3JjSxrt;LbdN%t!Fqr5LdRJ^tMbr-HNw!S#0{y#aMRQ6aGCdT{Hhw$o5(JA{uAJu29SVCzP5=M^ literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Location.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Location.class new file mode 100644 index 0000000000000000000000000000000000000000..d01bab9a5e5a80645f499842c33419b74ea5d42a GIT binary patch literal 3043 zcmc&$TW=Fb6h7mG_(Ix1AoM~hLrSU5WtSG7ni4=!tEMsml@zJGjdv&ZklmSOX4WNr z;$Kw)seSK{s(NO}i{o^0jD=KL-iv+CH{UtmH)r?ne_s6w0Gn{T4mARIjE=&T2^l_P zLl(w7R2i51QvH_jxX(kw(k$U2i(}1=31d~nc2&fzP_hn-1Wpy136uTsVec7_EP?v( zBHL0iC$QO_Yk{Xee}h0H682z+rHFNRKCG<~0D>)jC#0~O1YWl1!zy{rm;HuG=Lv!H zKj+ellyA$SFrt_6`%)@fwAB#!u=|ryvbUuF^yfZTuXn;H&YAv&6K$U>&-rRXVPA)EG3{9@oZc?;4Q`W3WxWAwF@U9aAC89?gO|W655% z=_R+|G@M(8CVWI-t=%0D37@>-hSo|KUAmK}J+5~d&IJOiPVVFh)56`4eq6KzfkSN0 zfn6F1hgYl5?Wn^9?zKDf&E0ZpH{c?HOVbA(&N%`naoJJ@0{7=#w}ik#8Utnq{*k!Q z?rdXa-BP-Z23#TVxSwskrU#x*s(5sa zM!{6?|G(P_b+XFaoxoX?{Xd(a0RaK|{+c(-#zeC`R=JM&55n!Z56b;fJR|Gy&BQBM z$$t5PA3?h~k_}t}0t@)sSHsTu%gc6h5)G6RYQWdt<)@)_&IBz>`r3OSTo}# zc@89A1qmeh&x`ODh%@$P>!_BxRTfc2v1hzzzB!lgT>kp!w?6=27rqXl!(h*vB+i9Y z@v)eSIK@~Os7{prDZ_M(u@!ldVJyQQ$U>rWx9_E*aA*QpVQ{^I$b=fl zk4MLtI0mbCrIK!!!E4()PZ_M-*C__jW3c{A_JJ|lbm0nH{rLw)x9Dt&DNl4>$V@n? z6)%h)WtiJR3 zJS|AoCK$OS@@7U}$7n(5S~Hm!+BmMwm?uK{i$=)LZMq=hu}_yGlwvE)_g1UX@T|aD zh+2fW(_C;zGGT8qsY7R^YO}2gbwHGniFDkS1saN^vd)x=(}vf)*xupN@=~REBo(F+ ze^^@Q15q8z&bU-leS%z&11A>|!rp(2zL0^-6M8&FV`OUtn;v~NFI-UhI_58+pHJ%? z-9)EkcWo}Qa~JKCe2IQAcR3_mI!vTw3c{Sqar=>r`7R;LGxk9}(Y);iAl3 zGY4jDuenbYpYy&c6!xn0U1Cx$7_5bu_TV(ZqN>c#7r7>F|BpMDO{5UIaCHq{hieQr zw+GMKR_&?2Z)@uVDr1kze1v8ws8cc680bV~Plb_wKl`}iCekw4A1tTm_8D9izWwe| zmUy}_7`7>R$?NEva2{|Qz}pOZ^F4!`PfF!vjt6Qgtt4ab`L}DR>GBremrjYs%{qjL9_hby)1(Vvs~RJE^i(vKafc4Xnft5XoN{7XAIU3 zltObq6V@WJ{8I+Im!klKU{<~Z_>AzI76tGH&BnD>0Yn!@=lUxhcv!bJKf+NabswR1 z``SAA2xy$95gzEts4%V4Goal!)gB=H9lAemgulYtpY*i@>-6+80N3dcV69q*@CIzq zj=`Jo7MA3;0|m<4`SN;vV8+~;B(UBOZbYubl}dv!6z!Y literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Recipient.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Recipient.class new file mode 100644 index 0000000000000000000000000000000000000000..b49d336c8516c387ce22b2971fcc008854e6dbd7 GIT binary patch literal 3531 zcmds3TW{P%6h5;}_hQl}gqB+iou;L13fKj~3qcW8)k;Ark=nEpyv^EYcc-yu%*=R6 ze+NGX2_$&uM|SqEASwycSxOh^?y z6B7|780j3~ z5*adB+c|#q*cqutn+)1tNhRGDgLD0jr(}9hC)k147_1x{-#12^E?i*kpPne%S*H_B zc&xKrrou_Bcy9C{#ms)mgWld4d4h2&3>oDj-WMa}>j{Q)T*8V=IOP&nT-JG_(Q>67 z&xDJ|+z}WaY?{BYzSR>CbL`{$F&*pSSY{I0Qn)OITMe4un?mJYhk{z zS`GG|=Xf^}xkFQ9#GU4XJF*FS&8(_}Jq2o;WF%whxGi$OuU6KXB6iyFN;CZpE-f!q zfchMUWT%6JF1dWlkCpddJujYmgov+UP z6wUaw&d`l@LcA-wq>`c0S(&6>qx`56QAQ2XSRWyu!I`v{%1tR`UVU4o+Cl+eR(E_z zdMAa8B6YJlFjae{eUkW*?q`jHUYWiNOv;9b)ktag4`R&A%6ziO&C2$_*mKcB3Lt=$ zW$3^vgUkJsJb&7Po6<(r|7Mfy}2>DSYTOKvPJgRgha)`zLBb|xY% zaCgXHug;~jZqS9d89aD#Gxoi>iC7qP4-=ZfrT)gr2r{_xs8CL3xUDAAN($Ir-#W|Q zPw@(N4t2EIh4&dWnGz1aIHl?|A<&fNSlF>2Z^~I`y9?_K)Qdw{&NB=2l*TRmp%5v> zY)+|F0p8sBz6-Y)TV0$}|BuQ}3VW;z6XOHvQ}^Ognc>ml?=ld0Qzo z_flak+75g|d2_bxzB#S=l;qR94&0fWyDQPs)(@+`=Vv?%*MWpb!#;x+oQF2uwPA_M zR+paiX!SY0WpoXGh2Xoj<)7i=@3d;c>!qcCvkGhUzg$_t8*qtM&w`zR2Rr=s}Vek&TOa5J<`!=nv()A>d_i8Y1*Ydaq{8Y5-HE4Hg(Qd%a oe?U8q&<8bWU(}$TgAa*Y3;OU8T?69!G5tbf`)SF&MNy0X0=3ROZU6uP literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Street.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/Street.class new file mode 100644 index 0000000000000000000000000000000000000000..71ce1caac03d5efc91d16de4986b19256887659f GIT binary patch literal 4693 zcmeHL*>W326uqq|mNaq2#+8I6EFBR*0wb{ouo4Hy4rNn{lZs8Mpm=F(+ET}(?qRw| z#6Q3v@GVq96+H09M^W6KmZFh8)@B}f;K8%$_UXHvd*+V+`uo@40bm_A3Xmhv&{3=2 zWkS}UvL37ZysknnkEHrB;C`FeHS2}}uQT6|xYl)FCANDe;@lJJtK&T>zUAXGU4)Z=Q_z5sNU>7}-eROK#nx2+0tcV_cZu zR6Z}jiv*U21-~CfDk{Rur16-%G6g}Q>+c`GSli%gJAqwhnG4pf&4bZAxh8 zndpWpGE_xv+G3Uj^nlYrOd}>1``P?Pd&kn?dTw(^m83=;aj91^H38Pa2Bz_tJe+dd znGh33_X&{^1nx{3HOP3cw1cfLTEb8rBMq>wQky7lA(N(cf@3OV4+EhL)7cqC@Jfjz1BOlTWENXXGh}o}`IaubaS&97eYHyQTFCaOtkz}xqkM1r(ynM>3rBj0Te!;K!|D0 zlF4HNRyrH$qGSs1OvXd<1ggaX+GCMm_}-(amx+|jgq9L8{zJ?HU2$q#rH7H~p46#C z1tNaPBW=6Hs#sTOjT-0F%&i?!v$3ctiNn|5MA#_{5d%=30$aDcL;dR z0T&uYc#pvLM88Ud1><>vgSBJqi6S~?H>%$ip-#x3HAK7f*kJ z9K>}#LBlTFQ*qSdUkR&Jmz=I7UpocBrc8Z|yT}S~hd|D#0^G$maWiTfn;&JYU7XPY}Ukc)!85 zZ&zOW1z!0B?{e@O9<3d~6?h$wi^;VIZve$R6fY>_ae!d$9KxF!2(?Uvx8Uum5pHE7 zyaSa~L{;?Rv_J1=__LOYwhHf`ftGt7T`dFcZYJ6X@ZlL~!(4rof%Z59Er(V5G&5JX bG7!GcMED$I$iWxzrHus{a{<=iHmv^xg|fiv literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/StreetName$Adapter.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/StreetName$Adapter.class new file mode 100644 index 0000000000000000000000000000000000000000..c58fd3c005fd1a6f871bcef54594e606ce064cf9 GIT binary patch literal 3340 zcmd5;>u%gc6h5M;jJ=yUs-EBvTJbHOYP4Kw$8+J533mjB zrv<6n1S6kB-p zQHv0FnhWkoChRRHMUj`P&9)}gfH)%)>9{QmG!#i?ohcKi4X=5zy~CyDrAqNwDoi8( zu(ZwxqWUa5<5Era335RWoLoi-d;cx^LIy5R=+P97k*yJIdi4K!;eyK7F@FyId|Kz| zCORd%YjcSu$42K>l6sDEI-ZpsN~vQs*1O23a4xO&w46)Py3V!bBEBf|_%Sh_I4;V} zHM3yG_nQ4g@^j`lg~DEyz)MWa1;e!v*Y2MtSX8z7*)rE8?#sAy=~N1#3)k1+Rd|iT z=JxQSbJecu`_8sLq)K*B<|8ymf|?bBjiF9N_Cy%z_p`$lH<6aXV7R2d+h=fH_!j(t zGQ|^y!Q(a!uX-Qd6Q0N12JjYx-ua%vtshI}WR3$hl~$6u_k8cGnm1V+4(BxJci|R; zMcVxQi|3S$Ws;S_waS%8`rMIMj8cE+S+aEDLk52@FzCDl4^+d|Qfnp!|4^4E!3Wak z#QH@EuZAmP(V-kG;Yvflo;18wH;w+VSp@H1nZ?6d#_kfIHwVg3Cek`oZeOAiCYhWu zSQ{vXW-k-gA~F3_2H#zg1`L8(84utyx}a%O0AJ9QT&op8bZLmLPw9h)b$|229HmqD z0Il2C*1<XX_cM==Y0IrZ-L#ZV3p0TFL{L#wwJ25ENHncf-n=tsciqg`gY8+; z-+{zmK>`Wh`B8}DnGM+`Lo-#jfOy!QvFG^n`Rwmpe*68WUjX11eAa;$gDB6h5{lgrm^%W)gPds0g3&pJo_pwh3>!jMp2;lN#&K;%JQm7tG(djHY0kuBA1uTu!CIJm zR;$6@vmCb)kvlXsM%-yGxFegO*UYNA*b|_(Nk=l4j@u&l`)XyKDPpG$uQb!&;L`F! zB{-A{laSvptn(L9E=!+rsU~_Kxu63a&m#oAmy6zr;NpySCuoeUy}{Yb`EPF6(D~}l zPtc4H>kQpkC&as=ODY)}os~iAHOgsvnu;i-hG?umA|Jw;w3hNssbp_O?^UcV7V$-u z$A_eN(zqy6HyZ_0y;tfdk^iauS)-s=2JiyIvf*_#(%PMa81phWA5U|$!u>b)T)311 z2w-IyIcsjY{#?ms_+Bsz{rqbG( zh_t}nA%jPCKAn1pF1*Fy?(-|MFUU>w!k~K?)eO$}H;$hngNt7m%E=73)kIoJN4Dkb zXW0PIi3K}{Qrhgo`wYH+zGG#paJb@x)YF7OQCEP|T4mwQjc>Yejj^@GIrV=@@1(JZx-c=`l|Fn|NEXv-Q}%&Ap#R!ZQ52pnqmaS! zwo+(rr@~sa9r&2RSEmf|tJBm^kv@Ivz$bGJcqLrg`+imbe6wfaGLZPF?Pt(}v(Tou zHY`!{>e5V)MmOmzqi66l1W(qMe}vb5rBMrBFD?C-Ram3{<;n`q!Fd{y7c_r_(4x5) zL+HT;cw>riea?qBYkas?`rvnA@HV_d{#~T^HjUn;=W!nI)nHt&<#7r4iD;K=(4tzj rD{%F1(2h>%gBrA(HE3txL*mwgKHQ*ZKwLkfUr1~}ExEVo)S^ECL`FZz literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/ZipCode$Adapter.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/ZipCode$Adapter.class new file mode 100644 index 0000000000000000000000000000000000000000..3811475b05e0920edfbe199e6c762cf205025723 GIT binary patch literal 3572 zcmds4-EJF26h7mIti4T138eg#pTU%pK-dL|D;!0tN~4ClElNTqxEt@D*wgIJtY&6S z@*=zn5=d~*d+-{FGrM-WA(rYYmWsIW?9TYi`Tx#2`{&=k{tf^;@MQok2H#kd#JP|v zek#TyPBGR6s$->p$}k;bY(-vV7>hJDXlAUDuC(L1aLI@}VumLL zsoG>l4vD;xk=HSrlXb0`%nNNC*Jj8Qp}e30@~KVdNIdV8g%G7Ulg+nQtHI%Efs+u; zAmUDQ!5z_ro%yH^oRMmlZB15tlrS=pj@z<8Ly=V0nKE(O@H#IxHo3ICR4E=wg=xee zme%<|RM)aoF4b5cBNybr(RmJGXD$^MNA!4%#>kmIZrc3c6ga2ybyS{{ARc%*x{*%# z5Z&EOSo^Oy@*|3{hm_AKKB*8Cuv6tI)ya+YH=)^4XqDX}6lds&c!ZYL>aa%0M2ge3 zQ%TB^(fI|=gsUSo)uE@XW(xVg`KK~EwD1piHEtseD5T|qH0eUE4_E( zw6MMmnGLKi07W6R;o1tk0eX4d6`%owGfIoBO45GRHkN zmR6FXcYN2c#ul`$ZLYr{N781CO*B*i#gJU4;cJ4 z$9(q^%&%t3#WfuY`++V^g1ge=U}e$>t7(#w=RgjXpt4GTjT(NcdvLGcEP30P%iR71 zpF5Pp>wC)2VbVIPB7Uhi~G5i@Y8;T*4@|E!G}QK5cJgov@$A1%d`fx`>NUlgug-ihqdq* zSowoaTd+#2rvZ2auG6|wy@&7`tkI6a>vVnv*6DqTb~oTHdh_&e({swVMejA=cV_T? zFq7}Q@ZJmXeSZeu?`H5_22Q%P$P3--`4QZrC&0(_3rTARw_yW15Yyfl>Km{LpOGG) L!xwbYg4_Q9xXgUl literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/ZipCode.class b/billing-service/bin/src/main/java/de/openknowledge/sample/address/domain/ZipCode.class new file mode 100644 index 0000000000000000000000000000000000000000..015d7873be977b53c20eb4d1cf9cb632abfca15c GIT binary patch literal 3995 zcmeHKZExH}5T3mzxjPeRNn74ZVSDr?X@df(PB*A@T&{R2UMKd1p z7b?L$sW1uo{lYq*h;mzc$fX+TedK}yIGjZYx{XYjpU{I58Y63CJRQFOr^1HDS6MkB ztG@6ubVHr+0lGV>u=c4u@;%D1FDZYb_?Sm1VTa1mQKvA{GF7vq)GE79Rh*(D;UQYO zmM^Oc8EbL63My&YGdeqlOm($~#`-LHrJ1yrXsA+N?VLN@OX|ux6`V!lt=>k@htqR! znVW~`3Zr(MaG0TTLHFO5c6lVI%tev9X(AYxd_@3M=YJRfv{2A3Ic$#1)UX~J74zML z81u3{9cOwU#0jy!2%S^4I)^F>Ab``$(1s56?QLT0Z;GssW>-*C_LG2)Mfyk>>7U0h z7u--<2AkW@GlI}W(UJBmG?`EgPOYvz?!dbY?mc@-@IykA1QPfpHVn?Kt{tCP7+iQ* zC?_-AQX^?4xp&+5^l~U6?}F_~ZZ|q`k-?A8Hmn?9COb|b+a?Y)^>Qfe(9d0E2)8U3$7nt1bEueudz>mF1t|t>5XX1!w5GL^1&9;BC4t zm-hjzz*%}CMuhWI2wtv2c&7&8S}nqR@ILAJfOdL47wCUt&k@>(HE7ps(LREYPekK2 vXi+U%4?g(^w4+#Eu0gw5gLVq8l5Z`z2G@O*$k!Y6`;@%>tn|G_83_LZdou0G literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.class b/billing-service/bin/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.class new file mode 100644 index 0000000000000000000000000000000000000000..13735f505c9b6b0ed853d2eda8b799cd66672a0a GIT binary patch literal 3105 zcmds3-EY)J5TCt#T+Tg`kOD1*R!ao*sOT9`rAkeOL};i^asY8h5RV&sa#`ZrwRYD> z^4R~WQl(aE-}^^ZjP0C!7aW|ZeQ5h&ug9MG?aXiI!+-wu>+b;Y5FRw4!Qe-0Lf@fv z{u?nCeuTbGP#q}!HpXa(z7?Y+MqfmcL2La@8OKr$9~ELB%~*ywAoC{7Gg!?rVxflq zv)wlsItGiOR(o>D;8L)S7#jUnjM3jrWgMZ|U~uh4s+=5QPmQIOyD>geN;~1CR+ho# z;BjuAn*2>qWuRoW_`OupJ!CM~xwFGy{;`g*2^Scwlpbsuqs_XM8$Gf}B61EK|>B<+b|M#_PiFA6bW^3T`|P+HkQAEm)&5&qxR{~0$r|D=H%|K8qXRsC!8S#!VGLw&A&bxg{!=xAdL$i(s(1JzxGGTDN z6P!Xx?qEA8Gi+lCt}*!O46~@TsUT)LzfRfu!&c;Ua^e)H^OV7IFfCKC?V!xgEcUU8 zQ(S@@jPWx>GY$4(&sya+HHs$#|JN1)vFY>=MeED!fk89KA1EDeF%V}U@bA?T1Fx@B z6XFw@704H-{=wycCk#FbDyI^iyBADaOP&+i859$S#JSYO46X!K(`2IwU8>BoDNla? znsATyJ9(DJt(DSlTUtw{KA^d0;Rety^yA8)0SmB5Ym=mGU7~fK))shl|A?e-NR!d( z{RZvVYsK`OE;A6UH767YoiSF$}3ogSIlE@eMl(Z(eSLxjZtwGNP+GiDLcPr2U zuFs%-UWvvl(C$~DF<3t(x|Z!EN%O0bl0vEfYf>lE0$sE?xKOTBNUEgZ~aj CgwTut literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.class b/billing-service/bin/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.class new file mode 100644 index 0000000000000000000000000000000000000000..f600fedd81c29e4ea76931d81929a8d73cc523ad GIT binary patch literal 2697 zcmd5;TTc@~6h2cc+b&fQ#S7jB6e*}(AAGWeXb473(T8FaUreUElXhS`vzeJ9{4x_w z^xfZNjAyr{U0|^_A%<)^-P!%l#yu^`ag81Jo7@_loM`*vM*BBq5AN;;4yFrCPUOt!opfWXA6kixDJ zm@3zXg4N{)f&97(xC2FeH!j$`JyU9)U`qzyb{vH&iN1JIwlRYEcW{kC0GW3kIbU2c;4_ zR~eN?V#p2t|D+V=GNg51E{Vb*%`;0q*}lkkYM8W9v!=BFVu?V$>?is6B|d zTijw%hI|@Pw_3<7BWX}vE)5oT`3PmhvKj$Ar~YrssB1AIoOMPJh?!*aWPHx!CM~vnu6mS!TUTA}^VcG7zPX`;sgpX1EPDr@ zU<>zbJ5V`tDC#}MQri;7;0JIUKbHpP0VVM`2DlO&NE5g^xOKbIkY lJ41ePAfJc3iRF8J5AFlSJ%ES5Y1B3k4`30NU>P36(?6X;Qj-7x literal 0 HcmV?d00001 diff --git a/billing-service/bin/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.class b/billing-service/bin/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..ee8df62c6b3278aba3ec7dc0d6675e46f22bd7d5 GIT binary patch literal 1004 zcmb_bO>Yx15FLl5NtTqff$-6CFg=ya?u8RYl_~_HXaOk+l{h-~C~>=MFSZj}egppp z2_(4lqY$&%qNWN$J)o66JEP~B=Qrb@zrKD4fPHw-ga(6G-pMqe59zU(i&SB1OEf3O ze#}u#F!iD+b4-O&4!uv6Erd4dhz=aop#hfG>tNkk6Cws1i!`|~ll1NQ7-e9v_EZ}k z_8DB;eRRYidS(?iq0OLsfqm|rbuGBT*yneKD%pIFic4FRIu}7(!%Jt!ITrp2kK3au za)mM%jzFc5Ct`wpM`3b_ds1^x&bcQw_Z_Y*dTwmsg$Q!W10}=LlBg~xBUzG4mZajC zTrqXcOc$kffma7lxfG@1.0.0-SNAPSHOT war + + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/billing-service.git + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/billing-service.git + + online-shop 11 @@ -108,6 +113,12 @@ 2.28.2 test + + au.com.dius.pact.provider + junit5 + 4.3.5 + test + org.apache.meecrowave meecrowave-junit @@ -135,6 +146,11 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M5 + + + ${project.version} + + org.apache.maven.plugins diff --git a/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java b/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java index d63c000..d214eb1 100644 --- a/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java +++ b/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java @@ -15,8 +15,6 @@ */ package de.openknowledge.sample.address.infrastructure; -import java.io.IOException; - import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; @@ -26,10 +24,7 @@ public class CorsFilter implements ContainerResponseFilter { @Override - public void filter( - ContainerRequestContext requestContext, - ContainerResponseContext responseContext) throws IOException { - + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true"); diff --git a/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/ValidationExceptionHandler.java b/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/ValidationExceptionHandler.java new file mode 100644 index 0000000..4ece55c --- /dev/null +++ b/billing-service/src/main/java/de/openknowledge/sample/address/infrastructure/ValidationExceptionHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address.infrastructure; + +import javax.validation.ValidationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class ValidationExceptionHandler implements ExceptionMapper { + + private static final String PROBLEM_JSON_TYPE = "application/problem+json"; + private static final String PROBLEM_JSON + = "{\"type\": \"%s\", \"title\": \"%s\", \"status\": %d, \"detail\": \"%s\", \"instance\": \"%s\"}"; + + @Context + private UriInfo uri; + + @Override + public Response toResponse(ValidationException exception) { + return Response.status(Response.Status.BAD_REQUEST) + .type(PROBLEM_JSON_TYPE) + .entity(String.format( + PROBLEM_JSON, + uri.getBaseUri().resolve("/errors/invalid-" + uri.getPathSegments().get(uri.getPathSegments().size() - 1)), + "bad request", Response.Status.BAD_REQUEST.getStatusCode(), + exception.getMessage(), + uri.getAbsolutePath().toString())) + .build(); + } +} diff --git a/billing-service/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.java b/billing-service/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.java new file mode 100644 index 0000000..e7b798b --- /dev/null +++ b/billing-service/src/test/java/de/openknowledge/sample/address/BillingAddressServiceTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address; + +import org.apache.meecrowave.Meecrowave; +import org.apache.meecrowave.junit5.MonoMeecrowaveConfig; +import org.apache.meecrowave.testing.ConfigurationInject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; + +import au.com.dius.pact.provider.junit5.HttpTestTarget; +import au.com.dius.pact.provider.junit5.PactVerificationContext; +import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider; +import au.com.dius.pact.provider.junitsupport.Provider; +import au.com.dius.pact.provider.junitsupport.State; +import au.com.dius.pact.provider.junitsupport.loader.PactFolder; + +@Provider("billing-service") +@PactFolder("src/test/pacts") +@MonoMeecrowaveConfig +public class BillingAddressServiceTest { + + @ConfigurationInject + private Meecrowave.Builder config; + + @BeforeEach + public void setUp(PactVerificationContext context) { + context.setTarget(new HttpTestTarget("localhost", config.getHttpPort(), "/")); + } + + @TestTemplate + @ExtendWith(PactVerificationInvocationContextProvider.class) + void pactVerificationTestTemplate(PactVerificationContext context) { + context.verifyInteraction(); + } + + @State("Three customers") + public void toDefaultState() { + // Nothing to do here + } +} diff --git a/billing-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java b/billing-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java new file mode 100644 index 0000000..c04fb88 --- /dev/null +++ b/billing-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address; + +import java.io.InputStream; +import java.util.Map; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.assertj.core.api.Condition; + +public class JsonObjectComparision extends Condition> { + + public JsonObjectComparision(JsonObject object) { + super(v -> v.entrySet().containsAll(object.entrySet()), "object containing %s", object); + } + + public static Condition> sameAs(InputStream in) { + return new JsonObjectComparision(Json.createReader(in).readObject()); + } + + public static Condition thatIsSameAs(InputStream in) { + Condition condition = new JsonObjectComparision(Json.createReader(in).readObject()); + return (Condition)condition; + } +} diff --git a/billing-service/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.java b/billing-service/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.java new file mode 100644 index 0000000..c6d9d0b --- /dev/null +++ b/billing-service/src/test/java/de/openknowledge/sample/address/domain/TestAddressRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address.domain; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Specializes; + +@Specializes +@RequestScoped +public class TestAddressRepository extends AddressRepository { + +} diff --git a/billing-service/src/test/pacts/customer-service-billing-service.json b/billing-service/src/test/pacts/customer-service-billing-service.json new file mode 100644 index 0000000..22b29ef --- /dev/null +++ b/billing-service/src/test/pacts/customer-service-billing-service.json @@ -0,0 +1,111 @@ +{ + "provider": { + "name": "billing-service" + }, + "consumer": { + "name": "customer-service" + }, + "interactions": [ + { + "description": "GET request for 0815", + "request": { + "method": "GET", + "path": "/billing-addresses/0815" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json; charset=UTF-8" + }, + "body": { + "city": "26122 Oldenburg", + "street": { + "number": "1", + "name": "Poststr." + }, + "recipient": "Max Mustermann" + }, + "matchingRules": { + "header": { + "Content-Type": { + "matchers": [ + { + "match": "regex", + "regex": "application/json(;\\s?charset=[\\w\\-]+)?" + } + ], + "combine": "AND" + } + } + } + }, + "providerStates": [ + { + "name": "Three customers" + } + ] + }, + { + "description": "GET request for 0817", + "request": { + "method": "GET", + "path": "/billing-addresses/0817" + }, + "response": { + "status": 404 + }, + "providerStates": [ + { + "name": "Three customers" + } + ] + }, + { + "description": "POST request for 0815", + "request": { + "method": "POST", + "path": "/billing-addresses/0815", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "city": "45127 Essen", + "street": { + "number": "7", + "name": "II. Hagen" + }, + "recipient": "Erika Mustermann" + }, + "matchingRules": { + "header": { + "Content-Type": { + "matchers": [ + { + "match": "regex", + "regex": "application/json.*" + } + ], + "combine": "AND" + } + } + } + }, + "response": { + "status": 200 + }, + "providerStates": [ + { + "name": "Three customers" + } + ] + } + ], + "metadata": { + "pactSpecification": { + "version": "3.0.0" + }, + "pact-jvm": { + "version": "4.1.7" + } + } +} diff --git a/customer-service/.gitignore b/customer-service/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/customer-service/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/customer-service/Jenkinsfile b/customer-service/Jenkinsfile new file mode 100644 index 0000000..f662ba0 --- /dev/null +++ b/customer-service/Jenkinsfile @@ -0,0 +1,125 @@ +#!/usr/bin/env groovy +pipeline { + agent any + + options { + disableConcurrentBuilds() + } + + environment { + SNAPSHOT_VERSION = readMavenPom().getVersion() + LAST_COMMIT_MESSAGE = "${currentBuild.changeSets.size() == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items[currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length - 1].msg}" + PERFORM_RELEASE = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') && env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ')}" + RELEASE_VERSION = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') ? env.SNAPSHOT_VERSION.substring(0, env.SNAPSHOT_VERSION.lastIndexOf('-SNAPSHOT')) : SNAPSHOT_VERSION}" + VERSION = "${env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ') ? env.RELEASE_VERSION : env.SNAPSHOT_VERSION}" + } + + triggers { + pollSCM("* * * * *") + } + + stages { + stage ('Compile') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + echo "Building version ${env.VERSION}" + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn versions:set -DnewVersion=${env.RELEASE_VERSION} -B" + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.RELEASE_VERSION}/g' deployment/overlays/prod/kustomization.yaml" + } else { + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.GIT_COMMIT}/g' deployment/overlays/test/kustomization.yaml" + } + } + sh 'mvn clean test-compile -B' + } + } + stage ('Test') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh "mvn test -B" + } + } + stage ('Package') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh 'mvn package -DskipTests -B' + sh 'docker build -t customer .' + } + } + stage ('Push') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh """ + docker tag customer localhost:30010/customer:${env.VERSION} + docker tag customer localhost:30010/customer:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker tag customer localhost:30010/customer:${env.GIT_COMMIT} + docker push localhost:30010/customer:${env.VERSION} + docker push localhost:30010/customer:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker push localhost:30010/customer:${env.GIT_COMMIT} + """ + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh 'git config --global user.name "Jenkins"' + sh 'git config --global user.email "ci@openknowledge.de"' + sh "mvn scm:checkin -Dmessage='release of version ${env.RELEASE_VERSION}' -B" + sh "mvn scm:tag -Dtag=${env.RELEASE_VERSION} -B" + int nextRevision = Integer.parseInt(env.RELEASE_VERSION.substring(env.RELEASE_VERSION.lastIndexOf(".") + 1)) + 1 + nextVersion = RELEASE_VERSION.substring(0, env.RELEASE_VERSION.lastIndexOf(".")) + "." + nextRevision + "-SNAPSHOT" + sh "sed -i 's/${env.RELEASE_VERSION}/${nextVersion}/g' deployment/overlays/prod/kustomization.yaml" + sh "mvn versions:set scm:checkin -DnewVersion=${nextVersion} -Dmessage='update version to ${nextVersion}' -B" + } + } + } + } + stage ('Deploy') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn scm:checkout -DscmVersion=${env.RELEASE_VERSION} -DscmVersionType=tag -B" + sh 'kubectl apply -k deployment/overlays/prod' + } else { + sh 'kubectl apply -k deployment/overlays/test' + sh "sed -i 's/${env.GIT_COMMIT}/${env.SNAPSHOT_VERSION}/g' deployment/overlays/test/kustomization.yaml" + } + } + } + } + } +} diff --git a/customer-service/README.md b/customer-service/README.md new file mode 100644 index 0000000..d2f796d --- /dev/null +++ b/customer-service/README.md @@ -0,0 +1,3 @@ +# cdc-customer-service + +Customer Service to show Consumer-Driven Contracts \ No newline at end of file diff --git a/customer-service/pom.xml b/customer-service/pom.xml index 997656e..1e329e9 100644 --- a/customer-service/pom.xml +++ b/customer-service/pom.xml @@ -16,6 +16,11 @@ 1.0.0-SNAPSHOT war + + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/customer-service.git + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/customer-service.git + + online-shop 11 @@ -108,6 +113,12 @@ 2.28.2 test + + au.com.dius.pact.consumer + junit5 + 4.3.5 + test + org.apache.meecrowave meecrowave-junit @@ -124,7 +135,17 @@ com.tngtech.archunit archunit 1.0.1 - test + test + + + org.apache.geronimo.config + geronimo-config-impl + 1.2.2 + + + org.apache.geronimo.specs + geronimo-validation_1.0_spec + 1.1 @@ -135,6 +156,11 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M5 + + + ${project.version} + + org.apache.maven.plugins diff --git a/customer-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java b/customer-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java index d63c000..d214eb1 100644 --- a/customer-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java +++ b/customer-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java @@ -15,8 +15,6 @@ */ package de.openknowledge.sample.address.infrastructure; -import java.io.IOException; - import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; @@ -26,10 +24,7 @@ public class CorsFilter implements ContainerResponseFilter { @Override - public void filter( - ContainerRequestContext requestContext, - ContainerResponseContext responseContext) throws IOException { - + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true"); diff --git a/customer-service/src/test/java/de/openknowledge/sample/address/domain/BillingAddressRepositoryTest.java b/customer-service/src/test/java/de/openknowledge/sample/address/domain/BillingAddressRepositoryTest.java new file mode 100644 index 0000000..b7d329b --- /dev/null +++ b/customer-service/src/test/java/de/openknowledge/sample/address/domain/BillingAddressRepositoryTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.Optional; + +import javax.ws.rs.client.ClientBuilder; + +import org.apache.johnzon.jaxrs.jsonb.jaxrs.JsonbJaxrsProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.PactDslJsonBody; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.consumer.junit5.PactTestFor; +import au.com.dius.pact.core.model.PactSpecVersion; +import au.com.dius.pact.core.model.RequestResponsePact; +import au.com.dius.pact.core.model.annotations.Pact; +import de.openknowledge.sample.customer.domain.CustomerNumber; + +@ExtendWith(PactConsumerTestExt.class) +@PactTestFor(providerName = "billing-service", pactVersion = PactSpecVersion.V3) +public class BillingAddressRepositoryTest { + + private BillingAddressRepository repository; + + @Pact(consumer = "customer-service") + public RequestResponsePact getMax(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("GET request for 0815") + .path("/billing-addresses/0815") + .method("GET") + .willRespondWith() + .status(200) + .body(new PactDslJsonBody() + .stringValue("recipient", "Max Mustermann") + .stringValue("city", "26122 Oldenburg") + .object("street") + .stringValue("name", "Poststr.") + .stringValue("number", "1") + .closeObject()) + .toPact(); + } + + @Pact(consumer = "customer-service") + public RequestResponsePact dontGetMissing(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("GET request for 0817") + .path("/billing-addresses/0817") + .method("GET") + .willRespondWith() + .status(404) + .toPact(); + } + + @Pact(consumer = "customer-service") + public RequestResponsePact updateMax(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("POST request for 0815") + .path("/billing-addresses/0815") + .method("POST") + .matchHeader("Content-Type", "application/json.*", "application/json") + .body(new PactDslJsonBody() + .stringValue("recipient", "Erika Mustermann") + .stringValue("city", "45127 Essen") + .object("street") + .stringValue("name", "II. Hagen") + .stringValue("number", "7") + .closeObject()) + .willRespondWith() + .status(200) + .toPact(); + } + + @BeforeEach + public void initializeRepository(MockServer mockServer) { + repository = new BillingAddressRepository(); + repository.billingServiceUrl = "http://localhost:" + mockServer.getPort(); + repository.client = ClientBuilder.newClient().register(JsonbJaxrsProvider.class); + } + + @PactTestFor(pactMethod = "getMax") + @Test + public void findDeliveryAddressForExistingCustomer() { + Optional
address = repository.find(new CustomerNumber("0815")); + assertThat(address).isPresent().contains( + new Address( + new Recipient("Max Mustermann"), + new Street(new StreetName("Poststr."), new HouseNumber("1")), + new City("26122 Oldenburg"))); + } + + @PactTestFor(pactMethod = "dontGetMissing") + @Test + public void dontFindNonExistingAddress() { + Optional
address = repository.find(new CustomerNumber("0817")); + assertThat(address).isNotPresent(); + } + + @PactTestFor(pactMethod = "updateMax") + @Test + public void updateAddress() { + repository.update(new CustomerNumber("0815"), new Address( + new Recipient("Erika Mustermann"), + new Street(new StreetName("II. Hagen"), new HouseNumber("7")), + new City("45127 Essen"))); + } +} diff --git a/customer-service/src/test/java/de/openknowledge/sample/address/domain/DeliveryAddressRepositoryTest.java b/customer-service/src/test/java/de/openknowledge/sample/address/domain/DeliveryAddressRepositoryTest.java new file mode 100644 index 0000000..b5fb2b3 --- /dev/null +++ b/customer-service/src/test/java/de/openknowledge/sample/address/domain/DeliveryAddressRepositoryTest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; +import java.util.Optional; + +import javax.validation.ValidationException; +import javax.ws.rs.client.ClientBuilder; + +import org.apache.johnzon.jaxrs.jsonb.jaxrs.JsonbJaxrsProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.PactDslJsonBody; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.consumer.junit5.PactTestFor; +import au.com.dius.pact.core.model.PactSpecVersion; +import au.com.dius.pact.core.model.RequestResponsePact; +import au.com.dius.pact.core.model.annotations.Pact; +import de.openknowledge.sample.customer.domain.CustomerNumber; + +@ExtendWith(PactConsumerTestExt.class) +@PactTestFor(providerName = "delivery-service", pactVersion = PactSpecVersion.V3) +public class DeliveryAddressRepositoryTest { + + private DeliveryAddressRepository repository; + + @Pact(consumer = "customer-service") + public RequestResponsePact getMax(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("GET request for 0815") + .path("/delivery-addresses/0815") + .method("GET") + .willRespondWith() + .status(200) + .body(new PactDslJsonBody() + .stringValue("recipient", "Max Mustermann") + .stringValue("city", "26122 Oldenburg") + .object("street") + .stringValue("name", "Poststr.") + .stringValue("number", "1") + .closeObject()) + .toPact(); + } + + @Pact(consumer = "customer-service") + public RequestResponsePact dontGetMissing(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("GET request for 0817") + .path("/delivery-addresses/0817") + .method("GET") + .willRespondWith() + .status(404) + .toPact(); + } + + @Pact(consumer = "customer-service") + public RequestResponsePact updateMax(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("POST request for 0815") + .path("/delivery-addresses/0815") + .method("POST") + .matchHeader("Content-Type", "application/json.*", "application/json") + .body(new PactDslJsonBody() + .stringValue("recipient", "Erika Mustermann") + .stringValue("city", "45127 Essen") + .object("street") + .stringValue("name", "II. Hagen") + .stringValue("number", "7") + .closeObject()) + .willRespondWith() + .status(200) + .toPact(); + } + + @Pact(consumer = "customer-service") + public RequestResponsePact dontUpdateSherlock(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("POST request for 007") + .path("/delivery-addresses/007") + .method("POST") + .matchHeader("Content-Type", "application/json.*", "application/json") + .body(new PactDslJsonBody() + .stringValue("recipient", "Sherlock Holmes") + .stringValue("city", "London NW1 6XE") + .object("street") + .stringValue("name", "Baker Street") + .stringValue("number", "221B") + .closeObject()) + .willRespondWith() + .status(400) + .matchHeader("Content-Type", "application/problem\\+json.*", "application/problem+json") + .body(new PactDslJsonBody().stringMatcher("detail", ".*", "Addresses from UK are not supported for delivery")) + .toPact(); + } + + @BeforeEach + public void initializeRepository(MockServer mockServer) { + repository = new DeliveryAddressRepository(); + repository.deliveryServiceUrl = "http://localhost:" + mockServer.getPort(); + repository.client = ClientBuilder.newClient().register(JsonbJaxrsProvider.class); + } + + @PactTestFor(pactMethod = "getMax") + @Test + public void findDeliveryAddressForExistingCustomer() { + Optional
address = repository.find(new CustomerNumber("0815")); + assertThat(address).isPresent().contains( + new Address( + new Recipient("Max Mustermann"), + new Street(new StreetName("Poststr."), new HouseNumber("1")), + new City("26122 Oldenburg"))); + } + + @PactTestFor(pactMethod = "dontGetMissing") + @Test + public void dontFindNonExistingAddress() { + Optional
address = repository.find(new CustomerNumber("0817")); + assertThat(address).isNotPresent(); + } + + @PactTestFor(pactMethod = "updateMax") + @Test + public void updateAddress() { + repository.update(new CustomerNumber("0815"), new Address( + new Recipient("Erika Mustermann"), + new Street(new StreetName("II. Hagen"), new HouseNumber("7")), + new City("45127 Essen"))); + } + + @PactTestFor(pactMethod = "dontUpdateSherlock") + @Test + public void dontUpdateInvalidAddress() { + assertThatThrownBy(() -> + repository.update(new CustomerNumber("007"), new Address( + new Recipient("Sherlock Holmes"), + new Street(new StreetName("Baker Street"), new HouseNumber("221B")), + new City("London NW1 6XE")))) + .isInstanceOf(ValidationException.class) + .hasMessage("Addresses from UK are not supported for delivery"); + } +} diff --git a/customer-service/src/test/java/de/openknowledge/sample/customer/CustomerServiceTest.java b/customer-service/src/test/java/de/openknowledge/sample/customer/CustomerServiceTest.java index 5e1e785..ff32dee 100644 --- a/customer-service/src/test/java/de/openknowledge/sample/customer/CustomerServiceTest.java +++ b/customer-service/src/test/java/de/openknowledge/sample/customer/CustomerServiceTest.java @@ -71,7 +71,6 @@ public void setUp() { .thenReturn(Optional.of(Address.of("Max Mustermann").atStreet("Poststrasse 1").inCity("26122 Oldenburg").build())); when(billingAddressRepository.find(new CustomerNumber("007"))) .thenReturn(Optional.of(Address.of("Sherlock Holmes").atStreet("221B Baker Street").inCity("London NW1 6XE").build())); - uri = URI.create("http://localhost:" + config.getHttpPort()); } diff --git a/delivery-db/Dockerfile b/delivery-db/Dockerfile new file mode 100644 index 0000000..4be14b3 --- /dev/null +++ b/delivery-db/Dockerfile @@ -0,0 +1,2 @@ +FROM postgres:15.4-bullseye +COPY delivery-service/src/main/resources/sql/create.sql /docker-entrypoint-initdb.d/create.sql diff --git a/delivery-service/.gitignore b/delivery-service/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/delivery-service/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/delivery-service/Jenkinsfile b/delivery-service/Jenkinsfile new file mode 100644 index 0000000..a31ff64 --- /dev/null +++ b/delivery-service/Jenkinsfile @@ -0,0 +1,125 @@ +#!/usr/bin/env groovy +pipeline { + agent any + + options { + disableConcurrentBuilds() + } + + environment { + SNAPSHOT_VERSION = readMavenPom().getVersion() + LAST_COMMIT_MESSAGE = "${currentBuild.changeSets.size() == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length == 0 ? 'update version to ' : currentBuild.changeSets[currentBuild.changeSets.size() - 1].items[currentBuild.changeSets[currentBuild.changeSets.size() - 1].items.length - 1].msg}" + PERFORM_RELEASE = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') && env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ')}" + RELEASE_VERSION = "${env.SNAPSHOT_VERSION.contains('-SNAPSHOT') ? env.SNAPSHOT_VERSION.substring(0, env.SNAPSHOT_VERSION.lastIndexOf('-SNAPSHOT')) : SNAPSHOT_VERSION}" + VERSION = "${env.BRANCH_NAME == 'main' && !env.LAST_COMMIT_MESSAGE.startsWith('update version to ') ? env.RELEASE_VERSION : env.SNAPSHOT_VERSION}" + } + + triggers { + pollSCM("* * * * *") + } + + stages { + stage ('Compile') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + echo "Building version ${env.VERSION}" + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn versions:set -DnewVersion=${env.RELEASE_VERSION} -B" + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.RELEASE_VERSION}/g' deployment/overlays/prod/kustomization.yaml" + } else { + sh "sed -i 's/${env.SNAPSHOT_VERSION}/${env.GIT_COMMIT}/g' deployment/overlays/test/kustomization.yaml" + } + } + sh 'mvn clean test-compile -B' + } + } + stage ('Test') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh "mvn test -B" + } + } + stage ('Package') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh 'mvn package -DskipTests -B' + sh 'docker build -t delivery .' + } + } + stage ('Push') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + sh """ + docker tag delivery localhost:30010/delivery:${env.VERSION} + docker tag delivery localhost:30010/delivery:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker tag delivery localhost:30010/delivery:${env.GIT_COMMIT} + docker push localhost:30010/delivery:${env.VERSION} + docker push localhost:30010/delivery:${env.BRANCH_NAME == 'main' ? 'stable' : 'latest'} + docker push localhost:30010/delivery:${env.GIT_COMMIT} + """ + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh 'git config --global user.name "Jenkins"' + sh 'git config --global user.email "ci@openknowledge.de"' + sh "mvn scm:checkin -Dmessage='release of version ${env.RELEASE_VERSION}' -B" + sh "mvn scm:tag -Dtag=${env.RELEASE_VERSION} -B" + int nextRevision = Integer.parseInt(env.RELEASE_VERSION.substring(env.RELEASE_VERSION.lastIndexOf(".") + 1)) + 1 + nextVersion = RELEASE_VERSION.substring(0, env.RELEASE_VERSION.lastIndexOf(".")) + "." + nextRevision + "-SNAPSHOT" + sh "sed -i 's/${env.RELEASE_VERSION}/${nextVersion}/g' deployment/overlays/prod/kustomization.yaml" + sh "mvn versions:set scm:checkin -DnewVersion=${nextVersion} -Dmessage='update version to ${nextVersion}' -B" + } + } + } + } + stage ('Deploy') { + when { + anyOf { + not { + branch 'main' + } + environment name: 'PERFORM_RELEASE', value: 'true' + } + } + steps { + script { + if (env.PERFORM_RELEASE.equals('true') && !env.RELEASE_VERSION.equals(env.SNAPSHOT_VERSION)) { + sh "mvn scm:checkout -DscmVersion=${env.RELEASE_VERSION} -DscmVersionType=tag -B" + sh 'kubectl apply -k deployment/overlays/prod' + } else { + sh 'kubectl apply -k deployment/overlays/test' + sh "sed -i 's/${env.GIT_COMMIT}/${env.SNAPSHOT_VERSION}/g' deployment/overlays/test/kustomization.yaml" + } + } + } + } + } +} diff --git a/delivery-service/README.md b/delivery-service/README.md new file mode 100644 index 0000000..22324ef --- /dev/null +++ b/delivery-service/README.md @@ -0,0 +1,18 @@ +# cdc-delivery-service + +Delivery Service to show Consumer-Driven Contracts + +## Installing the database + +Please run the following commands: +``` +docker build -t delivery-db -f Postgres-Dockerfile . +docker tag delivery-db localhost:5000/delivery-db:1.0.0 +docker tag delivery-db localhost:5000/delivery-db:latest +docker push localhost:5000/delivery-db:1.0.0 +docker push localhost:5000/delivery-db:latest + +cd helm-database/delivery-db/ +helm package . +helm install delivery-db --namespace onlineshop . +helm install delivery-db --namespace onlineshop-test . diff --git a/delivery-service/namespaces.yaml b/delivery-service/namespaces.yaml new file mode 100644 index 0000000..ab5c6df --- /dev/null +++ b/delivery-service/namespaces.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: prod + labels: + name: prod +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test + labels: + name: test diff --git a/delivery-service/pom.xml b/delivery-service/pom.xml index 997656e..404c65e 100644 --- a/delivery-service/pom.xml +++ b/delivery-service/pom.xml @@ -16,6 +16,11 @@ 1.0.0-SNAPSHOT war + + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/delivery-service.git + scm:git:http://openknowledge:workshop@gogs-service:3000/openknowledge/delivery-service.git + + online-shop 11 @@ -24,6 +29,7 @@ UTF-8 1.2.13 1.9.6 + 5.4.21.Final 5.8.2 @@ -78,6 +84,26 @@ ${deltaspike.version} runtime + + org.eclipse.microprofile.config + microprofile-config-api + 1.3 + + + org.microjpa + microjpa + 1.2.2 + + + org.hibernate + hibernate-entitymanager + ${hibernate.version} + + + org.postgresql + postgresql + 42.2.5 + org.apache.commons commons-lang3 @@ -108,6 +134,30 @@ 2.28.2 test + + org.hibernate + hibernate-c3p0 + ${hibernate.version} + test + + + com.h2database + h2 + 2.1.210 + test + + + au.com.dius.pact.consumer + junit5 + 4.3.5 + test + + + au.com.dius.pact.provider + junit5 + 4.3.5 + test + org.apache.meecrowave meecrowave-junit @@ -124,7 +174,17 @@ com.tngtech.archunit archunit 1.0.1 - test + test + + + org.apache.geronimo.config + geronimo-config-impl + 1.2.2 + + + org.apache.geronimo.specs + geronimo-validation_1.0_spec + 1.1 @@ -135,6 +195,15 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M5 + + + ${project.version} + jdbc:h2:mem:delivery + org.h2.Driver + sa + sa + + org.apache.maven.plugins @@ -184,6 +253,14 @@ + + au.com.dius.pact.provider + maven + 4.3.5 + + http://localhost + + diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Address.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Address.java index f650f7f..5e26bfb 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Address.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Address.java @@ -15,19 +15,39 @@ */ package de.openknowledge.sample.address.domain; -import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.Validate.notNull; -import java.util.Objects; - import javax.json.bind.annotation.JsonbCreator; import javax.json.bind.annotation.JsonbProperty; - +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "ADDRESSES") public class Address { + + @EmbeddedId + CustomerNumber id; + @Embedded private Recipient recipient; + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "name.name", column = @Column(name = "STREET")), + @AttributeOverride(name = "number.number", column = @Column(name = "HOUSE_NUMBER")), + }) private Street street; + @Embedded private City city; + protected Address() { + // for JPA + } + @JsonbCreator public Address(@JsonbProperty("recipient") Recipient recipient) { this.recipient = notNull(recipient, "recipient may not be null"); @@ -58,49 +78,4 @@ public City getCity() { public void setCity(City city) { this.city = city; } - - @Override - public int hashCode() { - return recipient.hashCode() + ofNullable(street).map(Object::hashCode).orElse(0) + ofNullable(city).map(Object::hashCode).orElse(0); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || !getClass().equals(object.getClass())) { - return false; - } - Address address = (Address)object; - return recipient.equals(recipient) && Objects.equals(street, address.street) && Objects.equals(city, address.city); - } - - public static Builder of(String recipient) { - return new Builder(new Recipient(recipient)); - } - - public static class Builder { - - private Address address; - - private Builder(Recipient recipient) { - address = new Address(recipient); - } - - public Builder atStreet(String name) { - AddressLine addressLine = new AddressLine(name); - address.setStreet(new Street(addressLine.getStreetName(), addressLine.getHouseNumber())); - return this; - } - - public Builder inCity(String city) { - address.setCity(new City(city)); - return this; - } - - public Address build() { - return address; - } - } } diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.java index a6b5a99..03bbd2f 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/AddressRepository.java @@ -15,44 +15,28 @@ */ package de.openknowledge.sample.address.domain; -import static java.lang.String.format; +import static javax.persistence.PersistenceContextType.EXTENDED; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; -import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; -/** - * Addresses repository - */ @ApplicationScoped public class AddressRepository { - private static final Logger LOGGER = Logger.getLogger(AddressRepository.class.getSimpleName()); - - private Map addresses; - - @PostConstruct - public void initialize() { - - addresses = new ConcurrentHashMap<>(); - - addresses.put(new CustomerNumber("0815"), new Address(new Recipient("Max Mustermann"), - new Street(new StreetName("Poststr."), new HouseNumber("1")), new City("26122 Oldenburg"))); - - addresses.put(new CustomerNumber("0816"), new Address(new Recipient("Erika Mustermann"), - new Street(new StreetName("II. Hagen"), new HouseNumber("7")), new City("45127 Essen"))); - LOGGER.info(format("address repository initialized with %d addresses: ", addresses.size())); - } + @PersistenceContext(unitName = "delivery-service", type = EXTENDED) + private EntityManager entityManager; public Optional
find(CustomerNumber number) { - return Optional.ofNullable(addresses.get(number)); + return Optional.ofNullable(entityManager.find(Address.class, number)); } + @Transactional public void update(CustomerNumber number, Address address) { - Optional.ofNullable(address).ifPresent(a -> addresses.put(number, a)); + address.id = number; + entityManager.merge(address); } } diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/City.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/City.java index cc521fe..ca06740 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/City.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/City.java @@ -19,12 +19,16 @@ import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import javax.persistence.Column; +import javax.persistence.Embeddable; import de.openknowledge.sample.address.domain.City.Adapter; +@Embeddable @JsonbTypeAdapter(Adapter.class) public class City { + @Column(name = "CITY") private String name; public City(String name) { @@ -35,6 +39,10 @@ protected City() { // for framework } + public static City valueOf(String name) { + return new City(name); + } + public ZipCode getZipCode() { String firstSegment = name.substring(0, name.indexOf(' ')); String lastSegment = name.substring(name.lastIndexOf(' ') + 1); diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.java index 2cf9dec..6fa62bc 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/CustomerNumber.java @@ -17,14 +17,20 @@ import static org.apache.commons.lang3.Validate.notBlank; +import java.io.Serializable; + import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import javax.persistence.Column; +import javax.persistence.Embeddable; import de.openknowledge.sample.address.domain.CustomerNumber.Adapter; +@Embeddable @JsonbTypeAdapter(Adapter.class) -public class CustomerNumber { +public class CustomerNumber implements Serializable { + @Column(name = "ID") private String number; protected CustomerNumber() { diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.java index e54c14c..67dc3de 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/HouseNumber.java @@ -19,12 +19,13 @@ import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import javax.persistence.Embeddable; import de.openknowledge.sample.address.domain.HouseNumber.Adapter; +@Embeddable @JsonbTypeAdapter(Adapter.class) public class HouseNumber { - private String number; protected HouseNumber() { diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Recipient.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Recipient.java index cbf39d9..68908bf 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Recipient.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Recipient.java @@ -19,12 +19,16 @@ import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import javax.persistence.Column; +import javax.persistence.Embeddable; import de.openknowledge.sample.address.domain.Recipient.Adapter; +@Embeddable @JsonbTypeAdapter(Adapter.class) public class Recipient { + @Column(name = "RECIPIENT") private String name; protected Recipient() { @@ -44,7 +48,6 @@ public String toString() { return name; } - @Override public int hashCode() { return name.hashCode(); @@ -65,7 +68,6 @@ public boolean equals(Object object) { return toString().equals(recipient.toString()); } - public static class Adapter implements JsonbAdapter { @Override diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Street.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Street.java index bc0af1c..104977e 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Street.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/Street.java @@ -19,10 +19,15 @@ import javax.json.bind.annotation.JsonbCreator; import javax.json.bind.annotation.JsonbProperty; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +@Embeddable public class Street { + @Embedded private StreetName name; + @Embedded private HouseNumber number; @JsonbCreator @@ -31,6 +36,10 @@ public Street(@JsonbProperty("name") StreetName name, @JsonbProperty("number") H this.number = notNull(houseNumber, "house number may not be null"); } + protected Street() { + // for frameworks + } + public StreetName getName() { return name; } diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/StreetName.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/StreetName.java index c8bf5ee..0c31076 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/StreetName.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/StreetName.java @@ -19,9 +19,11 @@ import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import javax.persistence.Embeddable; import de.openknowledge.sample.address.domain.StreetName.Adapter; +@Embeddable @JsonbTypeAdapter(Adapter.class) public class StreetName { diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/ZipCode.java b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/ZipCode.java index 1e62563..1b43e63 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/domain/ZipCode.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/domain/ZipCode.java @@ -27,6 +27,10 @@ public class ZipCode { private String code; + protected ZipCode() { + // for frameworks + } + public ZipCode(String code) { this.code = notNull(code, "code may not be empty").trim(); } diff --git a/delivery-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java b/delivery-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java index d63c000..d214eb1 100644 --- a/delivery-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java +++ b/delivery-service/src/main/java/de/openknowledge/sample/address/infrastructure/CorsFilter.java @@ -15,8 +15,6 @@ */ package de.openknowledge.sample.address.infrastructure; -import java.io.IOException; - import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; @@ -26,10 +24,7 @@ public class CorsFilter implements ContainerResponseFilter { @Override - public void filter( - ContainerRequestContext requestContext, - ContainerResponseContext responseContext) throws IOException { - + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true"); diff --git a/delivery-service/src/main/resources/META-INF/persistence.xml b/delivery-service/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..232c692 --- /dev/null +++ b/delivery-service/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/delivery-service/src/main/resources/sql/create.sql b/delivery-service/src/main/resources/sql/create.sql new file mode 100644 index 0000000..950c434 --- /dev/null +++ b/delivery-service/src/main/resources/sql/create.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS ADDRESSES (ID VARCHAR(255) NOT NULL, RECIPIENT VARCHAR(255), STREET VARCHAR(255), HOUSE_NUMBER VARCHAR(255), CITY VARCHAR(255), PRIMARY KEY (ID)); diff --git a/delivery-service/src/test/java/de/openknowledge/sample/address/DeliveryAddressServiceTest.java b/delivery-service/src/test/java/de/openknowledge/sample/address/DeliveryAddressServiceTest.java new file mode 100644 index 0000000..91ef351 --- /dev/null +++ b/delivery-service/src/test/java/de/openknowledge/sample/address/DeliveryAddressServiceTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address; + +import static de.openknowledge.sample.infrastructure.DatabaseCleanup.with; +import static de.openknowledge.sample.infrastructure.ScriptExecutor.executeWith; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doThrow; + +import javax.enterprise.inject.Any; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Status; +import javax.transaction.UserTransaction; +import javax.validation.ValidationException; + +import org.apache.meecrowave.Meecrowave; +import org.apache.meecrowave.junit5.MonoMeecrowaveConfig; +import org.apache.meecrowave.testing.ConfigurationInject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; + +import au.com.dius.pact.provider.junit5.HttpTestTarget; +import au.com.dius.pact.provider.junit5.PactVerificationContext; +import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider; +import au.com.dius.pact.provider.junitsupport.Provider; +import au.com.dius.pact.provider.junitsupport.State; +import au.com.dius.pact.provider.junitsupport.StateChangeAction; +import au.com.dius.pact.provider.junitsupport.loader.PactFolder; +import de.openknowledge.sample.address.domain.AddressValidationService; +import rocks.limburg.cdimock.MockitoBeans; + +@MockitoBeans(types = {AddressValidationService.class}) +@Provider("delivery-service") +@PactFolder("src/test/pacts") +@MonoMeecrowaveConfig +public class DeliveryAddressServiceTest { + + @ConfigurationInject + private Meecrowave.Builder config; + + @Inject + private AddressValidationService addressValidationService; + @Inject + private UserTransaction transaction; + @Inject @Any + private EntityManager entityManager; + + @BeforeEach + public void setUp(PactVerificationContext context) { + doThrow(new ValidationException("City not found")) + .when(addressValidationService).validate(argThat(a -> a.getCity().toString().contains("London"))); + context.setTarget(new HttpTestTarget("localhost", config.getHttpPort(), "/")); + } + + @TestTemplate + @ExtendWith(PactVerificationInvocationContextProvider.class) + void pactVerificationTestTemplate(PactVerificationContext context) { + context.verifyInteraction(); + } + + @State("Three customers") + public void setThreeCustomers() throws Exception { + transaction.begin(); + executeWith(entityManager) + .script("sql/create.sql") + .script("sql/data.sql"); + transaction.commit(); + } + + @State(value = "Three customers", action = StateChangeAction.TEARDOWN) + public void cleanupThreeCustomers() throws Exception { + if (transaction.getStatus() != Status.STATUS_ACTIVE) { + transaction.begin(); + } + with(entityManager).clearDatabase(); + transaction.commit(); + } +} + diff --git a/delivery-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java b/delivery-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java new file mode 100644 index 0000000..c04fb88 --- /dev/null +++ b/delivery-service/src/test/java/de/openknowledge/sample/address/JsonObjectComparision.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address; + +import java.io.InputStream; +import java.util.Map; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonValue; + +import org.assertj.core.api.Condition; + +public class JsonObjectComparision extends Condition> { + + public JsonObjectComparision(JsonObject object) { + super(v -> v.entrySet().containsAll(object.entrySet()), "object containing %s", object); + } + + public static Condition> sameAs(InputStream in) { + return new JsonObjectComparision(Json.createReader(in).readObject()); + } + + public static Condition thatIsSameAs(InputStream in) { + Condition condition = new JsonObjectComparision(Json.createReader(in).readObject()); + return (Condition)condition; + } +} diff --git a/delivery-service/src/test/java/de/openknowledge/sample/address/domain/AddressValidationServiceTest.java b/delivery-service/src/test/java/de/openknowledge/sample/address/domain/AddressValidationServiceTest.java new file mode 100644 index 0000000..9d0d61f --- /dev/null +++ b/delivery-service/src/test/java/de/openknowledge/sample/address/domain/AddressValidationServiceTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.address.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; + +import javax.validation.ValidationException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import au.com.dius.pact.consumer.MockServer; +import au.com.dius.pact.consumer.dsl.PactDslJsonBody; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.consumer.junit5.PactTestFor; +import au.com.dius.pact.core.model.PactSpecVersion; +import au.com.dius.pact.core.model.RequestResponsePact; +import au.com.dius.pact.core.model.annotations.Pact; + +@ExtendWith(PactConsumerTestExt.class) +@PactTestFor(providerName = "address-validation-service", pactVersion = PactSpecVersion.V3) +public class AddressValidationServiceTest { + + private AddressValidationService service; + + @Pact(consumer = "delivery-service") + public RequestResponsePact validateMaxsAddress(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("POST request for Max's address") + .path("/valid-addresses") + .method("POST") + .matchHeader("Content-Type", "application/json.*", "application/json") + .body(new PactDslJsonBody() + .stringValue("recipient", "Max Mustermann") + .stringValue("city", "26122 Oldenburg") + .object("street") + .stringValue("name", "Poststrasse") + .stringValue("number", "1") + .closeObject()) + .willRespondWith() + .status(200) + .toPact(); + } + + @Pact(consumer = "delivery-service") + public RequestResponsePact validateSherlocksAddress(PactDslWithProvider builder) throws IOException { + return builder + .given("Three customers") + .uponReceiving("POST request for Sherlock's address") + .path("/valid-addresses") + .method("POST") + .matchHeader("Content-Type", "application/json.*", "application/json") + .body(new PactDslJsonBody() + .stringValue("recipient", "Sherlock Holmes") + .stringValue("city", "London NW1 6XE") + .object("street") + .stringValue("name", "Baker Street") + .stringValue("number", "221B") + .closeObject()) + .willRespondWith() + .status(400) + .matchHeader("Content-Type", "application/problem\\+json.*", "application/problem+json") + .body(new PactDslJsonBody().stringMatcher("detail", ".*", "Addresses from UK are not supported for delivery")) + .toPact(); + } + + @BeforeEach + public void initializeService(MockServer mockServer) { + service = new AddressValidationService(); + service.addressValidationServiceUrl = "http://localhost:" + mockServer.getPort(); + } + + @PactTestFor(pactMethod = "validateMaxsAddress") + @Test + public void validAddress() { + service.validate(new Address( + new Recipient("Max Mustermann"), + new Street(new StreetName("Poststrasse"), new HouseNumber("1")), + new City("26122 Oldenburg"))); + } + + @Test + @PactTestFor(pactMethod = "validateSherlocksAddress") + public void invalidAddress() { + assertThatThrownBy(() -> + service.validate(new Address( + new Recipient("Sherlock Holmes"), + new Street(new StreetName("Baker Street"), new HouseNumber("221B")), + new City("London NW1 6XE")))) + .isInstanceOf(ValidationException.class) + .hasMessage("Addresses from UK are not supported for delivery"); + } +} diff --git a/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/CdiMock.java b/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/CdiMock.java new file mode 100644 index 0000000..13aadd1 --- /dev/null +++ b/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/CdiMock.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.infrastructure; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Stereotype; + +@Stereotype +@Alternative +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD}) +public @interface CdiMock { + +} diff --git a/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/DatabaseCleanup.java b/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/DatabaseCleanup.java new file mode 100644 index 0000000..58902a6 --- /dev/null +++ b/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/DatabaseCleanup.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.infrastructure; + +import java.util.stream.Stream; + +import javax.persistence.EntityManager; +import javax.persistence.Tuple; + +public class DatabaseCleanup { + + private EntityManager entityManager; + + public DatabaseCleanup(EntityManager entityManager) { + this.entityManager = entityManager; + } + + public static DatabaseCleanup with(EntityManager entityManager) { + return new DatabaseCleanup(entityManager); + } + + public void clearDatabase() { + entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); + Stream tables = (Stream)entityManager.createNativeQuery("SHOW TABLES", Tuple.class).getResultList().stream(); + tables.map(table -> table.get(0).toString()) + .forEach(table -> entityManager.createNativeQuery("TRUNCATE TABLE " + table).executeUpdate()); + entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); + } +} diff --git a/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/ScriptExecutor.java b/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/ScriptExecutor.java new file mode 100644 index 0000000..c76ca0d --- /dev/null +++ b/delivery-service/src/test/java/de/openknowledge/sample/infrastructure/ScriptExecutor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 open knowledge GmbH + * + * 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 de.openknowledge.sample.infrastructure; + +import java.io.IOException; + +import javax.persistence.EntityManager; + +import org.apache.commons.io.IOUtils; + +public class ScriptExecutor { + + private EntityManager entityManager; + + public ScriptExecutor(EntityManager entityManager) { + this.entityManager = entityManager; + } + + public static ScriptExecutor executeWith(EntityManager entityManager) { + return new ScriptExecutor(entityManager); + } + + public ScriptExecutor script(String name) throws IOException { + String script = IOUtils.toString(Thread.currentThread().getContextClassLoader().getResourceAsStream(name)); + for (String line: script.split(";")) { + entityManager.createNativeQuery(line).executeUpdate(); + } + return this; + } +} diff --git a/delivery-service/src/test/resources/c3p0.properties b/delivery-service/src/test/resources/c3p0.properties new file mode 100644 index 0000000..06bef26 --- /dev/null +++ b/delivery-service/src/test/resources/c3p0.properties @@ -0,0 +1 @@ +c3p0.testConnectionOnCheckout=true diff --git a/delivery-service/src/test/resources/de/openknowledge/sample/address/007-invalid.json b/delivery-service/src/test/resources/de/openknowledge/sample/address/007-invalid.json new file mode 100644 index 0000000..481cc89 --- /dev/null +++ b/delivery-service/src/test/resources/de/openknowledge/sample/address/007-invalid.json @@ -0,0 +1,8 @@ +{ + "recipient": "Sherlock Holmes", + "street": { + "name": "Baker Street", + "number": "221B" + }, + "city": "London NW1 6XE" +} diff --git a/delivery-service/src/test/resources/de/openknowledge/sample/address/0815-new.json b/delivery-service/src/test/resources/de/openknowledge/sample/address/0815-new.json new file mode 100644 index 0000000..9541839 --- /dev/null +++ b/delivery-service/src/test/resources/de/openknowledge/sample/address/0815-new.json @@ -0,0 +1,8 @@ +{ + "recipient": "Erika Mustermann", + "street": { + "name": "II. Hagen", + "number": "7" + }, + "city": "45127 Essen" +} \ No newline at end of file diff --git a/delivery-service/src/test/resources/de/openknowledge/sample/address/0815.json b/delivery-service/src/test/resources/de/openknowledge/sample/address/0815.json new file mode 100644 index 0000000..84bb8d0 --- /dev/null +++ b/delivery-service/src/test/resources/de/openknowledge/sample/address/0815.json @@ -0,0 +1,8 @@ +{ + "recipient": "Max Mustermann", + "street": { + "name": "Poststraße", + "number": "1" + }, + "city": "26122 Oldenburg" +} diff --git a/delivery-service/src/test/resources/de/openknowledge/sample/address/0816-invalid.json b/delivery-service/src/test/resources/de/openknowledge/sample/address/0816-invalid.json new file mode 100644 index 0000000..58068cc --- /dev/null +++ b/delivery-service/src/test/resources/de/openknowledge/sample/address/0816-invalid.json @@ -0,0 +1,8 @@ +{ + "recipient": "Max Mustermann", + "street": { + "name": "Poststraße", + "number": "1" + }, + "city": "26122 London" +} diff --git a/delivery-service/src/test/resources/de/openknowledge/sample/address/0816.json b/delivery-service/src/test/resources/de/openknowledge/sample/address/0816.json new file mode 100644 index 0000000..9541839 --- /dev/null +++ b/delivery-service/src/test/resources/de/openknowledge/sample/address/0816.json @@ -0,0 +1,8 @@ +{ + "recipient": "Erika Mustermann", + "street": { + "name": "II. Hagen", + "number": "7" + }, + "city": "45127 Essen" +} \ No newline at end of file diff --git a/delivery-service/src/test/resources/de/openknowledge/sample/address/0817.json b/delivery-service/src/test/resources/de/openknowledge/sample/address/0817.json new file mode 100644 index 0000000..77c04eb --- /dev/null +++ b/delivery-service/src/test/resources/de/openknowledge/sample/address/0817.json @@ -0,0 +1,8 @@ +{ + "recipient": "Erika Mustermann", + "street": { + "name": "II. Hagen", + "number": "7" + }, + "city": "45127 Essen" +} diff --git a/delivery-service/src/test/resources/h2-ds.xml b/delivery-service/src/test/resources/h2-ds.xml new file mode 100644 index 0000000..b652c1e --- /dev/null +++ b/delivery-service/src/test/resources/h2-ds.xml @@ -0,0 +1,20 @@ + + + + ${database.url} + h2 + + 4 + 4 + 64 + + + sa + sa + + + diff --git a/delivery-service/src/test/resources/sql/data.sql b/delivery-service/src/test/resources/sql/data.sql new file mode 100644 index 0000000..6d35447 --- /dev/null +++ b/delivery-service/src/test/resources/sql/data.sql @@ -0,0 +1,2 @@ +INSERT INTO ADDRESSES (ID, RECIPIENT, STREET, HOUSE_NUMBER, CITY) VALUES ('0815', 'Max Mustermann', 'Poststr.', '1', '26122 Oldenburg'); +INSERT INTO ADDRESSES (ID, RECIPIENT, STREET, HOUSE_NUMBER, CITY) VALUES ('0816', 'Erika Mustermann', 'II. Hagen', '7', '45127 Essen'); diff --git a/deployment/base/gogs/deployment.yaml b/deployment/base/gogs/deployment.yaml new file mode 100644 index 0000000..c3b0b38 --- /dev/null +++ b/deployment/base/gogs/deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gogs-deployment + labels: + app: gogs-service +spec: + replicas: 1 + selector: + matchLabels: + app: gogs-service + template: + metadata: + labels: + app: gogs-service + spec: + containers: + - name: gogs + image: host.docker.internal:5000/gogs:local + ports: + - containerPort: 3000 + name: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gogs-db-deployment + labels: + app: gogs-db-service +spec: + replicas: 1 + selector: + matchLabels: + app: gogs-db-service + template: + metadata: + labels: + app: gogs-db-service + spec: + containers: + - name: gogs-postgres + image: postgres:15.4-bullseye + ports: + - containerPort: 5432 + env: + - name: POSTGRES_PASSWORD + value: g0g5db diff --git a/deployment/base/gogs/ingress.yaml b/deployment/base/gogs/ingress.yaml new file mode 100644 index 0000000..14cb82f --- /dev/null +++ b/deployment/base/gogs/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gogs-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: gogs.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gogs-service + port: + number: 30030 diff --git a/deployment/base/gogs/kustomization.yaml b/deployment/base/gogs/kustomization.yaml new file mode 100644 index 0000000..350ec31 --- /dev/null +++ b/deployment/base/gogs/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml + - ingress.yaml \ No newline at end of file diff --git a/deployment/base/gogs/service.yaml b/deployment/base/gogs/service.yaml new file mode 100644 index 0000000..10e7c6e --- /dev/null +++ b/deployment/base/gogs/service.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: gogs-service +spec: + selector: + app: gogs-service + type: NodePort + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 + nodePort: 30030 + name: service +--- +apiVersion: v1 +kind: Service +metadata: + name: gogs-db-service +spec: + selector: + app: gogs-db-service + type: NodePort + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 + name: service diff --git a/deployment/base/jenkins/deployment.yaml b/deployment/base/jenkins/deployment.yaml new file mode 100644 index 0000000..f6b3228 --- /dev/null +++ b/deployment/base/jenkins/deployment.yaml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jenkins-deployment + labels: + app: jenkins-service +spec: + replicas: 1 + selector: + matchLabels: + app: jenkins-service + template: + metadata: + labels: + app: jenkins-service + spec: + containers: + - name: jenkins + image: host.docker.internal:5000/jenkins:local + ports: + - containerPort: 8080 + name: http + volumeMounts: + - mountPath: /var/run + name: docker-sock + volumes: + - name: docker-sock + hostPath: + path: /var/run diff --git a/deployment/base/jenkins/ingress.yaml b/deployment/base/jenkins/ingress.yaml new file mode 100644 index 0000000..d73f18d --- /dev/null +++ b/deployment/base/jenkins/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: jenkins-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: jenkins.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: jenkins-service + port: + number: 30040 diff --git a/deployment/base/jenkins/kustomization.yaml b/deployment/base/jenkins/kustomization.yaml new file mode 100644 index 0000000..350ec31 --- /dev/null +++ b/deployment/base/jenkins/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml + - ingress.yaml \ No newline at end of file diff --git a/deployment/base/jenkins/service.yaml b/deployment/base/jenkins/service.yaml new file mode 100644 index 0000000..83006e4 --- /dev/null +++ b/deployment/base/jenkins/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: jenkins-service +spec: + selector: + app: jenkins-service + type: NodePort + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 30040 + name: service diff --git a/deployment/base/kustomization.yaml b/deployment/base/kustomization.yaml new file mode 100644 index 0000000..14b02c0 --- /dev/null +++ b/deployment/base/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ./namespaces.yaml + - ./nginx + - ./registry + - ./pact + - ./gogs + - ./jenkins diff --git a/deployment/base/namespaces.yaml b/deployment/base/namespaces.yaml new file mode 100644 index 0000000..ab5c6df --- /dev/null +++ b/deployment/base/namespaces.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: prod + labels: + name: prod +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test + labels: + name: test diff --git a/deployment/base/nginx/kustomization.yaml b/deployment/base/nginx/kustomization.yaml new file mode 100644 index 0000000..d603cab --- /dev/null +++ b/deployment/base/nginx/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml diff --git a/deployment/base/pact/deployment.yaml b/deployment/base/pact/deployment.yaml new file mode 100644 index 0000000..3fe5a6e --- /dev/null +++ b/deployment/base/pact/deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pact-deployment + labels: + app: pact-service +spec: + replicas: 1 + selector: + matchLabels: + app: pact-service + template: + metadata: + labels: + app: pact-service + spec: + containers: + - name: pact + image: pactfoundation/pact-broker:2.111.0-pactbroker2.107.1 + ports: + - containerPort: 8080 + name: http + env: + - name: PACT_BROKER_PORT + value: "8080" + - name: PACT_BROKER_DATABASE_ADAPTER + value: sqlite + - name: PACT_BROKER_DATABASE_NAME + value: pact_broker.sqlite + - name: PACT_BROKER_WEBHOOK_SCHEME_WHITELIST + value: http + - name: PACT_BROKER_WEBHOOK_HTTP_METHOD_WHITELIST + value: GET + - name: PACT_BROKER_WEBHOOK_HOST_WHITELIST + value: jenkins-service diff --git a/deployment/base/pact/ingress.yaml b/deployment/base/pact/ingress.yaml new file mode 100644 index 0000000..e7a8a1d --- /dev/null +++ b/deployment/base/pact/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: pact-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: pact.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: pact-service + port: + number: 30020 diff --git a/deployment/base/pact/kustomization.yaml b/deployment/base/pact/kustomization.yaml new file mode 100644 index 0000000..350ec31 --- /dev/null +++ b/deployment/base/pact/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml + - ingress.yaml \ No newline at end of file diff --git a/deployment/base/pact/service.yaml b/deployment/base/pact/service.yaml new file mode 100644 index 0000000..67e4d39 --- /dev/null +++ b/deployment/base/pact/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: pact-service +spec: + selector: + app: pact-service + type: NodePort + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 30020 + name: service diff --git a/deployment/base/registry/deployment.yaml b/deployment/base/registry/deployment.yaml new file mode 100644 index 0000000..acd8b8d --- /dev/null +++ b/deployment/base/registry/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: registry-deployment + labels: + app: registry-service +spec: + replicas: 1 + selector: + matchLabels: + app: registry-service + template: + metadata: + labels: + app: registry-service + spec: + containers: + - name: registry + image: registry:2.8.2 + ports: + - containerPort: 5000 + name: http diff --git a/deployment/base/registry/ingress.yaml b/deployment/base/registry/ingress.yaml new file mode 100644 index 0000000..650564f --- /dev/null +++ b/deployment/base/registry/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: registry-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: registry.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: registry-service + port: + number: 30010 diff --git a/deployment/base/registry/kustomization.yaml b/deployment/base/registry/kustomization.yaml new file mode 100644 index 0000000..350ec31 --- /dev/null +++ b/deployment/base/registry/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml + - ingress.yaml \ No newline at end of file diff --git a/deployment/base/registry/service.yaml b/deployment/base/registry/service.yaml new file mode 100644 index 0000000..7b341e7 --- /dev/null +++ b/deployment/base/registry/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: registry-service +spec: + selector: + app: registry-service + type: NodePort + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + nodePort: 30010 + name: service diff --git a/deployment/cluster-config/kind-config.yml b/deployment/cluster-config/kind-config.yml new file mode 100644 index 0000000..f9ab44f --- /dev/null +++ b/deployment/cluster-config/kind-config.yml @@ -0,0 +1,58 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 30010 + hostPort: 30010 + protocol: TCP + - containerPort: 30020 + hostPort: 30020 + protocol: TCP + - containerPort: 30030 + hostPort: 30030 + protocol: TCP + - containerPort: 30040 + hostPort: 30040 + protocol: TCP + - containerPort: 30060 + hostPort: 30060 + protocol: TCP + - containerPort: 30070 + hostPort: 30070 + protocol: TCP + - containerPort: 30080 + hostPort: 30080 + protocol: TCP + - containerPort: 30090 + hostPort: 30090 + protocol: TCP + - containerPort: 31060 + hostPort: 31060 + protocol: TCP + - containerPort: 31070 + hostPort: 31070 + protocol: TCP + - containerPort: 31080 + hostPort: 31080 + protocol: TCP + - containerPort: 31090 + hostPort: 31090 + protocol: TCP + extraMounts: + - hostPath: /var/run/docker.sock + containerPath: /var/run/docker.sock + - role: worker + extraMounts: + - hostPath: /var/run/docker.sock + containerPath: /var/run/docker.sock + - role: worker + extraMounts: + - hostPath: /var/run/docker.sock + containerPath: /var/run/docker.sock diff --git a/deployment/kustomization.yaml b/deployment/kustomization.yaml new file mode 100644 index 0000000..8ebfae3 --- /dev/null +++ b/deployment/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ./base + - ./setup diff --git a/deployment/setup/job.yaml b/deployment/setup/job.yaml new file mode 100644 index 0000000..118bbeb --- /dev/null +++ b/deployment/setup/job.yaml @@ -0,0 +1,12 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: setup +spec: + template: + spec: + containers: + - name: setup + image: host.docker.internal:5000/setup:local + restartPolicy: Never + backoffLimit: 1 diff --git a/deployment/setup/kustomization.yaml b/deployment/setup/kustomization.yaml new file mode 100644 index 0000000..3e6455e --- /dev/null +++ b/deployment/setup/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - job.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml index 8a0f58c..75899b3 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,3 +24,19 @@ services: build: address-validation-service/ ports: - "4003:8080" + gogs: + build: gogs/ + image: host.docker.internal:5000/gogs:local + jenkins: + build: jenkins/ + image: host.docker.internal:5000/jenkins:local + setup: + build: + context: . + dockerfile: ./setup/Dockerfile + image: host.docker.internal:5000/setup:local + delivery-db: + build: + context: . + dockerfile: ./delivery-db/Dockerfile + image: host.docker.internal:5000/delivery-db:local diff --git a/gogs/Dockerfile b/gogs/Dockerfile new file mode 100644 index 0000000..166a883 --- /dev/null +++ b/gogs/Dockerfile @@ -0,0 +1,2 @@ +FROM gogs/gogs:0.13 +COPY app.ini /data/gogs/conf/app.ini diff --git a/gogs/app.ini b/gogs/app.ini new file mode 100644 index 0000000..f8ae527 --- /dev/null +++ b/gogs/app.ini @@ -0,0 +1,15 @@ +[repository] +DEFAULT_BRANCH = main +[database] +TYPE = postgres +HOST = gogs-db-service:5432 +NAME = postgres +USER = postgres +PASSWORD = g0g5db +SCHEMA = public +SSL_MODE = disable +[auth] +REQUIRE_EMAIL_CONFIRMATION = false +ENABLE_REGISTRATION_CAPTCHA = false +[security] +INSTALL_LOCK = true diff --git a/jenkins/Dockerfile b/jenkins/Dockerfile new file mode 100644 index 0000000..5984f37 --- /dev/null +++ b/jenkins/Dockerfile @@ -0,0 +1,18 @@ +FROM jenkins/jenkins:2.414.1-lts-jdk11 + +RUN jenkins-plugin-cli -p git workflow-aggregator pipeline-utility-steps generic-webhook-trigger pipeline-stage-view + +ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false + +USER root + +# increase number of executors +COPY --chown=jenkins:jenkins executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy + +# install maven +RUN apt-get update -qq && apt-get install --no-install-recommends -y maven\ + +# install pact client + && apt-get install --no-install-recommends -y curl\ + && curl -LO https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v1.69.0/pact-1.69.0-linux-x86_64.tar.gz && tar xzf pact-1.69.0-linux-x86_64.tar.gz -C /usr/local/ && ln -s /usr/local/pact/bin/pact-broker /usr/local/bin/pact-broker\ + && rm -rf /var/lib/apt/lists/* diff --git a/jenkins/executors.groovy b/jenkins/executors.groovy new file mode 100644 index 0000000..6c8324f --- /dev/null +++ b/jenkins/executors.groovy @@ -0,0 +1,2 @@ +import jenkins.model.* +Jenkins.instance.setNumExecutors(4) \ No newline at end of file diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..9e33b5a --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,2 @@ +/.tmp/* +!.gitignore diff --git a/scripts/cleanJobs.groovy b/scripts/cleanJobs.groovy new file mode 100644 index 0000000..c928277 --- /dev/null +++ b/scripts/cleanJobs.groovy @@ -0,0 +1,9 @@ +Jenkins.instance.getItems().each { + def pipeline = it + pipeline.allJobs.each { + def job = it + job.builds.each { it.delete() } + job.nextBuildNumber = 1 + job.save() + } +} \ No newline at end of file diff --git a/scripts/cleanJobs.sh b/scripts/cleanJobs.sh new file mode 100755 index 0000000..d91d60a --- /dev/null +++ b/scripts/cleanJobs.sh @@ -0,0 +1,7 @@ +#!/bin/bash +SCRIPTS_DIR=$(readlink -f $(dirname $0)) +cd $SCRIPTS_DIR + +JENKINS_URL="http://localhost:30070" + +./jenkinsCurl.sh -X POST --data-urlencode "script@$SCRIPTS_DIR/cleanJobs.groovy" $JENKINS_URL/scriptText diff --git a/scripts/jenkinsCurl.sh b/scripts/jenkinsCurl.sh new file mode 100755 index 0000000..d24c009 --- /dev/null +++ b/scripts/jenkinsCurl.sh @@ -0,0 +1,14 @@ +#!/bin/bash +SCRIPTS_DIR=$(readlink -f $(dirname $0)) +cd $SCRIPTS_DIR + +TMP_DIR=.tmp/jenkins +mkdir -p $TMP_DIR +cd $TMP_DIR + +JENKINS_URL="http://localhost:30040" + +COOKIE_FILE=./cookiefile +CRUMB=$(curl -c $COOKIE_FILE --silent "$JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)") +curl -b ./cookiefile -H "$CRUMB" $* + diff --git a/scripts/push.sh b/scripts/push.sh new file mode 100755 index 0000000..05cda72 --- /dev/null +++ b/scripts/push.sh @@ -0,0 +1,36 @@ +#!/bin/bash +SCRIPTS_DIR=$(readlink -f $(dirname $0)) +TMP_DIR=.tmp +APPS_DIR=$SCRIPTS_DIR/../apps + +REPO_BASE_URL=localhost:30030 +JENKINS_URL="http://localhost:30040" + +REPO_NAME=$1 +TARGET_BRANCH="${2:-main}" + +# get branch name of the current repo +cd $APPS_DIR +BRANCH_NAME=$(git symbolic-ref --short HEAD) + +cd $SCRIPTS_DIR/$TMP_DIR +# clean +rm -rf ./$REPO_NAME/* +rm -drf ./$REPO_NAME/.git +mkdir -p $REPO_NAME +cd ./$REPO_NAME + +git init --initial-branch=$TARGET_BRANCH +git config user.name openknowledge +git remote add origin http://openknowledge:workshop@$REPO_BASE_URL/openknowledge/$REPO_NAME + +cp -r $APPS_DIR/$REPO_NAME/* . +cp $APPS_DIR/$REPO_NAME/.gitignore ./ + +git add . +git commit -m "copy from workshop-repo branch $BRANCH_NAME" +git push --force --set-upstream origin $TARGET_BRANCH + +# trigger build on jenkins right now +$SCRIPTS_DIR/jenkinsCurl.sh -X POST "$JENKINS_URL/job/$REPO_NAME/build" + diff --git a/scripts/pushAll.sh b/scripts/pushAll.sh new file mode 100755 index 0000000..91d2745 --- /dev/null +++ b/scripts/pushAll.sh @@ -0,0 +1,11 @@ +#!/bin/bash +SCRIPTS_DIR=$(readlink -f $(dirname $0)) + +TARGET_BRANCH="${1:-main}" + +cd $SCRIPTS_DIR +./push.sh customer-service $TARGET_BRANCH +./push.sh billing-service $TARGET_BRANCH +./push.sh address-validation-service $TARGET_BRANCH +./push.sh delivery-service $TARGET_BRANCH + diff --git a/setup/Dockerfile b/setup/Dockerfile new file mode 100644 index 0000000..050e16b --- /dev/null +++ b/setup/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine/git:2.40.1 + +RUN apk --no-cache add curl bash +COPY .git /repo/.git +COPY setup/setup.sh / +COPY setup/pushToGogs.sh / +COPY setup/jenkins/ /jenkins + +ENTRYPOINT ["sh", "/setup.sh"] diff --git a/setup/jenkins/address-validation-service/config.xml b/setup/jenkins/address-validation-service/config.xml new file mode 100644 index 0000000..2b25fb8 --- /dev/null +++ b/setup/jenkins/address-validation-service/config.xml @@ -0,0 +1,58 @@ + + + + + address-validation-service + + + + + + + + + + true + -1 + -1 + false + + + + * * * * * + 60000 + + + false + + + + + e873cb34-fb6f-4cf4-9ed3-b697a88c7f55 + http://gogs-service:3000/openknowledge/address-validation-service.git + + + + + main develop + + + + + ** + + + + + + + + + + + + + + Jenkinsfile + + \ No newline at end of file diff --git a/setup/jenkins/billing-service/config.xml b/setup/jenkins/billing-service/config.xml new file mode 100644 index 0000000..4f72231 --- /dev/null +++ b/setup/jenkins/billing-service/config.xml @@ -0,0 +1,58 @@ + + + + + billing-service + + + + + + + + + + true + -1 + -1 + false + + + + * * * * * + 60000 + + + false + + + + + e873cb34-fb6f-4cf4-9ed3-b697a88c7f55 + http://gogs-service:3000/openknowledge/billing-service.git + + + + + main develop + + + + + ** + + + + + + + + + + + + + + Jenkinsfile + + \ No newline at end of file diff --git a/setup/jenkins/create-jobs.sh b/setup/jenkins/create-jobs.sh new file mode 100755 index 0000000..069e98c --- /dev/null +++ b/setup/jenkins/create-jobs.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +JENKINS_URL="http://jenkins-service:8080" +# JENKINS_URL="http://localhost:30070" # for local testing + +#wait until jenkins is ready +curl --retry 100 -f --retry-all-errors --retry-delay 1 --silent "$JENKINS_URL/crumbIssuer/api/xml" >> /dev/null + +COOKIE_FILE=./cookiefile +CRUMB=$(curl -c $COOKIE_FILE --silent "$JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)") + +createjob() +{ + local NAME=$1 + if curl --fail --silent "$JENKINS_URL/job/$NAME"; then # check if job already exists + curl -b ./cookiefile -X POST -H "$CRUMB" "$JENKINS_URL/job/$NAME/doDelete" # delete old job + echo old job for $NAME was removed + fi + curl -b ./cookiefile -X POST -H "Content-Type:application/xml" -H "$CRUMB" -d @$NAME/config.xml "$JENKINS_URL/createItem?name=$NAME" + echo pipeline for $NAME created +} + +createjob customer-service +createjob address-validation-service +createjob billing-service +createjob delivery-service + +rm $COOKIE_FILE \ No newline at end of file diff --git a/setup/jenkins/customer-service/config.xml b/setup/jenkins/customer-service/config.xml new file mode 100644 index 0000000..f199a94 --- /dev/null +++ b/setup/jenkins/customer-service/config.xml @@ -0,0 +1,58 @@ + + + + + customer-service + + + + + + + + + + true + -1 + -1 + false + + + + * * * * * + 60000 + + + false + + + + + e873cb34-fb6f-4cf4-9ed3-c697a88c7f55 + http://gogs-service:3000/openknowledge/customer-service.git + + + + + main develop + + + + + ** + + + + + + + + + + + + + + Jenkinsfile + + \ No newline at end of file diff --git a/setup/jenkins/delivery-service/config.xml b/setup/jenkins/delivery-service/config.xml new file mode 100644 index 0000000..74498f7 --- /dev/null +++ b/setup/jenkins/delivery-service/config.xml @@ -0,0 +1,58 @@ + + + + + delivery-service + + + + + + + + + + true + -1 + -1 + false + + + + * * * * * + 60000 + + + false + + + + + e873cb34-fb6f-4cf4-9ed3-b697a88c7f55 + http://gogs-service:3000/openknowledge/delivery-service.git + + + + + main develop + + + + + ** + + + + + + + + + + + + + + Jenkinsfile + + \ No newline at end of file diff --git a/setup/pushToGogs.sh b/setup/pushToGogs.sh new file mode 100755 index 0000000..9e44690 --- /dev/null +++ b/setup/pushToGogs.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +REPO_BASE_URL=gogs-service:3000 + +BRANCHES=("pact" "pact-broker" "pipeline" "pact-tags" "webhook") +APPS=("address-validation-service" "billing-service" "customer-service" "delivery-service") + +REPO_DIR=/repo +TMP_DIR=/.tmp + +cd $REPO_DIR + +mkdir -p $TMP_DIR +for BRANCH in "${BRANCHES[@]}" +do + echo $(pwd) + git reset --hard origin/$BRANCH + rm -rf $TMP_DIR/* + cp -r $REPO_DIR/apps/* $TMP_DIR + for APP in "${APPS[@]}" + do + ( + echo "$BRANCH -> $APP" + cd $TMP_DIR/$APP + echo $(pwd) + git init --initial-branch=$BRANCH + git config user.name openknowledge + git config user.email "workshop@openknowledge.local" + git remote add origin http://openknowledge:workshop@$REPO_BASE_URL/openknowledge/$APP + git add . + git commit -m "copy from workshop-repo branch $BRANCH" + git push --force --set-upstream origin $BRANCH + ) + done +done diff --git a/setup/setup.sh b/setup/setup.sh new file mode 100755 index 0000000..6155d21 --- /dev/null +++ b/setup/setup.sh @@ -0,0 +1,50 @@ +echo "Creating Gogs user..." +curl \ + --retry 20 \ + -f \ + --retry-all-errors \ + --retry-delay 30 \ + -d "user_name=openknowledge&email=test@openknowledge.de&password=workshop&retype=workshop" \ + -X POST http://gogs-service:3000/user/sign_up \ + --silent --output /dev/null --show-error --fail +echo "Gogs user created." + +echo "Creating repositories..." + +curl \ + -f \ + -u "openknowledge:workshop" \ + -d "name=customer-service" \ + -X POST http://gogs-service:3000/api/v1/admin/users/openknowledge/repos \ + --silent --output /dev/null --show-error --fail +echo "Repository 'customer-service' created." + +curl \ + -f \ + -u "openknowledge:workshop" \ + -d "name=billing-service" \ + -X POST http://gogs-service:3000/api/v1/admin/users/openknowledge/repos \ + --silent --output /dev/null --show-error --fail +echo "Repository 'billing-service' created." + +curl \ + -f \ + -u "openknowledge:workshop" \ + -d "name=delivery-service" \ + -X POST http://gogs-service:3000/api/v1/admin/users/openknowledge/repos \ + --silent --output /dev/null --show-error --fail + +echo "Repository 'delivery-service' created." + +curl \ + -f \ + -u "openknowledge:workshop" \ + -d "name=address-validation-service" \ + -X POST http://gogs-service:3000/api/v1/admin/users/openknowledge/repos \ + --silent --output /dev/null --show-error --fail +echo "Repository 'address-validation-service' created." + +/pushToGogs.sh + +cd /jenkins +./create-jobs.sh