{"id":1377,"date":"2022-03-08T08:05:52","date_gmt":"2022-03-08T15:05:52","guid":{"rendered":"https:\/\/bullyrooks.com\/?p=1377"},"modified":"2022-03-13T13:02:06","modified_gmt":"2022-03-13T20:02:06","slug":"kube-cloud-pt6-consumer-contract-tests-for-rest-endpoints","status":"publish","type":"post","link":"https:\/\/bullyrooks.com\/index.php\/2022\/03\/08\/kube-cloud-pt6-consumer-contract-tests-for-rest-endpoints\/","title":{"rendered":"Kube Cloud Pt6 | Consumer Contract Tests for REST Endpoints"},"content":{"rendered":"\n<p>Let&#8217;s start with cloud-application, create a new branch<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git checkout -b client-contract<\/code><\/pre>\n\n\n\n<p>Lets add the necessary dependencies to our pom.xml<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>         &lt;pact.version>4.0.10&lt;\/pact.version>\r\n        &lt;pact.provider.maven.plugin>4.3.5&lt;\/pact.provider.maven.plugin>\n    &lt;\/properties>\n...\n        &lt;!-- Contract Testing -->\n        &lt;dependency>\n            &lt;groupId>au.com.dius&lt;\/groupId>\n            &lt;artifactId>pact-jvm-consumer-junit5&lt;\/artifactId>\n            &lt;version>${pact.version}&lt;\/version>\n            &lt;scope>test&lt;\/scope>\n        &lt;\/dependency>\n...\n            &lt;plugin>\n                &lt;groupId>au.com.dius.pact.provider&lt;\/groupId>\n                &lt;artifactId>maven&lt;\/artifactId>\n                &lt;version${pact.provider.maven.plugin}&lt;\/version>\n                &lt;configuration>\n                    &lt;pactBrokerUrl>${env.PACTFLOW_URL}&lt;\/pactBrokerUrl>\n                    &lt;pactBrokerToken>${env.PACTFLOW_TOKEN}&lt;\/pactBrokerToken>\n                    &lt;projectVersion>${env.PACT_PUBLISH_CONSUMER_VERSION}&lt;\/projectVersion>\n                    &lt;pactBrokerAuthenticationScheme>Bearer&lt;\/pactBrokerAuthenticationScheme>\n                &lt;\/configuration>\n            &lt;\/plugin><\/code><\/pre>\n\n\n\n<p>Pact is mostly a testing framework so these dependencies add what&#8217;s needed for testing.  The plugin allows us to access pact functions (publish, verify, etc&#8230;) from maven, which is useful in our pipeline so that we don&#8217;t have to install any additional CLI tools to our github actions workflow.<\/p>\n\n\n\n<p>Create a test class in <code>src\/test\/java\/com\/bullyrooks\/cloud_application\/message_generator.client<\/code> called <code>MessageGeneratorClientTest.<\/code>  Add this content<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>package com.bullyrooks.cloud_application.message_generator.client;\r\n\r\nimport au.com.dius.pact.consumer.dsl.PactDslWithProvider;\r\nimport au.com.dius.pact.consumer.junit5.PactConsumerTestExt;\r\nimport au.com.dius.pact.consumer.junit5.PactTestFor;\r\nimport au.com.dius.pact.core.model.RequestResponsePact;\r\nimport au.com.dius.pact.core.model.annotations.Pact;\r\nimport com.bullyrooks.cloud_application.message_generator.client.dto.MessageResponseDTO;\r\nimport com.fasterxml.jackson.databind.DeserializationFeature;\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\r\nimport feign.Contract;\r\nimport feign.Feign;\r\nimport feign.codec.Decoder;\r\nimport lombok.extern.slf4j.Slf4j;\r\nimport org.apache.commons.io.IOUtils;\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.json.JSONException;\r\nimport org.json.JSONObject;\r\nimport org.junit.jupiter.api.BeforeEach;\r\nimport org.junit.jupiter.api.Tag;\r\nimport org.junit.jupiter.api.Tags;\r\nimport org.junit.jupiter.api.Test;\r\nimport org.junit.jupiter.api.extension.ExtendWith;\r\nimport org.springframework.beans.factory.ObjectFactory;\r\nimport org.springframework.beans.factory.ObjectProvider;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.autoconfigure.http.HttpMessageConverters;\r\nimport org.springframework.boot.test.context.SpringBootTest;\r\nimport org.springframework.cloud.openfeign.support.*;\r\nimport org.springframework.context.ApplicationContext;\r\nimport org.springframework.http.HttpStatus;\r\nimport org.springframework.http.MediaType;\r\nimport org.springframework.http.codec.HttpMessageDecoder;\r\nimport org.springframework.http.converter.HttpMessageConverter;\r\nimport org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;\r\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\r\nimport org.springframework.web.client.RestTemplate;\r\n\r\nimport javax.ws.rs.HttpMethod;\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.List;\r\n\r\nimport static org.junit.jupiter.api.Assertions.assertEquals;\r\nimport static org.junit.jupiter.api.Assertions.assertTrue;\r\n\r\n@ExtendWith(PactConsumerTestExt.class)\r\n@ExtendWith(SpringExtension.class)\r\n@PactTestFor(providerName = MessageGeneratorClientTest.PROVIDER, port = MessageGeneratorClientTest.PROVIDER_PORT)\r\n@Slf4j\r\n@Tag(\"ContractTest\")\r\npublic class MessageGeneratorClientTest {\r\n    final static String PROVIDER = \"message-generator\";\r\n    final static String PROVIDER_PORT = \"8888\";\r\n    final static String CONSUMER = \"cloud-application\";\r\n    final static String JSON_RESPONSE = \"message-generator-response.json\";\r\n\r\n    private MessageGeneratorClient messageGeneratorClient;\r\n\r\n    @Autowired\r\n    ApplicationContext context;\r\n    @BeforeEach\r\n    public void init() {\r\n        messageGeneratorClient = Feign.builder()\r\n                .contract(new SpringMvcContract())\r\n                .decoder(feignDecoder())\r\n                .target(MessageGeneratorClient.class, \"http:\/\/localhost:\"+PROVIDER_PORT);\r\n        log.info(\"messageGeneratorClient: {}\", messageGeneratorClient);\r\n    }\r\n    public Decoder feignDecoder() {\r\n        List&lt;HttpMessageConverter&lt;?>> converters = new RestTemplate().getMessageConverters();\r\n        ObjectFactory&lt;HttpMessageConverters> factory = () -> new HttpMessageConverters(converters);\r\n        return new ResponseEntityDecoder(new SpringDecoder(factory, context.getBeanProvider(HttpMessageConverterCustomizer.class)));\r\n    }\r\n    @Pact(provider = MessageGeneratorClientTest.PROVIDER, consumer = MessageGeneratorClientTest.CONSUMER)\r\n    public RequestResponsePact generateMessagePact(PactDslWithProvider builder) throws JSONException, IOException {\r\n        \/\/ @formatter:off\r\n        return builder\r\n                .given(\"generator creates a message\")\r\n                .uponReceiving(\"a request to GET a message\")\r\n                .path(\"\/message\")\r\n                .method(HttpMethod.GET)\r\n                .willRespondWith()\r\n                .status(HttpStatus.OK.value())\r\n                .matchHeader(\"Content-Type\", MediaType.APPLICATION_JSON_VALUE)\r\n                .body(getJsonBody())\r\n                .toPact();\r\n        \/\/ @formatter:on\r\n    }\r\n\r\n    private JSONObject getJsonBody() throws IOException, JSONException {\r\n        File f = new File(\"src\/test\/resources\/json\/\" + JSON_RESPONSE);\r\n        InputStream is = new FileInputStream(f);\r\n        String jsonTxt = IOUtils.toString(is, \"UTF-8\");\r\n        log.info(\"test data: {}\" + jsonTxt);\r\n        JSONObject json = new JSONObject(jsonTxt);\r\n        return json;\r\n    }\r\n\r\n    @Test\r\n    @PactTestFor(pactMethod=\"generateMessagePact\")\r\n    public void generateMessage() {\r\n        MessageResponseDTO response = messageGeneratorClient.getMessage();\r\n        assertTrue(StringUtils.isNotBlank(response.getMessage()));\r\n    }\r\n}\r<\/code><\/pre>\n\n\n\n<p>we&#8217;ll also need this test data class in <code>src\/test\/resources\/json\/message-generator-response.json<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"message\": \"All dwarfs are bastards in their father's eyes\"\n}<\/code><\/pre>\n\n\n\n<p>I got this message directly from a postman request to the <code>message-generator<\/code> <code>\/message<\/code> endpoint.  So yours can be different if you prefer. <\/p>\n\n\n\n<p>Here&#8217;s what&#8217;s going on in the test:t<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The annotations are setting up the test, the important part is the <code>@PactTestFor<\/code> which is going to tell the test how to setup a mock server that our client will talk to.<\/li><li>In <code>init()<\/code> we&#8217;re going to inject a &#8216;default&#8217; feign configuration into our client.  Feign is declarative, which means that it depends on another library for the implementation.  Previously, we&#8217;ve been just relying on the kubernetes implementation (which is why you can&#8217;t make &#8216;partial message&#8217; requests locally, there&#8217;s no kubernetes for discovery).  However, we need feign to talk to our mock server, so we&#8217;re just configuring feign to point to it here. <\/li><li><code>generateMessagePact<\/code> is going to define our actual contact.  This essentially tells the mock server what endpoints it should create and listen to and how to respond when they receive requests.  We&#8217;re injecting the content of our json file here.<\/li><li>The actual test is very basic.  We just use our client to get a response and validate the structure of the data returned.  The <code>@PactTestFor<\/code> <code>pactMethod<\/code> <strong>must<\/strong> match the name of the contract method above for this to work.<\/li><li>You can apply whatever assertions you want.  This is for you.  Since you&#8217;ve already defined the data structure you expect this is mostly informational to the future developers about the types of validation you expect.  For example, if you change your test data file, it might break your test and you might want to know that.<\/li><\/ul>\n\n\n\n<p>Run the test.  It should pass.  Lets take a look at the target\/pacts directory now.  You should see a new file there: <code>cloud-application-message-generator.json<\/code>.  This is the contract file that pact uses and should be mostly human readable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pact Credentials<\/h2>\n\n\n\n<p>Log into pact and hit the gear icon to get to the settings<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"79\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.07-PM-1024x84.png?resize=960%2C79&#038;ssl=1\" alt=\"\" class=\"wp-image-1380\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.07-PM.png?resize=1024%2C84&amp;ssl=1 1024w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.07-PM.png?resize=300%2C24&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.07-PM.png?resize=768%2C63&amp;ssl=1 768w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.07-PM.png?resize=1536%2C125&amp;ssl=1 1536w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.07-PM.png?w=1581&amp;ssl=1 1581w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>On the API Tokens page get the url and the read\/write token and save them for later<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"208\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.53-PM-1024x222.png?resize=960%2C208&#038;ssl=1\" alt=\"\" class=\"wp-image-1381\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.53-PM.png?resize=1024%2C222&amp;ssl=1 1024w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.53-PM.png?resize=300%2C65&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.53-PM.png?resize=768%2C167&amp;ssl=1 768w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.53-PM.png?resize=1536%2C334&amp;ssl=1 1536w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.04.53-PM.png?w=1745&amp;ssl=1 1745w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>We&#8217;re going to add another secret to the build so, log into the github repo for your cloud-application code and go to the settings\/secrets section.  Add a new secret for PACTFLOW_TOKEN and put your read\/write token there<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"778\" height=\"77\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.08.07-PM.png?resize=778%2C77&#038;ssl=1\" alt=\"\" class=\"wp-image-1382\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.08.07-PM.png?w=778&amp;ssl=1 778w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.08.07-PM.png?resize=300%2C30&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/Screen-Shot-2022-03-02-at-2.08.07-PM.png?resize=768%2C76&amp;ssl=1 768w\" sizes=\"auto, (max-width: 778px) 100vw, 778px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Update Workflows<\/h2>\n\n\n\n<p>We&#8217;re going to update both features and main workflows in our .github\/workflows directory<\/p>\n\n\n\n<p>features.yaml<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Cloud Application Feature Branch Build\r\n\r\non:\r\n  push:\r\n    branches-ignore:\r\n      - main\r\nenv:\r\n  PACTFLOW_URL: https:\/\/bullyrooks.pactflow.io\r\n  PACTFLOW_TOKEN: ${{secrets.PACTFLOW_TOKEN}}\r\n\r\njobs:\r\n  build:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions\/checkout@v3\r\n        with:\r\n          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis\r\n      - name: Set environment variables\r\n        run: |\r\n          # Short name for current branch. For PRs, use target branch (base ref)\r\n          GIT_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs\/heads\/}}\r\n          echo \"GIT_BRANCH=$GIT_BRANCH\" >> $GITHUB_ENV\r\n          echo \"VERSION=$GITHUB_SHA\"\r\n          echo \"VERSION=$GITHUB_SHA\" >> $GITHUB_ENV\r\n\r\n      - name: Set up JDK 11\r\n        uses: actions\/setup-java@v3\r\n        with:\r\n          java-version: '11'\r\n          distribution: 'adopt'\r\n          cache: maven\r\n\r\n      - name: Maven Tests\r\n        run: .\/mvnw -B test\r\n\r\n      - name: Publish Contracts\r\n        run: |\r\n         .\/mvnw pact:publish \\\r\n         -Dpact.publish.consumer.branchName=${{env.GIT_BRANCH}} \\\r\n         -Dconsumer.version=${{env.VERSION}} \\\r\n         -Dpact.consumer.tags=development\r\n\r\n      - name: Set up Docker Buildx\r\n        uses: docker\/setup-buildx-action@v1\r\n\r\n      - name: Cache Docker layers\r\n        uses: actions\/cache@v2\r\n        with:\r\n          path: \/tmp\/.buildx-cache\r\n          # Key is named differently to avoid collision\r\n          key: ${{ runner.os }}-multi-buildx-${{ github.sha }}\r\n          restore-keys: |\r\n            ${{ runner.os }}-multi-buildx\r\n\r\n      - name: Build and push\r\n        id: docker_build\r\n        uses: docker\/build-push-action@v2\r\n        with:\r\n          context: .\r\n          push: false\r\n          cache-from: type=local,src=\/tmp\/.buildx-cache\r\n          cache-to: type=local,mode=max,dest=\/tmp\/.buildx-cache-new\r\n        # This ugly bit is necessary if you don't want your cache to grow forever\r\n        # till it hits GitHub's limit of 5GB.\r\n        # Temp fix\r\n        # https:\/\/github.com\/docker\/build-push-action\/issues\/252\r\n        # https:\/\/github.com\/moby\/buildkit\/issues\/1896\r\n      - name: Move cache\r\n        run: |\r\n          rm -rf \/tmp\/.buildx-cache\r\n          mv \/tmp\/.buildx-cache-new \/tmp\/.buildx-cache\r\n      - name: Wait for contract verification\r\n        run: |\r\n          sleep 40s\r\n\r\n      - name: Pact Can-i-deploy\r\n        run: |\r\n          docker run --rm pactfoundation\/pact-cli:latest \\\r\n          broker can-i-deploy \\\r\n          --pacticipant=cloud-application  \\\r\n          --to-environment=okteto \\\r\n          --version=${{ env.VERSION }}  \\\r\n          --broker-base-url=${{ env.PACTFLOW_URL }} \\\r\n          --broker-token=${{ env.PACTFLOW_TOKEN }}\r<\/code><\/pre>\n\n\n\n<p>We&#8217;re setting some global environment variables to connect and authenticate at pactflow.  Then we&#8217;re pulling the git branch into the version and branch name of the contract that gets published.  Then we&#8217;re leveraging the pact plugin via maven to publish the contract the test generates to pactflow.  I&#8217;ve cleaned up some of the docker build here (so we&#8217;re not pushing to canister.io) and added the can-i-deploy check which will show a build failure in our PR to show that this consumer cannot be deployed into production.<\/p>\n\n\n\n<p>We&#8217;re going to do something similar to main.yaml<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Cloud Application Main Branch Build\r\n\r\non:\r\n  push:\r\n    branches:\r\n      - main\r\n\r\nenv:\r\n  PACTFLOW_URL: https:\/\/bullyrooks.pactflow.io\r\n  PACTFLOW_TOKEN: ${{secrets.PACTFLOW_TOKEN}}\r\n\r\njobs:\r\n  build:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions\/checkout@v3\r\n        with:\r\n          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis\r\n\r\n      - name: Set up JDK 11\r\n        uses: actions\/setup-java@v3\r\n        with:\r\n          java-version: '11'\r\n          distribution: 'adopt'\r\n          cache: maven\r\n      - name: find current version\r\n        uses: actions-ecosystem\/action-get-latest-tag@v1\r\n        id: get-latest-tag\r\n\r\n      - name: get current version tag\r\n        run: |\r\n          echo \"VERSION=${{ steps.get-latest-tag.outputs.tag }}\" >> $GITHUB_ENV\r\n          echo ${{ env.VERSION }}\r\n\r\n      - name: Echo current version tag\r\n        run: |\r\n          echo $VERSION\r\n          echo ${{ env.VERSION }}\r\n\r\n      - name: Bump version\r\n        run: |\r\n          git config --global user.email \"github+actions@gmail.com\"\r\n          git config --global user.name \"Actions\"\r\n          git fetch --tags\r\n          wget -O - https:\/\/raw.githubusercontent.com\/treeder\/bump\/master\/gitbump.sh | bash\r\n          echo \"VERSION=$(git tag --sort=-v:refname --list \"v&#91;0-9]*\" | head -n 1 | cut -c 2-)\" >> $GITHUB_ENV\r\n          echo ${{ env.VERSION }}\r\n\r\n      - name: Build with Maven\r\n        run: .\/mvnw -B test\r\n\r\n      - name: Publish Pact Contract\r\n        run: |\r\n          .\/mvnw pact:publish \\\r\n          -Dpact.publish.consumer.branchName=main \\\r\n          -Dconsumer.version=${{env.VERSION}} \\\r\n          -Dpact.consumer.tags=pre-okteto\r\n\r\n      - name: Set up Docker Buildx\r\n        uses: docker\/setup-buildx-action@v1\r\n\r\n      - name: Login to canister.io\r\n        uses: docker\/login-action@v1\r\n        with:\r\n          registry: cloud.canister.io:5000\r\n          username: ${{ secrets.CANISTER_USERNAME }}\r\n          password: ${{ secrets.CANISTER_PASSWORD }}\r\n\r\n      - name: Extract metadata (tags, labels) for Docker\r\n        id: meta\r\n        uses: docker\/metadata-action@v3\r\n        with:\r\n          images: cloud.canister.io:5000\/bullyrooks\/cloud-application\r\n          #setting value manually, but could come from git tag\r\n          tags: |\r\n            type=ref,event=tag\r\n\r\n      - name: Cache Docker layers\r\n        uses: actions\/cache@v2\r\n        with:\r\n          path: \/tmp\/.buildx-cache\r\n          # Key is named differently to avoid collision\r\n          key: ${{ runner.os }}-multi-buildx-${{ github.sha }}\r\n          restore-keys: |\r\n            ${{ runner.os }}-multi-buildx\r\n\r\n      - name: Build and push\r\n        id: docker_build\r\n        uses: docker\/build-push-action@v2\r\n        with:\r\n          context: .\r\n          push: true\r\n          tags: |\r\n            cloud.canister.io:5000\/bullyrooks\/cloud-application:${{ env.VERSION }}\r\n          cache-from: type=local,src=\/tmp\/.buildx-cache\r\n          cache-to: type=local,mode=max,dest=\/tmp\/.buildx-cache-new\r\n        # This ugly bit is necessary if you don't want your cache to grow forever\r\n        # till it hits GitHub's limit of 5GB.\r\n        # Temp fix\r\n        # https:\/\/github.com\/docker\/build-push-action\/issues\/252\r\n        # https:\/\/github.com\/moby\/buildkit\/issues\/1896\r\n      - name: Move cache\r\n        run: |\r\n          rm -rf \/tmp\/.buildx-cache\r\n          mv \/tmp\/.buildx-cache-new \/tmp\/.buildx-cache\r\n\r\n      - name: Publish Helm chart\r\n        uses: stefanprodan\/helm-gh-pages@master\r\n        with:\r\n          token: ${{ secrets.CHART_TOKEN }}\r\n          charts_dir: helm\r\n          charts_url: https:\/\/bullyrooks.github.io\/helm-charts\/\r\n          repository: helm-charts\r\n          branch: gh-charts\r\n          app_version: ${{ env.VERSION }}\r\n          chart_version: ${{ env.VERSION }}\r\n\r\n      - name: Pact Can-i-deploy\r\n        run: |\r\n          docker run --rm pactfoundation\/pact-cli:latest \\\r\n          broker can-i-deploy \\\r\n          --pacticipant=cloud-application  \\\r\n          --to-environment=okteto \\\r\n          --version=${{ env.VERSION }}  \\\r\n          --broker-base-url=${{ env.PACTFLOW_URL }} \\\r\n          --broker-token=${{ env.PACTFLOW_TOKEN }}\r\n\r\n\r\n      - name: Deploy\r\n        uses: WyriHaximus\/github-action-helm3@v2\r\n        with:\r\n          exec: |\r\n            helm repo add bullyrooks https:\/\/bullyrooks.github.io\/helm-charts\/\r\n            helm repo update\r\n            echo \"helm upgrade cloud-application bullyrooks\/cloud-application --install --version ${{ env.VERSION }}\"\r\n            sleep 60s\r\n            helm repo update\r\n            helm upgrade cloud-application bullyrooks\/cloud-application --install --version ${{ env.VERSION }}\r\n          kubeconfig: '${{ secrets.KUBECONFIG }}'\r\n\r\n      - name: Create Pact Release Record\r\n        run: |\r\n          docker run --rm pactfoundation\/pact-cli:latest \\\r\n          broker record-release \\\r\n          --environment=okteto \\\r\n          --pacticipant=cloud-application \\\r\n          --version=${{ env.VERSION }} \\\r\n          --broker-base-url=${{ env.PACTFLOW_URL }} \\\r\n          --broker-token=${{ env.PACTFLOW_TOKEN }}\r\n\r\n      - name: Create Pact Deployment Record\r\n        run: |\r\n          docker run --rm pactfoundation\/pact-cli:latest \\\r\n          broker record-deployment \\\r\n          --environment=okteto \\\r\n          --pacticipant=cloud-application \\\r\n          --version=${{ env.VERSION }} \\\r\n          --broker-base-url=${{ env.PACTFLOW_URL }} \\\r\n          --broker-token=${{ env.PACTFLOW_TOKEN }}\r<\/code><\/pre>\n\n\n\n<p>Pact relies HEAVILY on a good versioning strategy.  This is mostly the same except the can-i-deploy refers to an environment and you can see that there&#8217;s some additional functionality around release and deployment after the helm deploy.  We&#8217;re going to rely on these to tell pact which versions of the consumer and provider meet their contracts before they get deployed to the correct environment (okteto)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify<\/h2>\n\n\n\n<p>That should be it.  Go ahead and push to your feature branch.  Lets go ahead and create a PR while we&#8217;re at it.  Navigate to your github repo pull requests page and you should see something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"213\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-3-1024x227.png?resize=960%2C213&#038;ssl=1\" alt=\"\" class=\"wp-image-1403\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-3.png?resize=1024%2C227&amp;ssl=1 1024w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-3.png?resize=300%2C66&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-3.png?resize=768%2C170&amp;ssl=1 768w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-3.png?w=1373&amp;ssl=1 1373w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>Click Compare &amp; pull request and you should see this<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"849\" height=\"520\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-4.png?resize=849%2C520&#038;ssl=1\" alt=\"\" class=\"wp-image-1404\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-4.png?w=849&amp;ssl=1 849w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-4.png?resize=300%2C184&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-4.png?resize=768%2C470&amp;ssl=1 768w\" sizes=\"auto, (max-width: 849px) 100vw, 849px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>Click create pull request.  You should see a failure<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"855\" height=\"508\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-5.png?resize=855%2C508&#038;ssl=1\" alt=\"\" class=\"wp-image-1405\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-5.png?w=855&amp;ssl=1 855w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-5.png?resize=300%2C178&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-5.png?resize=768%2C456&amp;ssl=1 768w\" sizes=\"auto, (max-width: 855px) 100vw, 855px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>Click Details and you should see this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"319\" height=\"659\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-6.png?resize=319%2C659&#038;ssl=1\" alt=\"\" class=\"wp-image-1406\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-6.png?w=319&amp;ssl=1 319w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-6.png?resize=145%2C300&amp;ssl=1 145w\" sizes=\"auto, (max-width: 319px) 100vw, 319px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"152\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-7-1024x162.png?resize=960%2C152&#038;ssl=1\" alt=\"\" class=\"wp-image-1407\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-7.png?resize=1024%2C162&amp;ssl=1 1024w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-7.png?resize=300%2C48&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-7.png?resize=768%2C122&amp;ssl=1 768w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-7.png?w=1205&amp;ssl=1 1205w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" data-recalc-dims=\"1\" \/><figcaption>(your error message will probably say something different)<\/figcaption><\/figure>\n\n\n\n<p>Lets go into pactflow and confirm that we&#8217;re seeing our new contract<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"361\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-8-1024x385.png?resize=960%2C361&#038;ssl=1\" alt=\"\" class=\"wp-image-1408\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-8.png?resize=1024%2C385&amp;ssl=1 1024w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-8.png?resize=300%2C113&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-8.png?resize=768%2C289&amp;ssl=1 768w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-8.png?resize=1536%2C577&amp;ssl=1 1536w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-8.png?w=1645&amp;ssl=1 1645w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>And if you click on view pact, you should see a visual representation of the contract file that we published<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"548\" src=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-9-1024x585.png?resize=960%2C548&#038;ssl=1\" alt=\"\" class=\"wp-image-1409\" srcset=\"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-9.png?resize=1024%2C585&amp;ssl=1 1024w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-9.png?resize=300%2C171&amp;ssl=1 300w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-9.png?resize=768%2C439&amp;ssl=1 768w, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-9.png?w=1296&amp;ssl=1 1296w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>Now lets go build the provider verification of the contract so we can get the contract to pass and deploy our client application.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"entry-summary\">\nLet&#8217;s start with cloud-application, create a new branch Lets add the necessary&hellip;\n<\/div>\n<div class=\"link-more\"><a href=\"https:\/\/bullyrooks.com\/index.php\/2022\/03\/08\/kube-cloud-pt6-consumer-contract-tests-for-rest-endpoints\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &ldquo;Kube Cloud Pt6 | Consumer Contract Tests for REST Endpoints&rdquo;<\/span>&hellip;<\/a><\/div>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[41],"tags":[199,147,197,198,43],"course":[196],"class_list":["post-1377","post","type-post","status-publish","format-standard","hentry","category-software-development","tag-contract-testing","tag-github-actions","tag-pact","tag-pactflow","tag-spring-boot","course-kube-cloud-pt6-contract-testing","entry"],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":1356,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/02\/27\/kube-cloud-pt5-create-an-event-consumer\/","url_meta":{"origin":1377,"position":0},"title":"Kube Cloud Pt5 | Create an Event Consumer","author":"Bullyrook","date":"February 27, 2022","format":false,"excerpt":"Now that we've got messages being published to kafka, we are going to need to build our consumer that receives those events and stores them into the mongo database. Go ahead and create a new repository called message-repository according to the microservice startup course here. This is the pom.xml that\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image-49.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image-49.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image-49.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":1069,"url":"https:\/\/bullyrooks.com\/index.php\/2021\/07\/23\/spring-boot-lambda-implementation\/","url_meta":{"origin":1377,"position":1},"title":"Spring Boot Lambda Implementation","author":"Bullyrook","date":"July 23, 2021","format":false,"excerpt":"Now we're going to add some code. I'm going to follow my ports and adapters method of building a DTO and value object that I've used previously. Yes, its a bit of overkill for this project (especially a hello world example), but if you're using this course as a springboard\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2021\/07\/image-4.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2021\/07\/image-4.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2021\/07\/image-4.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":1258,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/02\/13\/kube-cloud-pt3-rest-interaction\/","url_meta":{"origin":1377,"position":2},"title":"Kube Cloud Pt3 | REST Interaction","author":"Bullyrook","date":"February 13, 2022","format":false,"excerpt":"Now that we've got a new service, we're going to make it discoverable via kubernetes and call it from the cloud application service. Enable Kubernetes Features Let's start a new branch in our cloud_application project $ git checkout -b kube Switched to a new branch 'kube' Edit the pom.xml and\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image.png?resize=1050%2C600&ssl=1 3x"},"classes":[]},{"id":1339,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/02\/27\/kube-cloud-pt5-create-an-event-publisher\/","url_meta":{"origin":1377,"position":3},"title":"Kube Cloud Pt5 | Create an Event Publisher","author":"Bullyrook","date":"February 27, 2022","format":false,"excerpt":"We're going to be updating cloud-application to remove the mongodb repository logic and replace it with an event publisher. Create a new branch from main git checkout -b migrate Update the pom.xml first, remove the mongodb dependencies <dependency> <groupId>org.springframework.boot<\/groupId> <artifactId>spring-boot-starter-data-mongodb<\/artifactId> <\/dependency> ... <dependency> <groupId>org.testcontainers<\/groupId> <artifactId>mongodb<\/artifactId> <version>1.16.3<\/version> <scope>test<\/scope> <\/dependency> and add\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image-39.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image-39.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/02\/image-39.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":1387,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/03\/08\/kube-cloud-pt6-provider-contract-test-for-rest-endpoints\/","url_meta":{"origin":1377,"position":4},"title":"Kube Cloud Pt6 | Provider Contract Test for REST Endpoints","author":"Bullyrook","date":"March 8, 2022","format":false,"excerpt":"Now lets move over to message-generator repo and create a new branch git checkout -b provider-test Pom Updates for Dependencies Update the pom.xml to pull in both the provider test library and the plugin ... <pact.version>4.3.5<\/pact.version> <maven-failsafe-plugin.version>3.0.0-M5<\/maven-failsafe-plugin.version> <maven-surefire-plugin.version>3.0.0-M5<\/maven-surefire-plugin.version> <pact.maven.plugin.version>4.3.5<\/pact.maven.plugin.version> <\/properties> ... <profiles> <profile> <id>contract<\/id> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin<\/artifactId> <version>${maven-surefire-plugin.version}<\/version> <configuration>\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-10.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":829,"url":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-15-34e1bba8351b\/","url_meta":{"origin":1377,"position":5},"title":"Messaging and Event Driven Design","author":"Bullyrook","date":"March 30, 2020","format":false,"excerpt":"In order publish messages, we need a message broker and add some logic to use the message broker. We\u2019re going to use a cloud based SaaS service as a message broker and use spring cloud stream to interact with it. Setup the Message\u00a0Broker We\u2019ll need a message broker in order\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1377","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/comments?post=1377"}],"version-history":[{"count":6,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1377\/revisions"}],"predecessor-version":[{"id":1410,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1377\/revisions\/1410"}],"wp:attachment":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/media?parent=1377"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/categories?post=1377"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/tags?post=1377"},{"taxonomy":"course","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/course?post=1377"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}