├── .github └── workflows │ ├── build-package.yml │ └── welcome-new-users.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── SECURITY.md ├── account-service-demo-amqp ├── account-service-asyncapi-1.1.0.yml └── account-service-asyncapi-1.2.0.yml ├── account-service-demo ├── Dockerfile.bad ├── Dockerfile.good ├── account-service-asyncapi-1.0.0.yml ├── bad-client.html ├── bad-server.js ├── bin │ └── uid_entrypoint ├── good-client.html ├── good-server.js └── package.json ├── acme-lib ├── .gitignore ├── Dockerfile.acme ├── Dockerfile.acme.minion ├── README.md ├── config │ ├── application.properties │ └── features.properties ├── docker-compose-acme-async.yml ├── docker-compose-acme.yml ├── docker-compose-mount-async.yml ├── docker-compose-mount.yml ├── lib │ └── acme-lib-0.0.1-SNAPSHOT.jar ├── podman-compose-mount.yml └── src │ └── main │ ├── groovy │ └── org │ │ └── acme │ │ └── lib │ │ └── GroovyGreeting.groovy │ └── java │ └── org │ └── acme │ └── lib │ ├── CustomAuthenticateCallbackHandler.java │ └── Greeting.java ├── api-pastry-demo └── api-implementations │ ├── node-graphql-api-pastry │ ├── api-pastry-collection.json │ ├── api-pastry-metadata.yml │ ├── api-pastry.graphql │ ├── package-lock.json │ ├── package.json │ └── server.js │ └── quarkus-api-pastry │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── README.md │ ├── deployment.yml │ ├── ingress.yml │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── docker │ │ ├── Dockerfile.fast-jar │ │ ├── Dockerfile.jvm │ │ └── Dockerfile.native │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── microcks │ │ │ └── samples │ │ │ └── pastry │ │ │ ├── Pastry.java │ │ │ ├── PastryRepository.java │ │ │ └── PastryResource.java │ └── resources │ │ ├── META-INF │ │ └── resources │ │ │ └── index.html │ │ ├── application.properties │ │ └── reflection-config.json │ └── test │ └── java │ └── io │ └── github │ └── microcks │ └── samples │ └── pastry │ ├── NativePastryResourceIT.java │ └── PastryResourceTest.java ├── beer-catalog-demo ├── api-consumer │ ├── README.md │ ├── beer.css │ ├── beer.js │ ├── brew.png │ ├── config.js │ ├── config.js.example │ ├── flags.js │ └── index.html ├── api-contracts │ ├── Beer Catalog API.postman_collection.json │ └── beer-catalog-api-swagger.json ├── api-implementation │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── lbroudoux │ │ │ └── beer │ │ │ ├── Beer.java │ │ │ ├── BeerCatalogApplication.java │ │ │ ├── BeerCatalogController.java │ │ │ └── BeerRepository.java │ │ └── resources │ │ └── application.properties ├── deploy-3scale.sh ├── deploy-pipeline.sh └── pipeline-template.yml ├── beer-catalog-workshop ├── README.md ├── RUNNING.md ├── SETUP.md ├── apicast-simple-template.yaml ├── apicast-template.yaml ├── assets │ ├── api-full-lifecycle.png │ ├── jenkins-ansible-server.png │ ├── jenkins-ansible-user.png │ ├── microcks-deployment.png │ └── workshop-setup.png ├── cicd-pipeline-no-param-template.yaml ├── cicd-pipeline-template.yaml ├── deploy-envs.yaml ├── documentation.html └── env-template.yaml ├── cloud-events-demo ├── animal-message-payload.avsc ├── animal-sensor-1.0.0-bin-asyncapi.yaml ├── animal-sensor-1.0.0-struct-asyncapi.yaml └── animal-sensor-structured-ce.json ├── contract-testing-demo-async ├── .gitignore ├── Dockerfile-01 ├── Dockerfile-02 ├── bin │ ├── uid_entrypoint_01 │ └── uid_entrypoint_02 ├── package-lock.json ├── package.json ├── pastry-orders-asyncapi.yml ├── server-01.js └── server-02.js ├── contract-testing-demo ├── .gitignore ├── Dockerfile-01 ├── Dockerfile-02 ├── Dockerfile-03 ├── apipastries-openapi.yaml ├── apipastries-postman-collection.json ├── bin │ ├── uid_entrypoint_01 │ ├── uid_entrypoint_02 │ └── uid_entrypoint_03 ├── deployment.yml ├── kube-deployment-01.yml ├── kube-deployment-02.yml ├── kube-deployment-03.yml ├── package-lock.json ├── package.json ├── server-01.js ├── server-02.js └── server-03.js ├── gitops-demo ├── base │ ├── kustomization.yaml │ ├── microcks-instance.yaml │ └── microcks-operator.yaml └── overlays │ └── minikube.local │ ├── kustomization.yaml │ ├── microcks-apisource.yaml │ └── microcks-instance.yaml ├── graphql-federation-demo ├── .meshrc.yaml ├── README.md ├── contracts │ ├── apipastries-openapi.yaml │ ├── chiefs-service.proto │ └── stores-graph.graphql ├── datasets │ ├── apipastries-examples.yaml │ ├── apipastries-postman-collection.json │ ├── chiefs-examples.yaml │ ├── stores-examples.yaml │ └── stores-recording.json ├── jest.config.ts ├── mesh.test.ts ├── microcks.sh ├── microcks.yaml ├── package-lock.json ├── package.json └── standalone │ ├── .DS_Store │ ├── config │ ├── application.properties │ └── features.properties │ └── docker-compose.yml ├── grpc-hello-world-demo ├── .dockerignore ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── .gitignore │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── docker │ │ ├── Dockerfile.jvm │ │ ├── Dockerfile.legacy-jar │ │ ├── Dockerfile.native │ │ └── Dockerfile.native-micro │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── microcks │ │ │ └── samples │ │ │ └── HelloGrpcService.java │ ├── proto │ │ └── hello.proto │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── io │ └── github │ └── microcks │ └── samples │ └── HelloGrpcServiceTest.java ├── shift-left-demo ├── nest-order-service │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── assets │ │ ├── order-service-architecture.png │ │ └── order-service-ecosystem.png │ ├── microcks-docker-compose.yml │ ├── microcks.sh │ ├── nest-cli.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ ├── order │ │ │ ├── dto │ │ │ │ ├── order-info.dto.ts │ │ │ │ └── unavailable-product.dto.ts │ │ │ ├── entities │ │ │ │ ├── order-status.ts │ │ │ │ ├── order.entity.ts │ │ │ │ └── product-quantity.entity.ts │ │ │ ├── order.controller.spec.ts │ │ │ ├── order.controller.ts │ │ │ ├── order.module.ts │ │ │ ├── order.service.spec.ts │ │ │ ├── order.service.ts │ │ │ └── unavailable-pastry.error.ts │ │ └── pastry │ │ │ ├── pastry.dto.ts │ │ │ ├── pastry.module.ts │ │ │ ├── pastry.service.spec.ts │ │ │ └── pastry.service.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ ├── jest-e2e.json │ │ ├── orders.api.e2e-spec.ts │ │ ├── orders.api.postman.e2e-spec.ts │ │ └── resources │ │ │ ├── apipastries-openapi.yml │ │ │ ├── apipastries-postman-collection.json │ │ │ ├── order-service-openapi.yml │ │ │ └── order-service-postman-collection.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── quarkus-order-service │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── MavenWrapperDownloader.java │ │ │ └── maven-wrapper.properties │ ├── README.md │ ├── assets │ │ ├── order-service-architecture.png │ │ └── order-service-ecosystem.png │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── docker │ │ │ ├── Dockerfile.jvm │ │ │ ├── Dockerfile.legacy-jar │ │ │ ├── Dockerfile.native │ │ │ └── Dockerfile.native-micro │ │ ├── java │ │ │ └── org │ │ │ │ └── acme │ │ │ │ └── order │ │ │ │ ├── api │ │ │ │ └── OrderResource.java │ │ │ │ ├── client │ │ │ │ ├── PastryAPIClient.java │ │ │ │ └── model │ │ │ │ │ └── Pastry.java │ │ │ │ └── service │ │ │ │ ├── OrderService.java │ │ │ │ ├── UnavailablePastryException.java │ │ │ │ └── model │ │ │ │ ├── Order.java │ │ │ │ ├── OrderInfo.java │ │ │ │ ├── OrderStatus.java │ │ │ │ ├── ProductQuantity.java │ │ │ │ └── UnavailableProduct.java │ │ └── resources │ │ │ ├── META-INF │ │ │ └── resources │ │ │ │ └── index.html │ │ │ ├── application.properties │ │ │ └── order-service-openapi.yaml │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── acme │ │ │ └── order │ │ │ ├── api │ │ │ └── OrderResourceContractTests.java │ │ │ └── client │ │ │ └── PastryAPIClientTests.java │ │ └── resources │ │ └── third-parties │ │ ├── HelloService-metadata.yml │ │ ├── HelloService-postman-collection.json │ │ ├── apipastries-openapi.yaml │ │ ├── apipastries-postman-collection.json │ │ └── hello-v1.proto └── spring-boot-order-service │ ├── .devcontainer │ └── devcontainer.json │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── README.md │ ├── assets │ ├── order-service-architecture.png │ └── order-service-ecosystem.png │ ├── microcks-docker-compose.yml │ ├── microcks-test.sh │ ├── microcks.sh │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── acme │ │ │ └── order │ │ │ ├── OrderServiceApplication.java │ │ │ ├── api │ │ │ └── OrderController.java │ │ │ ├── client │ │ │ ├── PastryAPIClient.java │ │ │ └── model │ │ │ │ └── Pastry.java │ │ │ └── service │ │ │ ├── OrderService.java │ │ │ ├── UnavailablePastryException.java │ │ │ └── model │ │ │ ├── Order.java │ │ │ ├── OrderInfo.java │ │ │ ├── OrderStatus.java │ │ │ ├── ProductQuantity.java │ │ │ └── UnavailableProduct.java │ └── resources │ │ ├── application.properties │ │ ├── order-service-openapi.yaml │ │ └── order-service-postman-collection.json │ └── test │ ├── java │ └── org │ │ └── acme │ │ └── order │ │ ├── OrderServiceApplicationTests.java │ │ ├── api │ │ ├── OrderControllerContractJUnit4Tests.java │ │ ├── OrderControllerContractTests.java │ │ └── OrderControllerPostmanContractTests.java │ │ └── client │ │ ├── PastryAPIClientJUnit4Tests.java │ │ └── PastryAPIClientTests.java │ └── resources │ └── third-parties │ ├── apipastries-openapi.yaml │ └── apipastries-postman-collection.json └── user-registration-demo ├── README.md ├── api-contracts ├── UserRegistrationAPI-openapi-1.0.0.yml └── UserSignedUpAPI-asyncapi-0.1.1.yml ├── api-implementations └── quarkus-user-registration │ ├── .dockerignore │ ├── .gitignore │ ├── .java-version │ ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── README.md │ ├── deployment.yml │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ └── main │ ├── docker │ ├── Dockerfile.fast-jar │ ├── Dockerfile.jvm │ └── Dockerfile.native │ ├── java │ └── io │ │ └── github │ │ └── microcks │ │ └── samples │ │ └── registration │ │ ├── ApprovedUserRegistration.java │ │ ├── KafkaUserRegistrationProducerManager.java │ │ ├── RegistrationResource.java │ │ ├── UserRegistration.java │ │ └── UserSignedUpEvent.java │ └── resources │ ├── META-INF │ └── resources │ │ └── index.html │ └── application.properties ├── assets ├── user-registration-broker-secret.png └── user-registration-tekton-pipeline.png ├── kafka-broker-kubernetes.yml ├── kafka-broker-openshift-beta2.yml ├── kafka-broker-openshift.yml ├── microcks-keycloak-client-secret.yml └── user-registration-tekton-pipeline.yml /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microcks Community Code of Conduct 2 | 3 | Microcks project adheres to the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md), aligning with the values of collaboration, transparency, and inclusivity that define open source. 4 | 5 | ## Our Pledge 6 | 7 | As contributors and maintainers of the Microcks project, we are committed to fostering an open, welcoming, and inclusive environment. By participating in our community, we pledge to: 8 | 9 | - Promote a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, experience level, nationality, personal appearance, race, religion, or sexual identity and orientation. 10 | - Support a spirit of mutual respect, empathy, and cooperation, ensuring that every contribution—big or small—is valued. 11 | - Encourage collaboration, knowledge-sharing, and continuous learning, as these are the cornerstones of any successful open-source community. 12 | 13 | ## Our Standards 14 | 15 | To ensure a productive, positive experience for all community members, we ask everyone to: 16 | 17 | - **Be respectful and considerate**: Engage with others constructively, appreciating the diverse perspectives and ideas within the community. 18 | - **Be collaborative**: Open-source thrives on shared efforts and diverse input. Offer help where needed and seek help when necessary. 19 | - **Lead by example**: Encourage new contributors and foster an environment where everyone feels welcome to participate. 20 | - **Value inclusivity**: Open source is for everyone. Actively support a diverse range of contributors and voices, regardless of background. 21 | 22 | ## Enforcement 23 | 24 | Instances of unacceptable behavior—including harassment, abusive language, or any other forms of misconduct—should be reported to the Microcks project team at **info@microcks.io**. 25 | 26 | The project team will: 27 | - Review and investigate all reports confidentially. 28 | - Take action deemed appropriate for the situation, including potential temporary or permanent bans from the community. 29 | - Keep the identity of the reporter confidential, ensuring a safe and respectful process for all involved. 30 | 31 | ## A Community-Driven Open Source 32 | 33 | Microcks thrives because of its open-source community. We encourage all members to actively contribute, share knowledge, and help make our project better for everyone. By following this Code of Conduct, we can create a community that supports innovation, collaboration, and the open-source values we all believe in. 34 | 35 | Thank you for being part of the Microcks community! 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Laurent BROUDOUX & Nicolas MASSE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # api-lifecycle 2 | Full lifecycle demonstration on Microcks usages 3 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you've found a vulnerability in our components or website or want additional information regarding how we manage security, please report it via a [GitHub discussion](https://github.com/microcks/microcks/discussions). 6 | 7 | If you do not want to publicly report a security issue for one of the libraries owned by the Microcks community, write an email with a detailed description of the issue to security@microcks.io. 8 | 9 | ## Public Disclosure Timing 10 | 11 | We prefer to fully disclose the bug as soon as possible once a user mitigation is available. The Fix Lead drives the schedule using their best judgment based on severity, development time, and release manager feedback. If the Fix Lead deals with public disclosure, all timelines will be set as soon as possible (ASAP). 12 | 13 | ## Supported Versions 14 | 15 | Microcks releases follow the [semver](https://semver.org/) specification. Security fixes are typically merged into the current development branch and are due for release in the next minor version. We may create a fix release upon request or, if deemed necessary, as part of a critical security fix. 16 | 17 | ## Security Team 18 | 19 | The security team is made up of a subset of the project [maintainers](https://github.com/microcks/.github/blob/main/GOVERNANCE.md#maintainers-code-owners-contributors-and-adopters) and [code owners](https://github.com/microcks/.github/blob/main/GOVERNANCE.md#maintainers-code-owners-contributors-and-adopters) who are willing and able to respond to vulnerability reports. 20 | 21 | ## Credits 22 | 23 | Sections of this document have been borrowed and inspired from the [OpenEBS](https://github.com/openebs/community/blob/72506ee3b885bd06324b82a650fcd3a61e93eef0/SECURITY.md) project. 24 | -------------------------------------------------------------------------------- /account-service-demo-amqp/account-service-asyncapi-1.1.0.yml: -------------------------------------------------------------------------------- 1 | asyncapi: '2.1.0' 2 | info: 3 | title: Account Service 4 | version: 1.1.0 5 | description: This service is in charge of processing user signups 6 | channels: 7 | user/signedup: 8 | bindings: 9 | amqp: 10 | is: routingKey 11 | exchange: 12 | name: signedup-exchange 13 | type: topic 14 | durable: true 15 | autoDelete: false 16 | vhost: / 17 | bindingVersion: 0.2.0 18 | subscribe: 19 | message: 20 | $ref: '#/components/messages/UserSignedUp' 21 | components: 22 | messages: 23 | UserSignedUp: 24 | payload: 25 | type: object 26 | properties: 27 | displayName: 28 | type: string 29 | description: Name of the user 30 | email: 31 | type: string 32 | format: email 33 | description: Email of the user 34 | required: 35 | - displayName 36 | - email 37 | additionalProperties: false 38 | examples: 39 | - name: Laurent 40 | payload: 41 | displayName: Laurent Broudoux 42 | email: laurent@microcks.io 43 | - name: Random 44 | payload: 45 | displayName: '{{randomFullName()}}' 46 | email: '{{randomEmail()}}' -------------------------------------------------------------------------------- /account-service-demo-amqp/account-service-asyncapi-1.2.0.yml: -------------------------------------------------------------------------------- 1 | asyncapi: '2.1.0' 2 | info: 3 | title: Account Service 4 | version: 1.2.0 5 | description: This service is in charge of processing user signups 6 | channels: 7 | user/signedup: 8 | bindings: 9 | amqp: 10 | is: routingKey 11 | exchange: 12 | name: signedup-exchange 13 | type: topic 14 | durable: true 15 | autoDelete: false 16 | vhost: / 17 | bindingVersion: 0.2.0 18 | subscribe: 19 | message: 20 | $ref: '#/components/messages/UserSignedUp' 21 | components: 22 | messages: 23 | UserSignedUp: 24 | payload: 25 | type: object 26 | properties: 27 | displayName: 28 | type: string 29 | description: Name of the user 30 | email: 31 | type: string 32 | format: email 33 | description: Email of the user 34 | age: 35 | type: number 36 | format: integer 37 | description: Age of the user 38 | required: 39 | - displayName 40 | - email 41 | additionalProperties: false 42 | examples: 43 | - name: Laurent 44 | payload: 45 | displayName: Laurent Broudoux 46 | email: laurent@microcks.io 47 | age: 43 48 | - name: Random 49 | payload: 50 | displayName: '{{randomFullName()}}' 51 | email: '{{randomEmail()}}' 52 | age: 23 -------------------------------------------------------------------------------- /account-service-demo/Dockerfile.bad: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-14-minimal:1-11 2 | 3 | ENV APP_ROOT=/app 4 | 5 | WORKDIR ${APP_ROOT} 6 | 7 | # root for build stages 8 | USER root 9 | 10 | # server 11 | COPY bad-server.js ${APP_ROOT}/server/server.js 12 | COPY package.json ${APP_ROOT}/server/package.json 13 | COPY bin/ ${APP_ROOT}/server/bin/ 14 | RUN cd ${APP_ROOT}/server \ 15 | && npm install --unsafe-perm \ 16 | && rm -rdf /opt/app-root/src/.npm /tmp/v8-compile-cache-0 17 | 18 | ### Setup user for build execution and application runtime 19 | ENV HOME=${APP_ROOT} 20 | RUN chmod -R u+x ${APP_ROOT}/server/bin && \ 21 | chgrp -R 0 ${APP_ROOT} && \ 22 | chmod -R g=u ${APP_ROOT} /etc/passwd 23 | 24 | ### Containers should NOT run as root as a good practice 25 | USER 1001 26 | 27 | # Finalize 28 | WORKDIR ${APP_ROOT}/server 29 | ENTRYPOINT [ "/app/server/bin/uid_entrypoint" ] 30 | EXPOSE 4000 -------------------------------------------------------------------------------- /account-service-demo/Dockerfile.good: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-14-minimal:1-11 2 | 3 | ENV APP_ROOT=/app 4 | 5 | WORKDIR ${APP_ROOT} 6 | 7 | # root for build stages 8 | USER root 9 | 10 | # server 11 | COPY good-server.js ${APP_ROOT}/server/server.js 12 | COPY package.json ${APP_ROOT}/server/package.json 13 | COPY bin/ ${APP_ROOT}/server/bin/ 14 | RUN cd ${APP_ROOT}/server \ 15 | && npm install --unsafe-perm \ 16 | && rm -rdf /opt/app-root/src/.npm /tmp/v8-compile-cache-0 17 | 18 | ### Setup user for build execution and application runtime 19 | ENV HOME=${APP_ROOT} 20 | RUN chmod -R u+x ${APP_ROOT}/server/bin && \ 21 | chgrp -R 0 ${APP_ROOT} && \ 22 | chmod -R g=u ${APP_ROOT} /etc/passwd 23 | 24 | ### Containers should NOT run as root as a good practice 25 | USER 1001 26 | 27 | # Finalize 28 | WORKDIR ${APP_ROOT}/server 29 | ENTRYPOINT [ "/app/server/bin/uid_entrypoint" ] 30 | EXPOSE 4000 -------------------------------------------------------------------------------- /account-service-demo/account-service-asyncapi-1.0.0.yml: -------------------------------------------------------------------------------- 1 | asyncapi: '2.1.0' 2 | info: 3 | title: Account Service 4 | version: 1.0.0 5 | description: This service is in charge of processing user signups 6 | channels: 7 | user/signedup: 8 | bindings: 9 | ws: 10 | method: post 11 | subscribe: 12 | message: 13 | $ref: '#/components/messages/UserSignedUp' 14 | components: 15 | messages: 16 | UserSignedUp: 17 | payload: 18 | type: object 19 | properties: 20 | displayName: 21 | type: string 22 | description: Name of the user 23 | email: 24 | type: string 25 | format: email 26 | description: Email of the user 27 | required: 28 | - displayName 29 | - email 30 | additionalProperties: false 31 | examples: 32 | - name: Laurent 33 | payload: 34 | displayName: Laurent Broudoux 35 | email: laurent@microcks.io 36 | - name: Random 37 | payload: 38 | displayName: '{{randomFullName()}}' 39 | email: '{{randomEmail()}}' -------------------------------------------------------------------------------- /account-service-demo/bad-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | const https = require('https'); 5 | const url = require('url'); 6 | const WebSocket = require('ws'); 7 | 8 | var commandArgs = process.argv.slice(2); 9 | 10 | const wsCert = commandArgs[0] || null; 11 | const wsKey = commandArgs[1] || null; 12 | 13 | var wss = new WebSocket.Server({ noServer: true }); 14 | 15 | wss.on('connection', function connection(ws) { 16 | ws.on('message', function incoming(message) { 17 | console.log('Received: %s', message); 18 | }); 19 | }); 20 | 21 | // Initialize WebSocket server with or without ssl depending on Cert presence. 22 | if (wsCert != null && wsKey != null) { 23 | const server = https.createServer({ 24 | cert: fs.readFileSync(wsCert, 'utf-8'), 25 | key: fs.readFileSync(wsKey, 'utf-8') 26 | }); 27 | 28 | server.on('upgrade', function upgrade(request, socket, head) { 29 | const pathname = url.parse(request.url).pathname; 30 | 31 | if (pathname === '/websocket') { 32 | wss.handleUpgrade(request, socket, head, function done(ws) { 33 | wss.emit('connection', ws, request); 34 | }); 35 | } else { 36 | socket.destroy(); 37 | } 38 | }); 39 | 40 | server.listen(4000); 41 | } else { 42 | wss = new WebSocket.Server({ port: 4000, path: "/websocket" }); 43 | } 44 | 45 | console.log("Starting WebSocket server on ws://localhost:4000/websocket"); 46 | 47 | const getRandomId = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 48 | 49 | const createMessage = id => ({ 50 | fullName: "Laurent Broudoux", 51 | email: "laurent@microcks.io", 52 | age: 42 53 | }) 54 | 55 | const sendMessage = () => { 56 | var msg = createMessage(getRandomId()); 57 | console.log("Sending to " + wss.clients.size + " clients"); 58 | wss.clients.forEach(function each(client) { 59 | if (client.readyState === WebSocket.OPEN) { 60 | client.send(JSON.stringify(msg)); 61 | } 62 | }); 63 | console.log(msg); 64 | } 65 | 66 | setInterval(sendMessage, 3000); -------------------------------------------------------------------------------- /account-service-demo/bin/uid_entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if ! whoami &> /dev/null; then 3 | if [ -w /etc/passwd ]; then 4 | echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 5 | fi 6 | fi 7 | /usr/bin/node /app/server/server.js 8 | -------------------------------------------------------------------------------- /account-service-demo/good-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | const https = require('https'); 5 | const url = require('url'); 6 | const WebSocket = require('ws'); 7 | 8 | var commandArgs = process.argv.slice(2); 9 | 10 | const wsCert = commandArgs[0] || null; 11 | const wsKey = commandArgs[1] || null; 12 | 13 | var wss = new WebSocket.Server({ noServer: true }); 14 | 15 | wss.on('connection', function connection(ws) { 16 | ws.on('message', function incoming(message) { 17 | console.log('Received: %s', message); 18 | }); 19 | }); 20 | 21 | // Initialize WebSocket server with or without ssl depending on Cert presence. 22 | if (wsCert != null && wsKey != null) { 23 | const server = https.createServer({ 24 | cert: fs.readFileSync(wsCert, 'utf-8'), 25 | key: fs.readFileSync(wsKey, 'utf-8') 26 | }); 27 | 28 | server.on('upgrade', function upgrade(request, socket, head) { 29 | const pathname = url.parse(request.url).pathname; 30 | 31 | if (pathname === '/websocket') { 32 | wss.handleUpgrade(request, socket, head, function done(ws) { 33 | wss.emit('connection', ws, request); 34 | }); 35 | } else { 36 | socket.destroy(); 37 | } 38 | }); 39 | 40 | server.listen(4000); 41 | } else { 42 | wss = new WebSocket.Server({ port: 4000, path: "/websocket" }); 43 | } 44 | 45 | console.log("Starting WebSocket server on ws://localhost:4000/websocket"); 46 | 47 | const getRandomId = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 48 | 49 | const createMessage = id => ({ 50 | displayName: "Laurent Broudoux", 51 | email: "laurent@microcks.io" 52 | }) 53 | 54 | const sendMessage = () => { 55 | var msg = createMessage(getRandomId()); 56 | console.log("Sending to " + wss.clients.size + " clients"); 57 | wss.clients.forEach(function each(client) { 58 | if (client.readyState === WebSocket.OPEN) { 59 | client.send(JSON.stringify(msg)); 60 | } 61 | }); 62 | console.log(msg); 63 | } 64 | 65 | setInterval(sendMessage, 3000); -------------------------------------------------------------------------------- /account-service-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "account-service-demo", 3 | "version": "0.0.1", 4 | "description": "account-service-demo", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "ws": "^7.4.6" 12 | }, 13 | "keywords": [], 14 | "author": "laurent.broudoux@gmail.com", 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /acme-lib/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /acme-lib/Dockerfile.acme: -------------------------------------------------------------------------------- 1 | FROM quay.io/microcks/microcks:nightly 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Copy libraries jar files 6 | COPY lib /deployments/lib 7 | 8 | ENV JAVA_OPTIONS=-Dloader.path=/deployments/lib 9 | ENV JAVA_MAIN_CLASS=org.springframework.boot.loader.PropertiesLauncher 10 | ENV JAVA_APP_JAR=app.jar -------------------------------------------------------------------------------- /acme-lib/Dockerfile.acme.minion: -------------------------------------------------------------------------------- 1 | FROM quay.io/microcks/microcks-async-minion:nightly 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Copy libraries jar files 6 | COPY lib /deployments/lib 7 | 8 | ENV JAVA_CLASSPATH=/deployments/*:/deployments/lib/* -------------------------------------------------------------------------------- /acme-lib/config/application.properties: -------------------------------------------------------------------------------- 1 | # Async mocking support. 2 | async-api.enabled=true 3 | 4 | # Access to Microcks API server. 5 | %docker-compose.io.github.microcks.minion.async.client.MicrocksAPIConnector/mp-rest/url=http://microcks:8080 6 | 7 | # Access to Keycloak through docker network 8 | %docker-compose.keycloak.auth.url=http://keycloak:8080 9 | 10 | # Access to Kafka broker. 11 | %docker-compose.kafka.bootstrap.servers=kafka:19092 12 | 13 | # Do not save any consumer-offset on the broker as there's a re-sync on each minion startup. 14 | %docker-compose.mp.messaging.incoming.microcks-services-updates.enable.auto.commit=false 15 | %docker-compose.mp.messaging.incoming.microcks-services-updates.bootstrap.servers=kafka:19092 16 | 17 | 18 | %docker-compose.kafka.security.protocol=SASL_SSL 19 | %docker-compose.kafka.sasl.mechanism=OAUTHBEARER 20 | %docker-compose.kafka.sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required kid="ABCDefgh11Pi7jKLMNOpq-R0s3TuVWxyzAbcDEFgHI0" login.id="kafka_producer_prem"; 21 | %docker-compose.kafka.sasl.login.callback.handler.class=org.acme.lib.CustomAuthenticateCallbackHandler 22 | 23 | %docker-compose.mp.messaging.incoming.microcks-services-updates.security.protocol=SASL_SSL 24 | %docker-compose.mp.messaging.incoming.microcks-services-updates.sasl.mechanism=OAUTHBEARER 25 | %docker-compose.mp.messaging.incoming.microcks-services-updates.sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required kid="ABCDefgh11Pi7jKLMNOpq-R0s3TuVWxyzAbcDEFgHI0" login.id="kafka_producer_prem"; 26 | %docker-compose.mp.messaging.incoming.microcks-services-updates.sasl.login.callback.handler.class=org.acme.lib.CustomAuthenticateCallbackHandler 27 | 28 | 29 | # Explicitly telling the minion the protocols we want to support 30 | %docker-compose.minion.supported-bindings=KAFKA,WS 31 | 32 | # %docker-compose.minion.supported-bindings=KAFKA,WS,MQTT,AMQP,NATS 33 | 34 | # %docker-compose.mqtt.server=localhost:1883 35 | # %docker-compose.mqtt.username=microcks 36 | # %docker-compose.mqtt.password=microcks 37 | 38 | # %docker-compose.amqp.server=localhost:5672 39 | # %docker-compose.amqp.username=microcks 40 | # %docker-compose.amqp.password=microcks 41 | -------------------------------------------------------------------------------- /acme-lib/config/features.properties: -------------------------------------------------------------------------------- 1 | features.feature.async-api.enabled=true 2 | features.feature.async-api.frequencies=3,10,30 3 | features.feature.async-api.default-binding=KAFKA 4 | features.feature.async-api.endpoint-KAFKA=localhost:9092 5 | features.feature.async-api.endpoint-MQTT=my-mqtt-broker.apps.try.microcks.io:1883 6 | features.feature.async-api.endpoint-AMQP=my-amqp-broker.apps.try.microcks.io:5672 7 | features.feature.async-api.endpoint-WS=localhost:8081 8 | features.feature.async-api.endpoint-NATS=localhost:4222 -------------------------------------------------------------------------------- /acme-lib/docker-compose-acme-async.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | mongo: 5 | image: mongo:3.6.23 6 | container_name: microcks-db 7 | volumes: 8 | - "~/tmp/microcks-data:/data/db" 9 | 10 | kafka: 11 | image: vectorized/redpanda:v22.2.2 12 | container_name: microcks-kafka 13 | command: [ 14 | "redpanda", "start", 15 | "--overprovisioned --smp 1 --memory 1G --reserve-memory 0M --node-id 0 --check=false", 16 | "--kafka-addr PLAINTEXT://0.0.0.0:19092,EXTERNAL://0.0.0.0:9092", 17 | "--advertise-kafka-addr PLAINTEXT://kafka:19092,EXTERNAL://localhost:9092" 18 | ] 19 | ports: 20 | - "9092:9092" 21 | - "19092:19092" 22 | 23 | app: 24 | depends_on: 25 | - mongo 26 | image: quay.io/microcks/microcks:nightly 27 | container_name: microcks 28 | volumes: 29 | - "./config:/deployments/config" 30 | ports: 31 | - "8080:8080" 32 | - "9090:9090" 33 | environment: 34 | - SPRING_PROFILES_ACTIVE=prod 35 | - SPRING_DATA_MONGODB_URI=mongodb://mongo:27017 36 | - SPRING_DATA_MONGODB_DATABASE=microcks 37 | - POSTMAN_RUNNER_URL=http://postman:3000 38 | - TEST_CALLBACK_URL=http://microcks:8080 39 | - SERVICES_UPDATE_INTERVAL=0 0 0/2 * * * 40 | - KEYCLOAK_ENABLED=false 41 | #- MAX_UPLOAD_FILE_SIZE=3MB 42 | - ASYNC_MINION_URL=http://microcks-async-minion:8081 43 | - KAFKA_BOOTSTRAP_SERVER=kafka:19092 44 | 45 | async-minion: 46 | depends_on: 47 | - app 48 | ports: 49 | - "8081:8081" 50 | image: acme/microcks-async-minion-ext:nightly 51 | container_name: microcks-async-minion 52 | restart: on-failure 53 | volumes: 54 | - "./config:/deployments/config" 55 | environment: 56 | - QUARKUS_PROFILE=docker-compose 57 | -------------------------------------------------------------------------------- /acme-lib/docker-compose-acme.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | mongo: 5 | image: mongo:3.6.23 6 | container_name: microcks-db 7 | volumes: 8 | - "~/tmp/microcks-data:/data/db" 9 | 10 | app: 11 | depends_on: 12 | - mongo 13 | image: acme/microcks-ext:nightly 14 | container_name: microcks 15 | ports: 16 | - "8080:8080" 17 | - "9090:9090" 18 | environment: 19 | - SPRING_PROFILES_ACTIVE=prod 20 | - SPRING_DATA_MONGODB_URI=mongodb://mongo:27017 21 | - SPRING_DATA_MONGODB_DATABASE=microcks 22 | - POSTMAN_RUNNER_URL=http://postman:3000 23 | - TEST_CALLBACK_URL=http://microcks:8080 24 | - SERVICES_UPDATE_INTERVAL=0 0 0/2 * * * 25 | - KEYCLOAK_ENABLED=false 26 | #- MAX_UPLOAD_FILE_SIZE=3MB 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /acme-lib/docker-compose-mount-async.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | mongo: 5 | image: mongo:3.6.23 6 | container_name: microcks-db 7 | volumes: 8 | - "~/tmp/microcks-data:/data/db" 9 | 10 | kafka: 11 | image: vectorized/redpanda:v22.2.2 12 | container_name: microcks-kafka 13 | command: [ 14 | "redpanda", "start", 15 | "--overprovisioned --smp 1 --memory 1G --reserve-memory 0M --node-id 0 --check=false", 16 | "--kafka-addr PLAINTEXT://0.0.0.0:19092,EXTERNAL://0.0.0.0:9092", 17 | "--advertise-kafka-addr PLAINTEXT://kafka:19092,EXTERNAL://localhost:9092" 18 | ] 19 | ports: 20 | - "9092:9092" 21 | - "19092:19092" 22 | 23 | app: 24 | depends_on: 25 | - mongo 26 | image: quay.io/microcks/microcks:nightly 27 | container_name: microcks 28 | volumes: 29 | - "./config:/deployments/config" 30 | ports: 31 | - "8080:8080" 32 | - "9090:9090" 33 | environment: 34 | - SPRING_PROFILES_ACTIVE=prod 35 | - SPRING_DATA_MONGODB_URI=mongodb://mongo:27017 36 | - SPRING_DATA_MONGODB_DATABASE=microcks 37 | - POSTMAN_RUNNER_URL=http://postman:3000 38 | - TEST_CALLBACK_URL=http://microcks:8080 39 | - SERVICES_UPDATE_INTERVAL=0 0 0/2 * * * 40 | - KEYCLOAK_ENABLED=false 41 | #- MAX_UPLOAD_FILE_SIZE=3MB 42 | - ASYNC_MINION_URL=http://microcks-async-minion:8081 43 | - KAFKA_BOOTSTRAP_SERVER=kafka:19092 44 | 45 | async-minion: 46 | depends_on: 47 | - app 48 | ports: 49 | - "8081:8081" 50 | image: quay.io/microcks/microcks-async-minion:nightly 51 | container_name: microcks-async-minion 52 | restart: on-failure 53 | volumes: 54 | - "./config:/deployments/config" 55 | - "./lib:/deployments/lib-ext" 56 | environment: 57 | - QUARKUS_PROFILE=docker-compose 58 | - JAVA_CLASSPATH=/deployments/*:/deployments/lib/*:/deployments/lib-ext/* 59 | -------------------------------------------------------------------------------- /acme-lib/docker-compose-mount.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | mongo: 5 | image: mongo:3.6.23 6 | container_name: microcks-db 7 | volumes: 8 | - "~/tmp/microcks-data:/data/db" 9 | 10 | app: 11 | depends_on: 12 | - mongo 13 | image: quay.io/microcks/microcks:nightly 14 | container_name: microcks 15 | volumes: 16 | - ./lib:/deployments/lib 17 | ports: 18 | - "8080:8080" 19 | - "9090:9090" 20 | environment: 21 | - SPRING_PROFILES_ACTIVE=prod 22 | - SPRING_DATA_MONGODB_URI=mongodb://mongo:27017 23 | - SPRING_DATA_MONGODB_DATABASE=microcks 24 | - POSTMAN_RUNNER_URL=http://postman:3000 25 | - TEST_CALLBACK_URL=http://microcks:8080 26 | - SERVICES_UPDATE_INTERVAL=0 0 0/2 * * * 27 | - KEYCLOAK_ENABLED=false 28 | - JAVA_OPTIONS=-Dloader.path=/deployments/lib 29 | - JAVA_MAIN_CLASS=org.springframework.boot.loader.PropertiesLauncher 30 | - JAVA_APP_JAR=app.jar 31 | #- MAX_UPLOAD_FILE_SIZE=3MB 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /acme-lib/lib/acme-lib-0.0.1-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/acme-lib/lib/acme-lib-0.0.1-SNAPSHOT.jar -------------------------------------------------------------------------------- /acme-lib/podman-compose-mount.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | networks: 3 | main: 4 | services: 5 | mongo: 6 | image: mongo:3.6.23 7 | container_name: microcks-db 8 | volumes: 9 | - "~/tmp/microcks-data:/data/db" 10 | ports: 11 | - "27019:27017" 12 | postman: 13 | image: quay.io/microcks/microcks-postman-runtime:latest 14 | container_name: microcks-postman-runtime-prod 15 | ports: 16 | - "3002:3000" 17 | app: 18 | depends_on: 19 | - mongo 20 | - postman 21 | image: quay.io/microcks/microcks:nightly 22 | container_name: microcks 23 | ports: 24 | - "8080:8080" 25 | environment: 26 | JAVA_OPTIONS: "-Dloader.path=/deployments/libs" 27 | JAVA_MAIN_CLASS: "org.springframework.boot.loader.PropertiesLauncher" 28 | JAVA_APP_JAR: "app.jar" 29 | SPRING_PROFILES_ACTIVE: "prod" 30 | SPRING_DATA_MONGODB_URI: "mongodb://mongo:27017" 31 | SPRING_DATA_MONGODB_DATABASE: "microcks" 32 | POSTMAN_RUNNER_URL: "http://postman:3000" 33 | TEST_CALLBACK_URL: "http://microcks:8080" 34 | SERVICES_UPDATE_INTERVAL: "0 0 0/2 * * *" 35 | KEYCLOAK_ENABLED: false 36 | volumes: 37 | - ./lib:/deployments/lib 38 | -------------------------------------------------------------------------------- /acme-lib/src/main/groovy/org/acme/lib/GroovyGreeting.groovy: -------------------------------------------------------------------------------- 1 | package org.acme.lib; 2 | 3 | class GroovyGreeting { 4 | 5 | def greet(def name) { 6 | return "Groovy " + name + "!" 7 | } 8 | } -------------------------------------------------------------------------------- /acme-lib/src/main/java/org/acme/lib/CustomAuthenticateCallbackHandler.java: -------------------------------------------------------------------------------- 1 | package org.acme.lib; 2 | 3 | import org.apache.kafka.common.KafkaException; 4 | import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler; 5 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule; 6 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback; 7 | 8 | import javax.security.auth.callback.Callback; 9 | import javax.security.auth.callback.UnsupportedCallbackException; 10 | import javax.security.auth.login.AppConfigurationEntry; 11 | import java.io.IOException; 12 | import java.util.*; 13 | 14 | public class CustomAuthenticateCallbackHandler implements AuthenticateCallbackHandler { 15 | 16 | private Map moduleOptions = null; 17 | private boolean configured = false; 18 | 19 | public boolean isConfigured() { 20 | return this.configured; 21 | } 22 | 23 | @Override 24 | public void configure(Map map, String saslMechanism, List jaasConfigEntries) { 25 | if (!OAuthBearerLoginModule.OAUTHBEARER_MECHANISM.equals(saslMechanism)) 26 | throw new IllegalArgumentException(String.format("Unexpected SASL mechanism: %s", saslMechanism)); 27 | if (Objects.requireNonNull(jaasConfigEntries).size() != 1 || jaasConfigEntries.get(0) == null) 28 | throw new IllegalArgumentException( 29 | String.format("Must supply exactly 1 non-null JAAS mechanism configuration (size was %d)", 30 | jaasConfigEntries.size())); 31 | this.moduleOptions = Collections.unmodifiableMap((Map) jaasConfigEntries.get(0).getOptions()); 32 | configured = true; 33 | } 34 | 35 | @Override 36 | public void close() { 37 | } 38 | 39 | @Override 40 | public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { 41 | if (!isConfigured()) 42 | throw new IllegalStateException("Callback handler not configured"); 43 | for (Callback callback : callbacks) { 44 | if (callback instanceof OAuthBearerTokenCallback) 45 | System.err.println("Handling the callback..."); 46 | else 47 | throw new UnsupportedCallbackException(callback); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /acme-lib/src/main/java/org/acme/lib/Greeting.java: -------------------------------------------------------------------------------- 1 | package org.acme.lib; 2 | 3 | public class Greeting { 4 | 5 | public String greet(String name) { 6 | return "Hello " + name + "!"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/node-graphql-api-pastry/api-pastry-metadata.yml: -------------------------------------------------------------------------------- 1 | apiVersion: mocks.microcks.io/v1alpha1 2 | kind: APIMetadata 3 | metadata: 4 | name: Pastry Graph API 5 | version: 1.0 6 | labels: 7 | domain: pastries 8 | status: stable 9 | team: Team A 10 | operations: 11 | 'addReview': 12 | dispatcher: JSON_BODY 13 | dispatcherRules: |- 14 | { 15 | "exp": "/pastryName", 16 | "operator": "equals", 17 | "cases": { 18 | "Baba Rhum": "Awesome Baba Rhum", 19 | "Tartelette Fraise": "Delicious Tartelette", 20 | "default": "Awesome Baba Rhum" 21 | } 22 | } -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/node-graphql-api-pastry/api-pastry.graphql: -------------------------------------------------------------------------------- 1 | # microcksId: Pastry Graph API : 1.0 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | } 7 | type Pastry { 8 | name: String! 9 | description: String! 10 | size: String! 11 | price: Float! 12 | status: String! 13 | rating: Float! 14 | reviews: Int 15 | } 16 | type Pastries { 17 | count: Int! 18 | pastries: [Pastry] 19 | } 20 | input Review { 21 | comment: String 22 | rating: Int 23 | } 24 | 25 | type Query { 26 | allPastries: Pastries 27 | pastry(name: String): Pastry 28 | pastries(size: String): Pastries 29 | } 30 | 31 | type Mutation { 32 | addReview(pastryName: String, review: Review): Pastry 33 | } -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/node-graphql-api-pastry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-graphql-api-pastry", 3 | "version": "0.0.1", 4 | "description": "Sample GraphQL API Pastry", 5 | "author": "laurent.broudoux@gmail.com", 6 | "license": "MIT", 7 | "dependencies": { 8 | "express": "^4.18.2", 9 | "express-graphql": "^0.12.0", 10 | "graphql": "^15.8.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .project 3 | .classpath 4 | .settings/ 5 | bin/ 6 | 7 | # IntelliJ 8 | .idea 9 | *.ipr 10 | *.iml 11 | *.iws 12 | 13 | # NetBeans 14 | nb-configuration.xml 15 | 16 | # Visual Studio Code 17 | .vscode 18 | .factorypath 19 | 20 | # OSX 21 | .DS_Store 22 | 23 | # Vim 24 | *.swp 25 | *.swo 26 | 27 | # patch 28 | *.orig 29 | *.rej 30 | 31 | # Maven 32 | target/ 33 | pom.xml.tag 34 | pom.xml.releaseBackup 35 | pom.xml.versionsBackup 36 | release.properties -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/api-pastry-demo/api-implementations/quarkus-api-pastry/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: quarkus-api-pastry 6 | app.kubernetes.io/component: quarkus-api-pastry 7 | app.kubernetes.io/instance: quarkus-api-pastry 8 | app.kubernetes.io/part-of: quarkus-api-pastry 9 | name: quarkus-api-pastry 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: quarkus-api-pastry 15 | template: 16 | metadata: 17 | labels: 18 | app: quarkus-api-pastry 19 | spec: 20 | containers: 21 | - name: quarkus-api-pastry 22 | image: quay.io/microcks/quarkus-api-pastry:latest 23 | imagePullPolicy: Always 24 | env: 25 | - name: QUARKUS_PROFILE 26 | value: kube 27 | resources: 28 | requests: 29 | cpu: 100m 30 | memory: 100Mi 31 | limits: 32 | cpu: 250m 33 | memory: 200Mi 34 | ports: 35 | - containerPort: 8282 36 | name: http 37 | protocol: TCP 38 | securityContext: 39 | privileged: false 40 | --- 41 | kind: Service 42 | apiVersion: v1 43 | metadata: 44 | name: quarkus-api-pastry 45 | labels: 46 | app: quarkus-api-pastry 47 | app.kubernetes.io/component: quarkus-api-pastry 48 | app.kubernetes.io/instance: quarkus-api-pastry 49 | app.kubernetes.io/part-of: quarkus-api-pastry 50 | spec: 51 | ports: 52 | - name: 8080-tcp 53 | protocol: TCP 54 | port: 8080 55 | targetPort: 8282 56 | selector: 57 | app: quarkus-api-pastry 58 | type: ClusterIP 59 | sessionAffinity: None 60 | -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/ingress.yml: -------------------------------------------------------------------------------- 1 | kind: Ingress 2 | apiVersion: networking.k8s.io/v1beta1 3 | metadata: 4 | name: quarkus-api-pastry 5 | annotations: 6 | ingress.kubernetes.io/rewrite-target: / 7 | spec: 8 | rules: 9 | - host: quarkus-api-pastry.192.168.64.7.nip.io 10 | http: 11 | paths: 12 | - backend: 13 | serviceName: quarkus-api-pastry 14 | servicePort: 8080 -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/main/docker/Dockerfile.fast-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-api-pastry-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-api-pastry-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/quarkus-api-pastry-jvm 22 | # 23 | ### 24 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 25 | 26 | ARG JAVA_PACKAGE=java-11-openjdk-headless 27 | ARG RUN_JAVA_VERSION=1.3.8 28 | 29 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 30 | 31 | # Install java and the run-java script 32 | # Also set up permissions for user `1001` 33 | RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 34 | && microdnf update \ 35 | && microdnf clean all \ 36 | && mkdir /deployments \ 37 | && chown 1001 /deployments \ 38 | && chmod "g+rwX" /deployments \ 39 | && chown 1001:root /deployments \ 40 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 41 | && chown 1001 /deployments/run-java.sh \ 42 | && chmod 540 /deployments/run-java.sh \ 43 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security 44 | 45 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 46 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 47 | 48 | # We make four distinct layers so if there are application changes the library layers can be re-used 49 | COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ 50 | COPY --chown=1001 target/quarkus-app/*.jar /deployments/ 51 | COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ 52 | COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ 53 | 54 | EXPOSE 8080 55 | USER 1001 56 | 57 | ENTRYPOINT [ "/deployments/run-java.sh" ] -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-api-pastry-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-api-pastry-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/quarkus-api-pastry-jvm 22 | # 23 | ### 24 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 25 | 26 | ARG JAVA_PACKAGE=java-11-openjdk-headless 27 | ARG RUN_JAVA_VERSION=1.3.8 28 | 29 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 30 | 31 | # Install java and the run-java script 32 | # Also set up permissions for user `1001` 33 | RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 34 | && microdnf update \ 35 | && microdnf clean all \ 36 | && mkdir /deployments \ 37 | && chown 1001 /deployments \ 38 | && chmod "g+rwX" /deployments \ 39 | && chown 1001:root /deployments \ 40 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 41 | && chown 1001 /deployments/run-java.sh \ 42 | && chmod 540 /deployments/run-java.sh \ 43 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security 44 | 45 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 46 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 47 | 48 | COPY target/lib/* /deployments/lib/ 49 | COPY target/*-runner.jar /deployments/app.jar 50 | 51 | EXPOSE 8080 52 | USER 1001 53 | 54 | ENTRYPOINT [ "/deployments/run-java.sh" ] -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package -Pnative -Dquarkus.native.container-build=true 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quay.io/microcks/quarkus-api-pastry . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quay.io/microcks/quarkus-api-pastry 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Configuration file 2 | quarkus.http.port=8282 3 | quarkus.http.cors=true 4 | 5 | # Following is necessary until https://github.com/quarkusio/quarkus/pull/11817 is made available in a release. 6 | # Note: this is only for JAXB XML Serialization of list of pastries. 7 | quarkus.native.additional-build-args =-H:ReflectionConfigurationFiles=reflection-config.json -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/main/resources/reflection-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "org.jboss.resteasy.plugins.providers.jaxb.JaxbCollection", 4 | "allDeclaredConstructors" : true, 5 | "allPublicConstructors" : true, 6 | "allDeclaredMethods" : true, 7 | "allPublicMethods" : true, 8 | "allDeclaredFields" : true, 9 | "allPublicFields" : true 10 | }, 11 | { 12 | "name" : "javax.xml.bind.annotation.W3CDomHandler", 13 | "allDeclaredConstructors" : true, 14 | "allPublicConstructors" : true, 15 | "allDeclaredMethods" : true, 16 | "allPublicMethods" : true, 17 | "allDeclaredFields" : true, 18 | "allPublicFields" : true 19 | } 20 | ] -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/test/java/io/github/microcks/samples/pastry/NativePastryResourceIT.java: -------------------------------------------------------------------------------- 1 | package io.github.microcks.samples.pastry; 2 | 3 | import io.quarkus.test.junit.NativeImageTest; 4 | 5 | @NativeImageTest 6 | public class NativePastryResourceIT extends PastryResourceTest { 7 | 8 | // Execute the same tests but in native mode. 9 | } -------------------------------------------------------------------------------- /api-pastry-demo/api-implementations/quarkus-api-pastry/src/test/java/io/github/microcks/samples/pastry/PastryResourceTest.java: -------------------------------------------------------------------------------- 1 | package io.github.microcks.samples.pastry; 2 | 3 | import io.quarkus.test.junit.QuarkusTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.restassured.RestAssured.given; 7 | 8 | @QuarkusTest 9 | public class PastryResourceTest { 10 | 11 | @Test 12 | public void testGetPastriesEndpoint() { 13 | given() 14 | .when().get("/pastry") 15 | .then() 16 | .statusCode(200); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/README.md: -------------------------------------------------------------------------------- 1 | # A Beer Catalog 2 | 3 | This project is a UI for a Beer Catalog. 4 | 5 | The look and feel is mostly taken from [brew.sh](https://brew.sh/). 6 | 7 | 8 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/beer.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "-apple-system", "BlinkMacSystemFont", "Helvetica Neue", "Roboto", sans-serif; 3 | 4 | /* Zoom page at 300% */ 5 | zoom: 3; 6 | -moz-transform: scale(3); 7 | -moz-transform-origin: 0 0; 8 | 9 | /* Homebrew.sh colors */ 10 | color: #f9d094; 11 | background: #2e2a24; 12 | } 13 | 14 | ul { 15 | list-style: none; 16 | } 17 | 18 | h1 { 19 | text-align: center; 20 | font-weight: 900; 21 | } 22 | 23 | img { 24 | height: 128px; 25 | width: 128px; 26 | display: block; 27 | margin-left: auto; 28 | margin-right: auto; 29 | } 30 | 31 | .flag { 32 | margin-right: 10px; 33 | } 34 | 35 | .status_out_of_stock { 36 | text-decoration: line-through; 37 | } 38 | 39 | .rating { 40 | float: right; 41 | margin-right: 30px; 42 | font-size: 50%; 43 | } 44 | 45 | .error { 46 | text-align: center; 47 | } 48 | 49 | .error h1 { 50 | font-size: 400%; 51 | margin-bottom: -10px; 52 | } 53 | 54 | .hidden { 55 | display: none; 56 | } 57 | 58 | .refresh { 59 | text-align: center; 60 | font-size: 200%; 61 | } 62 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/beer.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | module.exports = function () { 3 | var flags = require("flags.js"); 4 | var config = require("config.js"); 5 | $(".error,.catalog").toggleClass("hidden", true); 6 | $(".throbber").toggleClass("hidden", false); 7 | $(".catalog ul").empty(); 8 | $.ajax({ 9 | "url": config.baseURL + "/beer?page=0" + (config.extraQueryStringSuffix != null ? "&" + config.extraQueryStringSuffix : ""), 10 | "success": function (data, status, xhr) { 11 | try { 12 | var count = data.length; 13 | for (var i = 0; i < count; i++) { 14 | var flag = flags[data[i].country] ? flags[data[i].country] : "🏳️"; 15 | var rating = ""; 16 | var stop = data[i].rating; 17 | for (var r = 1; r < stop; r++) { 18 | rating += "🍺"; 19 | } 20 | $("
  • ").toggleClass("status_" + data[i].status) 21 | .text(data[i].name) 22 | .prepend($("").text(flag).toggleClass("flag")) 23 | .append($("").text(rating).toggleClass("rating")) 24 | .appendTo($(".catalog ul")); 25 | } 26 | 27 | $(".catalog").toggleClass("hidden", false); 28 | $(".throbber").toggleClass("hidden", true); 29 | } catch (e) { 30 | console.log(e); 31 | $("#error_message").text(e.message); 32 | $(".error").toggleClass("hidden", false); 33 | $(".throbber").toggleClass("hidden", true); 34 | } 35 | }, 36 | "error": function (xhr, status, error) { 37 | if (xhr.status == "403") { 38 | $("#error_message").text("API Quota reached !"); 39 | } else { 40 | $("#error_message").text("Sorry ! HTTP Status Code " + xhr.status); 41 | } 42 | $(".error").toggleClass("hidden", false); 43 | $(".throbber").toggleClass("hidden", true); 44 | }, 45 | "headers": config.additionalHeaders != null ? config.additionalHeaders : {} 46 | }); 47 | }; 48 | }); 49 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/brew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-demo/api-consumer/brew.png -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Sample config file for the Beer UI 3 | * 4 | * Usage: edit this file and save it as "config.js" 5 | */ 6 | define(function(require, exports, module) { 7 | module.exports = { 8 | // The base URL of the Beer API 9 | baseURL: "http://beer-catalog-api-beer-catalog-prod.52.174.149.59.nip.io/api", 10 | 11 | // If a user-key is required, you can pass it as HTTP Header: 12 | additionalHeaders: { "user-key": "db0a8a7d3ca99b5df5aace2f61ac4e45" }, 13 | 14 | // Or in the Query String 15 | extraQueryStringSuffix: "user_key=db0a8a7d3ca99b5df5aace2f61ac4e45" 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/config.js.example: -------------------------------------------------------------------------------- 1 | /* 2 | * Sample config file for the Beer UI 3 | * 4 | * Usage: edit this file and save it as "config.js" 5 | */ 6 | define(function(require, exports, module) { 7 | module.exports = { 8 | // The base URL of the Beer API 9 | baseURL: "http://microcks/rest/Beer%20Catalog%20API/0.9", 10 | 11 | // If a user-key is required, you can pass it as HTTP Header: 12 | additionalHeaders: { "user-key": "" }, 13 | 14 | // Or in the Query String 15 | extraQueryStringSuffix: "user_key=" 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/flags.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | module.exports = { 3 | "Austria": "🇦🇹", 4 | "Belgium": "🇧🇪", 5 | "Bulgaria": "🇧🇬", 6 | "Croatia": "🇭🇷", 7 | "Cyprus": "🇨🇾", 8 | "The Czech Republic": "🇨🇿", 9 | "Denmark": "🇩🇰", 10 | "Estonia": "🇪🇪", 11 | "Finland": "🇫🇮", 12 | "France": "🇫🇷", 13 | "Germany": "🇩🇪", 14 | "Gibraltar": "🇬🇮", 15 | "Greece": "🇬🇷", 16 | "Hungary": "🇭🇺", 17 | "Ireland": "🇮🇪", 18 | "Italy": "🇮🇹", 19 | "Latvia": "🇱🇻", 20 | "Lithuania": "🇱🇹", 21 | "Luxembourg": "🇱🇺", 22 | "Malta": "🇲🇹", 23 | "The Netherlands": "🇳🇱", 24 | "Poland": "🇵🇱", 25 | "Portugal": "🇵🇹", 26 | "România": "🇷🇴", 27 | "Slovakia": "🇸🇰", 28 | "Slovenia": "🇸🇮", 29 | "Spain": "🇪🇸", 30 | "Sweden": "🇸🇪", 31 | "United Kingdom": "🇬🇧" 32 | }; 33 | }); 34 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-consumer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Beer Catalog 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 |

    BEER CATALOG

    21 |
    22 |
      23 |
    24 |
    🔄
    25 |
    26 | 27 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-implementation/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.lbroudoux 7 | beer-catalog 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-hello 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.4.2.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-implementation/src/main/java/com/github/lbroudoux/beer/Beer.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Laurent BROUDOUX 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package com.github.lbroudoux.beer; 25 | 26 | /** 27 | * @author laurent 28 | */ 29 | public class Beer { 30 | 31 | private String name; 32 | private String country; 33 | private String type; 34 | private Float rating; 35 | private String status; 36 | 37 | 38 | public Beer(){ 39 | 40 | } 41 | 42 | public Beer(String name, String country, String type, Float rating, String status) { 43 | this.name = name; 44 | this.country = country; 45 | this.type = type; 46 | this.rating = rating; 47 | this.status = status; 48 | } 49 | 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | public void setName(String name) { 55 | this.name = name; 56 | } 57 | 58 | public String getCountry() { 59 | return country; 60 | } 61 | 62 | public void setCountry(String country) { 63 | this.country = country; 64 | } 65 | 66 | public String getType() { 67 | return type; 68 | } 69 | 70 | public void setType(String type) { 71 | this.type = type; 72 | } 73 | 74 | public Float getRating() { 75 | return rating; 76 | } 77 | 78 | public void setRating(Float rating) { 79 | this.rating = rating; 80 | } 81 | 82 | public String getStatus() { 83 | return status; 84 | } 85 | 86 | public void setStatus(String status) { 87 | this.status = status; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-implementation/src/main/java/com/github/lbroudoux/beer/BeerCatalogApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Laurent BROUDOUX 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package com.github.lbroudoux.beer; 25 | 26 | import org.springframework.boot.SpringApplication; 27 | import org.springframework.boot.autoconfigure.SpringBootApplication; 28 | 29 | /** 30 | * @author laurent 31 | */ 32 | @SpringBootApplication 33 | public class BeerCatalogApplication { 34 | 35 | public static void main(String[] args) { 36 | SpringApplication.run(BeerCatalogApplication.class, args); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-implementation/src/main/java/com/github/lbroudoux/beer/BeerCatalogController.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Laurent BROUDOUX 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package com.github.lbroudoux.beer; 25 | 26 | import org.springframework.web.bind.annotation.*; 27 | 28 | import java.util.List; 29 | 30 | /** 31 | * @author laurent 32 | */ 33 | @RestController 34 | @RequestMapping("/api") 35 | public class BeerCatalogController { 36 | 37 | @CrossOrigin 38 | @RequestMapping(method = RequestMethod.GET, value = "/beer") 39 | public List getBeers( 40 | @RequestParam(value = "page", required = false, defaultValue = "0") int page) { 41 | return BeerRepository.getBeers(); 42 | } 43 | 44 | @CrossOrigin 45 | @RequestMapping(method = RequestMethod.GET, value = "/beer/{name}") 46 | public Beer getBeer( 47 | @PathVariable("name") String name) { 48 | return BeerRepository.findByName(name); 49 | } 50 | 51 | @CrossOrigin 52 | @RequestMapping(method = RequestMethod.GET, value = "/beer/findByStatus/{status}") 53 | public List getByStatus( 54 | @PathVariable("status") String status) { 55 | return BeerRepository.findByStatus(status); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /beer-catalog-demo/api-implementation/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-demo/api-implementation/src/main/resources/application.properties -------------------------------------------------------------------------------- /beer-catalog-demo/deploy-3scale.sh: -------------------------------------------------------------------------------- 1 | oc login -u -p 2 | 3 | oc project beer-catalog-prod 4 | 5 | oc secret new-basicauth threescale-portal-endpoint-secret --password=https://<3scale-token>@<3scale-user>.3scale.net 6 | oc secret new-basicauth apicast-configuration-url-secret --password=https://<3scale-token>@<3scale-user>.3scale.net 7 | 8 | oc new-app 3scale-gateway \ 9 | --param=APICAST_NAME=beer-catalog-api \ 10 | --param=LOG_LEVEL=info \ 11 | --param=RESPONSE_CODES=true 12 | 13 | oc expose service/beer-catalog-api --name beer-catalog-api 14 | -------------------------------------------------------------------------------- /beer-catalog-demo/deploy-pipeline.sh: -------------------------------------------------------------------------------- 1 | oc login -u -p 2 | 3 | oc new-project beer-catalog-prod --display-name="Beer Catalog (PROD)" 4 | 5 | oc adm policy add-role-to-user edit system:serviceaccount:microcks:jenkins -n beer-catalog-dev 6 | oc adm policy add-role-to-user edit system:serviceaccount:microcks:jenkins -n beer-catalog-prod 7 | 8 | oc adm policy add-role-to-group system:image-puller system:serviceaccounts:beer-catalog-prod -n beer-catalog-dev 9 | 10 | oc get dc beer-catalog-impl -o json -n beer-catalog-dev | jq '.spec.triggers |= []' | oc replace -f - 11 | oc patch dc/beer-catalog-impl --type=json -p '[{"op":"add", "path":"/spec/template/spec/containers/0/readinessProbe", "value": {"failureThreshold": 3, "httpGet": { "path": "/api/beer?page=0", "port": 8080, "scheme": "HTTP" }, "initialDelaySeconds": 1, "periodSeconds": 10, "successThreshold": 1, "timeoutSeconds": 1}}]' -n beer-catalog-dev 12 | 13 | oc create deploymentconfig beer-catalog-impl --image=docker-registry.default.svc:5000/beer-catalog-dev/beer-catalog-impl:promoteToProd -n beer-catalog-prod 14 | oc rollout cancel dc/beer-catalog-impl -n beer-catalog-prod 15 | oc get dc beer-catalog-impl -o json -n beer-catalog-prod | jq '.spec.triggers |= []' | oc replace -f - 16 | oc get dc beer-catalog-impl -o yaml -n beer-catalog-prod | sed 's/imagePullPolicy: IfNotPresent/imagePullPolicy: Always/g' | oc replace -f - 17 | oc expose dc beer-catalog-impl --port=8080 -n beer-catalog-prod 18 | oc expose svc beer-catalog-impl -n beer-catalog-prod 19 | 20 | oc login -u -p 21 | 22 | oc create -f pipeline-template.yml -n microcks 23 | 24 | oc start-build beer-catalog-pipeline -n microcks 25 | -------------------------------------------------------------------------------- /beer-catalog-demo/pipeline-template.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: BuildConfig 3 | metadata: 4 | name: beer-catalog-pipeline 5 | labels: 6 | name: beer-catalog-pipeline 7 | annotations: 8 | pipeline.alpha.openshift.io/uses: '[{"name": "beer-catalog-impl", "namespace": "beer-catalog-dev", "kind": "DeploymentConfig"}]' 9 | spec: 10 | triggers: 11 | - 12 | type: GitHub 13 | github: 14 | secret: secret101 15 | - 16 | type: Generic 17 | generic: 18 | secret: secret101 19 | runPolicy: Serial 20 | source: 21 | type: None 22 | strategy: 23 | type: JenkinsPipeline 24 | jenkinsPipelineStrategy: 25 | jenkinsfile: |- 26 | node('maven') { 27 | stage ('buildInDev') { 28 | openshiftBuild(buildConfig: 'beer-catalog-impl', namespace: 'beer-catalog-dev', showBuildLogs: 'true') 29 | } 30 | stage ('deployInDev') { 31 | openshiftDeploy(namespace: 'beer-catalog-dev', deploymentConfig: 'beer-catalog-impl') 32 | } 33 | stage ('testInDev') { 34 | // Add Microcks test here. 35 | microcksTest(apiURL: 'http:///api', 36 | serviceId: 'Beer Catalog API:0.9', 37 | testEndpoint: 'http:///api/', 38 | runnerType: 'POSTMAN', verbose: 'true') 39 | } 40 | stage ('promoteToProd') { 41 | openshiftTag(namespace: 'beer-catalog-dev', sourceStream: 'beer-catalog-impl', sourceTag: 'latest', destinationStream: 'beer-catalog-impl', destinationTag: 'promoteToProd') 42 | } 43 | stage ('deployToProd') { 44 | openshiftDeploy(deploymentConfig: 'beer-catalog-impl', namespace: 'beer-catalog-prod') 45 | openshiftScale(deploymentConfig: 'beer-catalog-impl', namespace: 'beer-catalog-prod', replicaCount: '2') 46 | } 47 | } 48 | output: 49 | resources: 50 | postCommit: 51 | -------------------------------------------------------------------------------- /beer-catalog-workshop/assets/api-full-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-workshop/assets/api-full-lifecycle.png -------------------------------------------------------------------------------- /beer-catalog-workshop/assets/jenkins-ansible-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-workshop/assets/jenkins-ansible-server.png -------------------------------------------------------------------------------- /beer-catalog-workshop/assets/jenkins-ansible-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-workshop/assets/jenkins-ansible-user.png -------------------------------------------------------------------------------- /beer-catalog-workshop/assets/microcks-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-workshop/assets/microcks-deployment.png -------------------------------------------------------------------------------- /beer-catalog-workshop/assets/workshop-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/beer-catalog-workshop/assets/workshop-setup.png -------------------------------------------------------------------------------- /beer-catalog-workshop/documentation.html: -------------------------------------------------------------------------------- 1 | {% cdn_asset /swagger-ui/2.2.10/swagger-ui.js %} 2 | {% cdn_asset /swagger-ui/2.2.10/swagger-ui.css %} 3 | 4 | {% include 'shared/swagger_ui' %} 5 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /cloud-events-demo/animal-message-payload.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "acme.avro", 3 | "type": "record", 4 | "name": "Animal", 5 | "fields": [ 6 | {"name": "type", "type": {"name": "AnimalTypes", "type":"enum", "symbols": ["CAT", "HEDGEHOG", "SQUIRREL"]} }, 7 | {"name": "score", "type": "float"}, 8 | {"name": "name", "type": "string"}, 9 | {"name": "detectedOn", "type": "string"} 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /cloud-events-demo/animal-sensor-1.0.0-bin-asyncapi.yaml: -------------------------------------------------------------------------------- 1 | asyncapi: '2.2.0' 2 | info: 3 | title: Animal Sensor Binary API 4 | version: 1.0.0 5 | description: This service publishes events when it detect animals entering of exiting the camera frame 6 | channels: 7 | animals/detections: 8 | subscribe: 9 | message: 10 | $ref: '#/components/messages/AnimalMessage' 11 | components: 12 | messages: 13 | AnimalMessage: 14 | contentType: application/octet-stream 15 | schemaFormat: application/vnd.apache.avro+json;version=1.9.0 16 | traits: 17 | - $ref: 'https://raw.githubusercontent.com/microcks/microcks-quickstarters/main/cloud/cloudevents/cloudevents-v1.0.1-asyncapi-trait.yml' 18 | payload: 19 | $ref: './animal-message-payload.avsc#/Animal' 20 | examples: 21 | - name: Zaza 22 | summary: Detected pet 23 | headers: 24 | ce_specversion: '1.0.1' 25 | ce_type: AnimalInFrameEvent 26 | ce_source: /acme.com/animal-sensor/lbroudoux-garden 27 | ce_id: "{{uuid()}}" 28 | ce_time: "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}" 29 | content-type: application/avro 30 | payload: 31 | type: CAT 32 | score: 0.99 33 | name: Zaza 34 | detectedOn: "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}" 35 | - name: Sonic 36 | summary: Detected animal 37 | headers: 38 | ce_specversion: '1.0.1' 39 | ce_type: AnimalOutFrameEvent 40 | ce_source: /acme.com/animal-sensor/lbroudoux-garden 41 | ce_id: "{{uuid()}}" 42 | ce_time: "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}" 43 | content-type: application/avro 44 | payload: 45 | type: HEDGEHOG 46 | score: 0.80 47 | name: 'null' 48 | detectedOn: "{{now(yyyy-MM-dd'T'HH:mm:SS'Z')}}" 49 | -------------------------------------------------------------------------------- /cloud-events-demo/animal-sensor-structured-ce.json: -------------------------------------------------------------------------------- 1 | { 2 | "specversion": "1.0.1", 3 | "type": "MyCoolEvent", 4 | "source": "https://acme.com/cloudevents/MyCoolEventProducer", 5 | "id": "fd7b9654-5bf4-4a29-8b76-4d36267bc162", 6 | "time": "2021-10-30T17:31:00Z", 7 | "datacontenttype": "text/xml", 8 | "data": "" 9 | } 10 | -------------------------------------------------------------------------------- /contract-testing-demo-async/.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | node_modules/ 3 | dist/ 4 | 5 | # Visual Studio Code 6 | .vscode 7 | .factorypath 8 | 9 | # OSX 10 | .DS_Store 11 | 12 | # Vim 13 | *.swp 14 | *.swo 15 | 16 | # patch 17 | *.orig 18 | *.rej -------------------------------------------------------------------------------- /contract-testing-demo-async/Dockerfile-01: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-16-minimal:1-114 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Some version information 6 | LABEL io.k8s.description="API Contract Testing demonstration for Microcks" \ 7 | io.k8s.display-name="API Contract Testing demo" \ 8 | maintainer="Laurent Broudoux " 9 | 10 | # Set the running environment as production 11 | ENV PORT 3001 12 | 13 | # Define working directory 14 | ENV APP_ROOT=/app 15 | WORKDIR ${APP_ROOT} 16 | 17 | # root for build stages 18 | USER root 19 | 20 | # Copy files and install dependencies 21 | COPY /* ${APP_ROOT} 22 | COPY bin/ ${APP_ROOT}/bin 23 | RUN cd ${APP_ROOT} \ 24 | && npm install \ 25 | && rm -rdf ${APP_ROOT}/.npm /tmp/v8-compile-cache-0 26 | 27 | ### Setup user for build execution and application runtime 28 | ENV HOME=${APP_ROOT} 29 | RUN chmod -R u+x ${APP_ROOT}/bin && \ 30 | chgrp -R 0 ${APP_ROOT} && \ 31 | chmod -R g=u ${APP_ROOT} /etc/passwd 32 | 33 | ### Containers should NOT run as root as a good practice 34 | USER 1001 35 | 36 | # Executing defaults 37 | ENTRYPOINT [ "/app/bin/uid_entrypoint_01" ] 38 | EXPOSE 4001 -------------------------------------------------------------------------------- /contract-testing-demo-async/Dockerfile-02: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-16-minimal:1-114 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Some version information 6 | LABEL io.k8s.description="API Contract Testing demonstration for Microcks" \ 7 | io.k8s.display-name="API Contract Testing demo" \ 8 | maintainer="Laurent Broudoux " 9 | 10 | # Set the running environment as production 11 | ENV PORT 3002 12 | 13 | # Define working directory 14 | ENV APP_ROOT=/app 15 | WORKDIR ${APP_ROOT} 16 | 17 | # root for build stages 18 | USER root 19 | 20 | # Copy files and install dependencies 21 | COPY /* ${APP_ROOT} 22 | COPY bin/ ${APP_ROOT}/bin 23 | RUN cd ${APP_ROOT} \ 24 | && npm install \ 25 | && rm -rdf ${APP_ROOT}/.npm /tmp/v8-compile-cache-0 26 | 27 | ### Setup user for build execution and application runtime 28 | ENV HOME=${APP_ROOT} 29 | RUN chmod -R u+x ${APP_ROOT}/bin && \ 30 | chgrp -R 0 ${APP_ROOT} && \ 31 | chmod -R g=u ${APP_ROOT} /etc/passwd 32 | 33 | ### Containers should NOT run as root as a good practice 34 | USER 1001 35 | 36 | # Executing defaults 37 | ENTRYPOINT [ "/app/bin/uid_entrypoint_02" ] 38 | EXPOSE 4002 -------------------------------------------------------------------------------- /contract-testing-demo-async/bin/uid_entrypoint_01: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if ! whoami &> /dev/null; then 3 | if [ -w /etc/passwd ]; then 4 | echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 5 | fi 6 | fi 7 | /usr/bin/node /app/server-01.js 8 | -------------------------------------------------------------------------------- /contract-testing-demo-async/bin/uid_entrypoint_02: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if ! whoami &> /dev/null; then 3 | if [ -w /etc/passwd ]; then 4 | echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 5 | fi 6 | fi 7 | /usr/bin/node /app/server-02.js 8 | -------------------------------------------------------------------------------- /contract-testing-demo-async/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contract-testing-demo-async", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "contract-testing-demo-async", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "ws": "^7.4.6" 13 | } 14 | }, 15 | "node_modules/ws": { 16 | "version": "7.5.9", 17 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", 18 | "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", 19 | "engines": { 20 | "node": ">=8.3.0" 21 | }, 22 | "peerDependencies": { 23 | "bufferutil": "^4.0.1", 24 | "utf-8-validate": "^5.0.2" 25 | }, 26 | "peerDependenciesMeta": { 27 | "bufferutil": { 28 | "optional": true 29 | }, 30 | "utf-8-validate": { 31 | "optional": true 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contract-testing-demo-async/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contract-testing-demo-async", 3 | "version": "0.0.1", 4 | "description": "Demonstrate contract-testing on Async API with WebSocket", 5 | "main": "server-1.js", 6 | "scripts": { 7 | "start": "node server-01.js" 8 | }, 9 | "keywords": [ 10 | "api", 11 | "contract-test" 12 | ], 13 | "author": "laurent.broudoux@gmail.com", 14 | "license": "MIT", 15 | "dependencies": { 16 | "ws": "^7.4.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contract-testing-demo-async/pastry-orders-asyncapi.yml: -------------------------------------------------------------------------------- 1 | asyncapi: '2.6.0' 2 | id: 'urn:io.microcks.example.pastry-orders' 3 | info: 4 | title: Pastry orders API 5 | version: 0.1.0 6 | description: Sample AsyncAPI for Pastry order events 7 | defaultContentType: application/json 8 | channels: 9 | pastry/orders: 10 | description: The topic on which pastry orders events may be consumed 11 | subscribe: 12 | summary: Receive informations about pastry orders 13 | operationId: receivedPastryOrder 14 | message: 15 | $ref: '#/components/messages/PastryOrder' 16 | bindings: 17 | ws: 18 | method: POST 19 | components: 20 | messages: 21 | PastryOrder: 22 | payload: 23 | type: object 24 | additionalProperties: false 25 | required: 26 | - id 27 | - customerId 28 | - status 29 | - productQuantities 30 | properties: 31 | id: 32 | description: Unique identifier of order (guid) 33 | type: string 34 | customerId: 35 | description: Identifier of customer of this order (guid) 36 | type: string 37 | status: 38 | description: Status of Order 39 | enum: 40 | - CREATED 41 | - VALIDATED 42 | - CANCELED 43 | - FAILED 44 | type: string 45 | productQuantities: 46 | description: Desired products and quantities for this order 47 | type: array 48 | items: 49 | $ref: '#/components/schemas/ProductQuantity' 50 | examples: 51 | - Validated order: 52 | payload: 53 | id: 4dab240d-7847-4e25-8ef3-1530687650c8 54 | customerId: fe1088b3-9f30-4dc1-a93d-7b74f0a072b9 55 | status: VALIDATED 56 | productQuantities: 57 | - quantity: 2 58 | pastryName: Croissant 59 | - quantity: 1 60 | pastryName: Millefeuille 61 | schemas: 62 | ProductQuantity: 63 | type: object 64 | additionalProperties: false 65 | required: 66 | - quantity 67 | - pastryName 68 | properties: 69 | quantity: 70 | description: Desired quantity 71 | type: integer 72 | pastryName: 73 | description: Desired pastry name 74 | type: string 75 | -------------------------------------------------------------------------------- /contract-testing-demo-async/server-01.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | const https = require('https'); 5 | const url = require('url'); 6 | const WebSocket = require('ws'); 7 | 8 | // Initialize WebSocket server. 9 | var wss = new WebSocket.Server({ noServer: true }); 10 | 11 | wss = new WebSocket.Server({ port: 4001, path: "/websocket" }); 12 | 13 | console.log("Starting WebSocket server on ws://localhost:4001/websocket"); 14 | 15 | const getRandomId = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 16 | 17 | const createMessage = (id, customerId) => ({ 18 | id: `${id}`, 19 | customerId: `${customerId}`, 20 | //status: "VALIDATED", 21 | productQuantities: [ 22 | { 23 | quantity: 1, 24 | pastryName: "Tartelette Fraise" 25 | }, 26 | { 27 | quantity: 2, 28 | pastryName: "Donut" 29 | } 30 | ] 31 | }) 32 | 33 | const sendMessage = () => { 34 | var msg = createMessage(getRandomId(), getRandomId()); 35 | console.log("Sending to " + wss.clients.size + " clients"); 36 | wss.clients.forEach(function each(client) { 37 | if (client.readyState === WebSocket.OPEN) { 38 | client.send(JSON.stringify(msg)); 39 | } 40 | }); 41 | console.log(msg); 42 | } 43 | 44 | setInterval(sendMessage, 3000); -------------------------------------------------------------------------------- /contract-testing-demo-async/server-02.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | const https = require('https'); 5 | const url = require('url'); 6 | const WebSocket = require('ws'); 7 | 8 | // Initialize WebSocket server. 9 | var wss = new WebSocket.Server({ noServer: true }); 10 | 11 | wss = new WebSocket.Server({ port: 4002, path: "/websocket" }); 12 | 13 | console.log("Starting WebSocket server on ws://localhost:4002/websocket"); 14 | 15 | const getRandomId = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 16 | 17 | const createMessage = (id, customerId) => ({ 18 | id: `${id}`, 19 | customerId: `${customerId}`, 20 | status: "VALIDATED", 21 | productQuantities: [ 22 | { 23 | quantity: 1, 24 | pastryName: "Tartelette Fraise" 25 | }, 26 | { 27 | quantity: 2, 28 | pastryName: "Donut" 29 | } 30 | ] 31 | }) 32 | 33 | const sendMessage = () => { 34 | var msg = createMessage(getRandomId(), getRandomId()); 35 | console.log("Sending to " + wss.clients.size + " clients"); 36 | wss.clients.forEach(function each(client) { 37 | if (client.readyState === WebSocket.OPEN) { 38 | client.send(JSON.stringify(msg)); 39 | } 40 | }); 41 | console.log(msg); 42 | } 43 | 44 | setInterval(sendMessage, 3000); -------------------------------------------------------------------------------- /contract-testing-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | node_modules/ 3 | dist/ 4 | 5 | # Visual Studio Code 6 | .vscode 7 | .factorypath 8 | 9 | # OSX 10 | .DS_Store 11 | 12 | # Vim 13 | *.swp 14 | *.swo 15 | 16 | # patch 17 | *.orig 18 | *.rej -------------------------------------------------------------------------------- /contract-testing-demo/Dockerfile-01: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-16-minimal:1-114 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Some version information 6 | LABEL io.k8s.description="API Contract Testing demonstration for Microcks" \ 7 | io.k8s.display-name="API Contract Testing demo" \ 8 | maintainer="Laurent Broudoux " 9 | 10 | # Set the running environment as production 11 | ENV PORT 3001 12 | 13 | # Define working directory 14 | ENV APP_ROOT=/app 15 | WORKDIR ${APP_ROOT} 16 | 17 | # root for build stages 18 | USER root 19 | 20 | # Copy files and install dependencies 21 | COPY /* ${APP_ROOT} 22 | COPY bin/ ${APP_ROOT}/bin 23 | RUN cd ${APP_ROOT} \ 24 | && npm install \ 25 | && rm -rdf ${APP_ROOT}/.npm /tmp/v8-compile-cache-0 26 | 27 | ### Setup user for build execution and application runtime 28 | ENV HOME=${APP_ROOT} 29 | RUN chmod -R u+x ${APP_ROOT}/bin && \ 30 | chgrp -R 0 ${APP_ROOT} && \ 31 | chmod -R g=u ${APP_ROOT} /etc/passwd 32 | 33 | ### Containers should NOT run as root as a good practice 34 | USER 1001 35 | 36 | # Executing defaults 37 | ENTRYPOINT [ "/app/bin/uid_entrypoint_01" ] 38 | EXPOSE 3001 -------------------------------------------------------------------------------- /contract-testing-demo/Dockerfile-02: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-16-minimal:1-114 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Some version information 6 | LABEL io.k8s.description="API Contract Testing demonstration for Microcks" \ 7 | io.k8s.display-name="API Contract Testing demo" \ 8 | maintainer="Laurent Broudoux " 9 | 10 | # Set the running environment as production 11 | ENV PORT 3002 12 | 13 | # Define working directory 14 | ENV APP_ROOT=/app 15 | WORKDIR ${APP_ROOT} 16 | 17 | # root for build stages 18 | USER root 19 | 20 | # Copy files and install dependencies 21 | COPY /* ${APP_ROOT} 22 | COPY bin/ ${APP_ROOT}/bin 23 | RUN cd ${APP_ROOT} \ 24 | && npm install \ 25 | && rm -rdf ${APP_ROOT}/.npm /tmp/v8-compile-cache-0 26 | 27 | ### Setup user for build execution and application runtime 28 | ENV HOME=${APP_ROOT} 29 | RUN chmod -R u+x ${APP_ROOT}/bin && \ 30 | chgrp -R 0 ${APP_ROOT} && \ 31 | chmod -R g=u ${APP_ROOT} /etc/passwd 32 | 33 | ### Containers should NOT run as root as a good practice 34 | USER 1001 35 | 36 | # Executing defaults 37 | ENTRYPOINT [ "/app/bin/uid_entrypoint_02" ] 38 | EXPOSE 3002 -------------------------------------------------------------------------------- /contract-testing-demo/Dockerfile-03: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/nodejs-16-minimal:1-114 2 | 3 | MAINTAINER Laurent Broudoux 4 | 5 | # Some version information 6 | LABEL io.k8s.description="API Contract Testing demonstration for Microcks" \ 7 | io.k8s.display-name="API Contract Testing demo" \ 8 | maintainer="Laurent Broudoux " 9 | 10 | # Set the running environment as production 11 | ENV PORT 3003 12 | 13 | # Define working directory 14 | ENV APP_ROOT=/app 15 | WORKDIR ${APP_ROOT} 16 | 17 | # root for build stages 18 | USER root 19 | 20 | # Copy files and install dependencies 21 | COPY /* ${APP_ROOT} 22 | COPY bin/ ${APP_ROOT}/bin 23 | RUN cd ${APP_ROOT} \ 24 | && npm install \ 25 | && rm -rdf ${APP_ROOT}/.npm /tmp/v8-compile-cache-0 26 | 27 | ### Setup user for build execution and application runtime 28 | ENV HOME=${APP_ROOT} 29 | RUN chmod -R u+x ${APP_ROOT}/bin && \ 30 | chgrp -R 0 ${APP_ROOT} && \ 31 | chmod -R g=u ${APP_ROOT} /etc/passwd 32 | 33 | ### Containers should NOT run as root as a good practice 34 | USER 1001 35 | 36 | # Executing defaults 37 | ENTRYPOINT [ "/app/bin/uid_entrypoint_03" ] 38 | EXPOSE 3003 -------------------------------------------------------------------------------- /contract-testing-demo/bin/uid_entrypoint_01: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if ! whoami &> /dev/null; then 3 | if [ -w /etc/passwd ]; then 4 | echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 5 | fi 6 | fi 7 | /usr/bin/node /app/server-01.js 8 | -------------------------------------------------------------------------------- /contract-testing-demo/bin/uid_entrypoint_02: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if ! whoami &> /dev/null; then 3 | if [ -w /etc/passwd ]; then 4 | echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 5 | fi 6 | fi 7 | /usr/bin/node /app/server-02.js 8 | -------------------------------------------------------------------------------- /contract-testing-demo/bin/uid_entrypoint_03: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if ! whoami &> /dev/null; then 3 | if [ -w /etc/passwd ]; then 4 | echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 5 | fi 6 | fi 7 | /usr/bin/node /app/server-03.js 8 | -------------------------------------------------------------------------------- /contract-testing-demo/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: pastries-api 6 | app.kubernetes.io/component: pastries-api 7 | app.kubernetes.io/instance: pastries-api 8 | app.kubernetes.io/part-of: pastries-api 9 | name: pastries-api 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: pastries-api 15 | template: 16 | metadata: 17 | labels: 18 | app: pastries-api 19 | spec: 20 | containers: 21 | - name: pastries-api 22 | image: quay.io/microcks/contract-testing-demo:03 23 | imagePullPolicy: Always 24 | ports: 25 | - containerPort: 3003 26 | name: http 27 | protocol: TCP 28 | securityContext: 29 | privileged: false 30 | --- 31 | kind: Service 32 | apiVersion: v1 33 | metadata: 34 | name: pastries-api 35 | labels: 36 | app: pastries-api 37 | app.kubernetes.io/component: pastries-api 38 | app.kubernetes.io/instance: pastries-api 39 | app.kubernetes.io/part-of: pastries-api 40 | spec: 41 | ports: 42 | - name: 8080-tcp 43 | protocol: TCP 44 | port: 8080 45 | targetPort: 3003 46 | selector: 47 | app: pastries-api 48 | type: ClusterIP 49 | sessionAffinity: None 50 | --- 51 | kind: Route 52 | apiVersion: route.openshift.io/v1 53 | metadata: 54 | name: pastries-api 55 | labels: 56 | app: pastries-api 57 | app.kubernetes.io/component: pastries-api 58 | app.kubernetes.io/instance: pastries-api 59 | app.kubernetes.io/part-of: pastries-api 60 | annotations: 61 | openshift.io/host.generated: 'true' 62 | spec: 63 | to: 64 | kind: Service 65 | name: pastries-api 66 | weight: 100 67 | port: 68 | targetPort: 8080-tcp 69 | wildcardPolicy: None -------------------------------------------------------------------------------- /contract-testing-demo/kube-deployment-01.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: apipastries-app-01 5 | labels: 6 | app: apipastries-app-01 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: apipastries-app-01 12 | template: 13 | metadata: 14 | labels: 15 | app: apipastries-app-01 16 | spec: 17 | containers: 18 | - name: apipastries-app-01 19 | image: quay.io/microcks/contract-testing-demo:01 20 | ports: 21 | - name: http 22 | containerPort: 3001 23 | protocol: TCP 24 | --- 25 | kind: Service 26 | apiVersion: v1 27 | metadata: 28 | name: apipastries-app-01 29 | labels: 30 | app: apipastries-app-01 31 | spec: 32 | selector: 33 | app: apipastries-app-01 34 | ports: 35 | - protocol: TCP 36 | port: 3001 37 | targetPort: 3001 38 | type: ClusterIP 39 | sessionAffinity: None 40 | -------------------------------------------------------------------------------- /contract-testing-demo/kube-deployment-02.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: apipastries-app-02 5 | labels: 6 | app: apipastries-app-02 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: apipastries-app-02 12 | template: 13 | metadata: 14 | labels: 15 | app: apipastries-app-02 16 | spec: 17 | containers: 18 | - name: apipastries-app-02 19 | image: quay.io/microcks/contract-testing-demo:02 20 | ports: 21 | - name: http 22 | containerPort: 3002 23 | protocol: TCP 24 | --- 25 | kind: Service 26 | apiVersion: v1 27 | metadata: 28 | name: apipastries-app-02 29 | labels: 30 | app: apipastries-app-02 31 | spec: 32 | selector: 33 | app: apipastries-app-02 34 | ports: 35 | - protocol: TCP 36 | port: 3002 37 | targetPort: 3002 38 | type: ClusterIP 39 | sessionAffinity: None -------------------------------------------------------------------------------- /contract-testing-demo/kube-deployment-03.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: apipastries-app-03 5 | labels: 6 | app: apipastries-app-03 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: apipastries-app-03 12 | template: 13 | metadata: 14 | labels: 15 | app: apipastries-app-03 16 | spec: 17 | containers: 18 | - name: apipastries-app-03 19 | image: quay.io/microcks/contract-testing-demo:03 20 | ports: 21 | - name: http 22 | containerPort: 3003 23 | protocol: TCP 24 | --- 25 | kind: Service 26 | apiVersion: v1 27 | metadata: 28 | name: apipastries-app-03 29 | labels: 30 | app: apipastries-app-03 31 | spec: 32 | selector: 33 | app: apipastries-app-03 34 | ports: 35 | - protocol: TCP 36 | port: 3003 37 | targetPort: 3003 38 | type: ClusterIP 39 | sessionAffinity: None 40 | -------------------------------------------------------------------------------- /contract-testing-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contract-testing-demo", 3 | "version": "0.0.1", 4 | "description": "Demonstrate contract-testing levels of API", 5 | "main": "server-1.js", 6 | "scripts": { 7 | "start": "node server-01.js" 8 | }, 9 | "keywords": [ 10 | "api", 11 | "contract-test" 12 | ], 13 | "author": "laurent.broudoux@gmail.com", 14 | "license": "MIT", 15 | "dependencies": { 16 | "express": "^4.18.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contract-testing-demo/server-01.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | // Set default server port to 3001 4 | var port = process.env.PORT || 3001; 5 | 6 | const pastries = [ 7 | { 8 | "name": "Baba Rhum", 9 | "description": "Delicieux Baba au Rhum pas calorique du tout", 10 | "size": "L", 11 | "price": "3.2" 12 | }, 13 | { 14 | "name": "Divorces", 15 | "description": "Delicieux Divorces pas calorique du tout", 16 | "size": "M", 17 | "price": "2.8" 18 | }, 19 | { 20 | "name": "Tartelette Fraise", 21 | "description": "Delicieuse Tartelette aux Fraises fraiches", 22 | "size": "S", 23 | "price": "2" 24 | }, 25 | { 26 | "name": "Eclair Cafe", 27 | "description": "Delicieux Eclair au Cafe pas calorique du tout", 28 | "size": "M", 29 | "price": "2.5" 30 | }, 31 | { 32 | "name": "Eclair Chocolat", 33 | "description": "Delicieux Eclair au Chocolat pas calorique du tout", 34 | "size": "M", 35 | "price": "2.4" 36 | }, 37 | { 38 | "name": "Millefeuille", 39 | "description": "Delicieux Millefeuille pas calorique du tout", 40 | "size": "L", 41 | "price": "4.4" 42 | } 43 | ] 44 | 45 | app.get('/pastries', (req, res) => { 46 | res.send(pastries); 47 | }) 48 | 49 | app.get('/pastries/:name', (req, res) => { 50 | res.send(pastries[Math.floor(Math.random() * pastries.length)]); 51 | }) 52 | 53 | app.patch('/pastries/:name', (req, res) => { 54 | res.send(pastries[Math.floor(Math.random() * pastries.length)]); 55 | }) 56 | 57 | app.listen(port, () => { 58 | console.log(`Example app listening on port ${port}`) 59 | }) -------------------------------------------------------------------------------- /contract-testing-demo/server-02.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | // Set default server port to 3002 4 | var port = process.env.PORT || 3002; 5 | 6 | const pastries = [ 7 | { 8 | "name": "Baba Rhum", 9 | "description": "Delicieux Baba au Rhum pas calorique du tout", 10 | "size": "L", 11 | "price": 3.2, 12 | "status": "available" 13 | }, 14 | { 15 | "name": "Divorces", 16 | "description": "Delicieux Divorces pas calorique du tout", 17 | "size": "M", 18 | "price": 2.8, 19 | "status": "available" 20 | }, 21 | { 22 | "name": "Tartelette Fraise", 23 | "description": "Delicieuse Tartelette aux Fraises fraiches", 24 | "size": "S", 25 | "price": 2, 26 | "status": "available" 27 | }, 28 | { 29 | "name": "Eclair Cafe", 30 | "description": "Delicieux Eclair au Cafe pas calorique du tout", 31 | "size": "M", 32 | "price": 2.5, 33 | "status": "available" 34 | }, 35 | { 36 | "name": "Eclair Chocolat", 37 | "description": "Delicieux Eclair au Chocolat pas calorique du tout", 38 | "size": "M", 39 | "price": 2.4, 40 | "status": "unknown" 41 | }, 42 | { 43 | "name": "Millefeuille", 44 | "description": "Delicieux Millefeuille pas calorique du tout", 45 | "size": "L", 46 | "price": 4.4, 47 | "status": "available" 48 | } 49 | ] 50 | 51 | app.get('/pastries', (req, res) => { 52 | res.send(pastries); 53 | }) 54 | 55 | app.get('/pastries/:name', (req, res) => { 56 | res.send(pastries[Math.floor(Math.random() * pastries.length)]); 57 | }) 58 | 59 | app.patch('/pastries/:name', (req, res) => { 60 | res.send(pastries[Math.floor(Math.random() * pastries.length)]); 61 | }) 62 | 63 | app.listen(port, () => { 64 | console.log(`Example app listening on port ${port}`) 65 | }) -------------------------------------------------------------------------------- /contract-testing-demo/server-03.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | // Set default server port to 3003 4 | var port = process.env.PORT || 3003; 5 | 6 | app.use(express.json()) // for parsing application/json 7 | 8 | const pastries = [ 9 | { 10 | "name": "Baba Rhum", 11 | "description": "Delicieux Baba au Rhum pas calorique du tout", 12 | "size": "L", 13 | "price": 3.2, 14 | "status": "available" 15 | }, 16 | { 17 | "name": "Divorces", 18 | "description": "Delicieux Divorces pas calorique du tout", 19 | "size": "M", 20 | "price": 2.8, 21 | "status": "available" 22 | }, 23 | { 24 | "name": "Tartelette Fraise", 25 | "description": "Delicieuse Tartelette aux Fraises fraiches", 26 | "size": "S", 27 | "price": 2, 28 | "status": "available" 29 | }, 30 | { 31 | "name": "Eclair Cafe", 32 | "description": "Delicieux Eclair au Cafe pas calorique du tout", 33 | "size": "M", 34 | "price": 2.5, 35 | "status": "available" 36 | }, 37 | { 38 | "name": "Eclair Chocolat", 39 | "description": "Delicieux Eclair au Chocolat pas calorique du tout", 40 | "size": "M", 41 | "price": 2.4, 42 | "status": "unknown" 43 | }, 44 | { 45 | "name": "Millefeuille", 46 | "description": "Delicieux Millefeuille pas calorique du tout", 47 | "size": "L", 48 | "price": 4.4, 49 | "status": "available" 50 | } 51 | ] 52 | 53 | app.get('/pastries', (req, res) => { 54 | res.send(pastries.filter( 55 | pastry => pastry.size === req.query.size 56 | )); 57 | }) 58 | 59 | app.get('/pastries/:name', (req, res) => { 60 | const pastry = pastries.find(pastry => pastry.name === req.params.name); 61 | res.send(pastry); 62 | }) 63 | 64 | app.patch('/pastries/:name', (req, res) => { 65 | const pastry = pastries.find(pastry => pastry.name === req.params.name); 66 | Object.keys(req.body).map( 67 | (key, index) => pastry[key] = req.body[key] 68 | ) 69 | res.send(pastry); 70 | }) 71 | 72 | app.listen(port, () => { 73 | console.log(`Example app listening on port ${port}`) 74 | }) -------------------------------------------------------------------------------- /gitops-demo/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - microcks-operator.yaml 3 | - microcks-instance.yaml -------------------------------------------------------------------------------- /gitops-demo/base/microcks-instance.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: microcks.io/v1alpha1 2 | kind: Microcks 3 | metadata: 4 | name: microcks 5 | spec: 6 | version: 1.11.1 7 | microcks: 8 | url: microcks.m.minikube.local 9 | env: 10 | - name: KEYCLOAK_ENABLED 11 | value: "false" 12 | keycloak: 13 | install: false 14 | url: keycloak.m.minikube.local -------------------------------------------------------------------------------- /gitops-demo/overlays/minikube.local/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../base 3 | - microcks-apisource.yaml 4 | patches: 5 | - path: microcks-instance.yaml -------------------------------------------------------------------------------- /gitops-demo/overlays/minikube.local/microcks-apisource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: microcks.io/v1alpha1 2 | kind: APISource 3 | metadata: 4 | name: tests-artifacts 5 | annotations: 6 | microcks.io/instance: microcks 7 | spec: 8 | artifacts: 9 | - url: https://raw.githubusercontent.com/microcks/microcks/master/samples/APIPastry-openapi.yaml 10 | mainArtifact: true 11 | - url: https://raw.githubusercontent.com/microcks/microcks/master/samples/hello-v1.proto 12 | mainArtifact: true 13 | - url: https://raw.githubusercontent.com/microcks/microcks/master/samples/HelloService.metadata.yml 14 | mainArtifact: false 15 | - url: https://raw.githubusercontent.com/microcks/microcks/master/samples/HelloService.postman.json 16 | mainArtifact: false 17 | -------------------------------------------------------------------------------- /gitops-demo/overlays/minikube.local/microcks-instance.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: microcks.io/v1alpha1 2 | kind: Microcks 3 | metadata: 4 | name: microcks 5 | spec: 6 | microcks: 7 | url: microcks.m3.minikube.local -------------------------------------------------------------------------------- /graphql-federation-demo/.meshrc.yaml: -------------------------------------------------------------------------------- 1 | sources: 2 | - name: Pastries 3 | handler: 4 | openapi: 5 | source: ./contracts/apipastries-openapi.yaml 6 | #endpoint: http://localhost:8080/rest/API+Pastries/0.0.1 7 | endpoint: http://localhost:8585/rest/API+Pastries/0.0.1 8 | - name: Chiefs 9 | handler: 10 | grpc: 11 | source: ./contracts/chiefs-service.proto 12 | #endpoint: localhost:9090 13 | endpoint: localhost:8686 14 | - name: Stores 15 | handler: 16 | graphql: 17 | source: ./contracts/stores-graph.graphql 18 | #endpoint: http://localhost:8080/graphql/Store+Graph/1.0 19 | endpoint: http://localhost:8585/graphql/Store+Graph/1.0 20 | 21 | 22 | transforms: 23 | - filterSchema: 24 | filters: 25 | - Query.stores 26 | 27 | additionalTypeDefs: | 28 | extend type Store { 29 | pastrySells: [Sells!]! @resolveTo( 30 | sourceName: "Stores", 31 | sourceTypeName: "Query", 32 | sourceFieldName: "pastrySells", 33 | requiredSelectionSet: "{ id }", 34 | sourceArgs: { 35 | storeId: "{root.id}" 36 | } 37 | ) 38 | } 39 | extend type Sells { 40 | pastry: Pastry @resolveTo( 41 | sourceName: "Pastries", 42 | sourceTypeName: "Query", 43 | sourceFieldName: "GetPastryByName", 44 | requiredSelectionSet: "{ pastryId }", 45 | sourceArgs: { 46 | name: "{root.pastryId}" 47 | } 48 | ) 49 | } 50 | extend type Pastry { 51 | chief: io__microcks__chiefs__v1__Chief @resolveTo( 52 | sourceName: "Chiefs" 53 | sourceTypeName: "Query", 54 | sourceFieldName: "io_microcks_chiefs_v1_ChiefsService_GetChief", 55 | requiredSelectionSet: "{ chiefId }", 56 | sourceArgs: { 57 | input: { id: "{root.chiefId}" } 58 | } 59 | ) 60 | } -------------------------------------------------------------------------------- /graphql-federation-demo/contracts/chiefs-service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package io.microcks.chiefs.v1; 4 | 5 | message GetChiefRequest { 6 | int32 id = 1; 7 | } 8 | 9 | message Chief { 10 | int32 id = 1; 11 | string name = 2; 12 | string region = 3; 13 | } 14 | 15 | message ListChiefsRequest {} 16 | 17 | message ChiefsResponse { 18 | repeated Chief chiefs = 1; 19 | } 20 | 21 | service ChiefsService { 22 | rpc GetChief(GetChiefRequest) returns (Chief); 23 | rpc ListChiefs(ListChiefsRequest) returns (ChiefsResponse); 24 | } -------------------------------------------------------------------------------- /graphql-federation-demo/contracts/stores-graph.graphql: -------------------------------------------------------------------------------- 1 | # microcksId: Store Graph : 1.0 2 | type Store { 3 | id: ID! 4 | name: String! 5 | location: String! 6 | } 7 | 8 | type Sells { 9 | pastryId: ID! 10 | sellsCount: Int! 11 | monthYear: String 12 | storeId: ID! 13 | } 14 | 15 | type Query { 16 | stores: [Store!]! 17 | pastrySells(storeId: ID!): [Sells!]! 18 | } 19 | -------------------------------------------------------------------------------- /graphql-federation-demo/datasets/apipastries-examples.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mocks.microcks.io/v1alpha1 2 | kind: APIExamples 3 | metadata: 4 | name: API Pastries 5 | version: '0.0.1' 6 | operations: 7 | 'GET /pastries': 8 | pastries_xl: 9 | request: 10 | parameters: 11 | size: XL 12 | response: 13 | status: 200 14 | mediaType: application/json 15 | body: 16 | - name: Paris-Brest 17 | description: Delicieux Paris-Brest calorique du tout 18 | size: '{{ request.params[size] }}' 19 | price: 5.1 20 | status: '{{ randomValue(available,out_of_stock) }}' 21 | chiefId: 3 22 | 'GET /pastries/{name}': 23 | Paris-Brest: 24 | request: 25 | parameters: 26 | name: Paris-Brest 27 | response: 28 | status: 200 29 | mediaType: application/json 30 | body: |- 31 | { 32 | "name": "Paris-Brest", 33 | "description": "Delicieux Paris-Brest calorique du tout", 34 | "size": "XL", 35 | "price": 5.1, 36 | "status": "available", 37 | "chiefId": 3 38 | } 39 | -------------------------------------------------------------------------------- /graphql-federation-demo/datasets/chiefs-examples.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: mocks.microcks.io/v1alpha1 2 | kind: APIExamples 3 | metadata: 4 | name: io.microcks.chiefs.v1.ChiefsService 5 | version: v1 6 | operations: 7 | GetChief: 8 | Cyril Lignac: 9 | request: 10 | body: 11 | id: 1 12 | response: 13 | body: 14 | id: 1 15 | name: Cyril Lignac 16 | region: Languedoc 17 | Christophe Michalak: 18 | request: 19 | body: 20 | id: 2 21 | response: 22 | body: 23 | id: 2 24 | name: Christophe Michalak 25 | region: Ile de France 26 | Silamaka Soukouna: 27 | request: 28 | body: 29 | id: 3 30 | response: 31 | body: | 32 | { 33 | "id": 3, 34 | "name": "Silamaka Soukouna", 35 | "region": "Mali" 36 | } 37 | ListChiefs: 38 | All Chiefs: 39 | request: 40 | body: "" 41 | response: 42 | body: 43 | chiefs: 44 | - id: 1 45 | name: Cyril Lignac 46 | region: Languedoc 47 | - id: 2 48 | name: Christophe Michalak 49 | region: Ile de France 50 | - id: 3 51 | name: Silamaka Soukouna 52 | region: Mali 53 | 54 | -------------------------------------------------------------------------------- /graphql-federation-demo/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "jest"; 2 | 3 | const config: Config = { 4 | preset: "ts-jest", 5 | resetMocks: true, 6 | restoreMocks: true, 7 | }; 8 | 9 | export default config; -------------------------------------------------------------------------------- /graphql-federation-demo/mesh.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { DEFAULT_CLI_PARAMS, graphqlMesh, handleFatalError } from '@graphql-mesh/cli'; 4 | import { DefaultLogger } from '@graphql-mesh/utils'; 5 | 6 | import { MicrocksContainer, StartedMicrocksContainer } from '@microcks/microcks-testcontainers'; 7 | 8 | describe("MeshTests", () => { 9 | jest.setTimeout(180_000); 10 | 11 | const contractsDir = path.resolve(__dirname, ".", "contracts"); 12 | const datasetsDir = path.resolve(__dirname, ".", "datasets"); 13 | 14 | let container: StartedMicrocksContainer; 15 | 16 | beforeAll(async () => { 17 | // Starting application. 18 | console.log('Starting application...'); 19 | graphqlMesh(DEFAULT_CLI_PARAMS).catch(e => handleFatalError(e, new DefaultLogger(DEFAULT_CLI_PARAMS.initialLoggerPrefix))); 20 | 21 | console.log('Starting container...'); 22 | container = await new MicrocksContainer('quay.io/microcks/microcks-uber:nightly-native') 23 | .withMainArtifacts([ 24 | path.resolve(contractsDir, "apipastries-openapi.yaml"), 25 | path.resolve(contractsDir, "chiefs-service.proto"), 26 | path.resolve(contractsDir, "stores-graph.graphql") 27 | ]) 28 | .withSecondaryArtifacts([ 29 | path.resolve(datasetsDir, "apipastries-postman-collection.json"), 30 | path.resolve(datasetsDir, "apipastries-examples.yaml"), 31 | path.resolve(datasetsDir, "chiefs-examples.yaml"), 32 | path.resolve(datasetsDir, "stores-examples.yaml"), 33 | path.resolve(datasetsDir, "stores-recording.json") 34 | ]) 35 | .start(); 36 | }); 37 | 38 | it("should start, load artifacts and test mesh", async () => { 39 | console.log("Test") 40 | 41 | await delay(1000); 42 | }); 43 | 44 | afterAll(async () => { 45 | // Now stop the app, the containers and the network. 46 | console.log('Stopping application...'); 47 | console.log('Stopping container...'); 48 | await container.stop(); 49 | }); 50 | 51 | function delay(ms: number) { 52 | return new Promise( resolve => setTimeout(resolve, ms) ); 53 | } 54 | }); -------------------------------------------------------------------------------- /graphql-federation-demo/microcks.sh: -------------------------------------------------------------------------------- 1 | docker compose -f microcks.yaml up -------------------------------------------------------------------------------- /graphql-federation-demo/microcks.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | importer: 3 | depends_on: 4 | - microcks 5 | restart: on-failure 6 | image: quay.io/microcks/microcks-cli:latest 7 | volumes: 8 | - "./contracts:/con" 9 | - "./datasets:/data" 10 | entrypoint: 11 | - microcks-cli 12 | - import 13 | - '/con/apipastries-openapi.yaml:true,/con/chiefs-service.proto:true,/con/stores-graph.graphql:true,/data/apipastries-postman-collection.json:false,/data/apipastries-examples.yaml:false,/data/chiefs-examples.yaml:false,/data/stores-examples.yaml:false,/data/stores-recording.json:false' 14 | - --microcksURL=http://microcks:8080/api 15 | - --insecure 16 | - --keycloakClientId=foo 17 | - --keycloakClientSecret=bar 18 | 19 | microcks: 20 | image: quay.io/microcks/microcks-uber:nightly-native 21 | ports: 22 | - "8585:8080" 23 | - "8686:9090" -------------------------------------------------------------------------------- /graphql-federation-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "mesh dev", 4 | "build": "echo \"noop\";", 5 | "test": "jest" 6 | }, 7 | "dependencies": { 8 | "@graphql-mesh/cli": "^0.95.4", 9 | "@graphql-mesh/graphql": "^0.102.4", 10 | "@graphql-mesh/grpc": "^0.104.5", 11 | "@graphql-mesh/openapi": "^0.106.6", 12 | "@graphql-mesh/transform-filter-schema": "^0.102.4", 13 | "graphql": "^16.9.0" 14 | }, 15 | "devDependencies": { 16 | "@microcks/microcks-testcontainers": "^0.2.5", 17 | "@types/jest": "^29.5.12", 18 | "jest": "^29.7.0", 19 | "ts-jest": "^29.2.5", 20 | "ts-node": "^10.9.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /graphql-federation-demo/standalone/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/graphql-federation-demo/standalone/.DS_Store -------------------------------------------------------------------------------- /graphql-federation-demo/standalone/config/application.properties: -------------------------------------------------------------------------------- 1 | # AI Copilot configuration properties 2 | ai-copilot.enabled=true 3 | ai-copilot.implementation=openai 4 | ai-copilot.openai.api-key= 5 | ai-copilot.openai.timeout=30 6 | ai-copilot.openai.maxTokens=3000 -------------------------------------------------------------------------------- /graphql-federation-demo/standalone/config/features.properties: -------------------------------------------------------------------------------- 1 | features.feature.ai-copilot.enabled=true -------------------------------------------------------------------------------- /graphql-federation-demo/standalone/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | app: 5 | image: quay.io/microcks/microcks-uber:nightly 6 | container_name: microcks 7 | volumes: 8 | - "./config:/deployments/config" 9 | - "./data:/deployments/data" 10 | ports: 11 | - "8080:8080" 12 | - "9090:9090" 13 | environment: 14 | - SPRING_PROFILES_ACTIVE=uber 15 | - TEST_CALLBACK_URL=http://microcks:8080 16 | - SERVICES_UPDATE_INTERVAL=0 0 0/2 * * * 17 | - MONGODB_STORAGE_PATH=/deployments/data/microcks.mv 18 | #- MAX_UPLOAD_FILE_SIZE=3MB 19 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /grpc-hello-world-demo/.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | 42 | # Plugin directory 43 | /.quarkus/cli/plugins/ 44 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/README.md: -------------------------------------------------------------------------------- 1 | # quarkus-grpc-hello 2 | 3 | This project uses Quarkus, the Supersonic Subatomic Java Framework. 4 | 5 | If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . 6 | 7 | ## Running the application in dev mode 8 | 9 | You can run your application in dev mode that enables live coding using: 10 | ```shell script 11 | ./mvnw compile quarkus:dev 12 | ``` 13 | 14 | > **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/. 15 | 16 | ## Packaging and running the application 17 | 18 | The application can be packaged using: 19 | ```shell script 20 | ./mvnw package 21 | ``` 22 | It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory. 23 | Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory. 24 | 25 | The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`. 26 | 27 | If you want to build an _über-jar_, execute the following command: 28 | ```shell script 29 | ./mvnw package -Dquarkus.package.jar.type=uber-jar 30 | ``` 31 | 32 | The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`. 33 | 34 | ## Creating a native executable 35 | 36 | You can create a native executable using: 37 | ```shell script 38 | ./mvnw package -Dnative 39 | ``` 40 | 41 | Or, if you don't have GraalVM installed, you can run the native executable build in a container using: 42 | ```shell script 43 | ./mvnw package -Dnative -Dquarkus.native.container-build=true 44 | ``` 45 | 46 | You can then execute your native executable with: `./target/quarkus-grpc-hello-1.0.0-SNAPSHOT-runner` 47 | 48 | If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling. 49 | 50 | ## Related Guides 51 | 52 | 53 | ## Provided Code 54 | 55 | ### gRPC 56 | 57 | Create your first gRPC service 58 | 59 | [Related guide section...](https://quarkus.io/guides/grpc-getting-started) 60 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Dnative 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-grpc-hello . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-grpc-hello 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./mvnw package -Dnative 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/quarkus-grpc-hello . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-grpc-hello 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:2.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root target/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] 31 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/src/main/java/io/github/microcks/samples/HelloGrpcService.java: -------------------------------------------------------------------------------- 1 | package io.github.microcks.samples; 2 | 3 | import io.quarkus.grpc.GrpcService; 4 | 5 | import io.smallrye.mutiny.Uni; 6 | 7 | @GrpcService 8 | public class HelloGrpcService implements HelloService { 9 | 10 | @Override 11 | public Uni greeting(HelloRequest request) { 12 | return Uni.createFrom().item("Hello " + request.getFirstname() + " " + request.getLastname() + "!") 13 | .map(msg -> HelloResponse.newBuilder().setGreeting(msg).build()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/src/main/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "io.github.microcks.samples"; 5 | option java_outer_classname = "HelloGrpcProto"; 6 | 7 | package io.github.microcks.grpc.hello.v1; 8 | 9 | message HelloRequest { 10 | string firstname = 1; 11 | string lastname = 2; 12 | } 13 | 14 | message HelloResponse { 15 | string greeting = 1; 16 | } 17 | 18 | service HelloService { 19 | rpc greeting(HelloRequest) returns (HelloResponse); 20 | } 21 | -------------------------------------------------------------------------------- /grpc-hello-world-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Configuration file 2 | quarkus.http.port=8383 3 | quarkus.http.cors=true -------------------------------------------------------------------------------- /grpc-hello-world-demo/src/test/java/io/github/microcks/samples/HelloGrpcServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.github.microcks.samples; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.time.Duration; 6 | 7 | import io.quarkus.grpc.GrpcClient; 8 | import io.quarkus.test.junit.QuarkusTest; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | @QuarkusTest 13 | class HelloGrpcServiceTest { 14 | @GrpcClient 15 | HelloService helloGrpc; 16 | 17 | @Test 18 | void testHello() { 19 | HelloResponse reply = helloGrpc 20 | .greeting(HelloRequest.newBuilder().setFirstname("Thomas").setLastname("Anderson").build()).await().atMost(Duration.ofSeconds(5)); 21 | assertEquals("Hello Thomas Anderson!", reply.getGreeting()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jestrunner.codeLensSelector": "" 3 | } -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/assets/order-service-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/shift-left-demo/nest-order-service/assets/order-service-architecture.png -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/assets/order-service-ecosystem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/shift-left-demo/nest-order-service/assets/order-service-ecosystem.png -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/microcks-docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | importer: 3 | depends_on: 4 | - microcks 5 | restart: on-failure 6 | image: quay.io/microcks/microcks-cli:latest 7 | volumes: 8 | - "./test/resources:/resources" 9 | entrypoint: 10 | - microcks-cli 11 | - import 12 | - '/resources/order-service-openapi.yml:true,/resources/apipastries-openapi.yml:true,/resources/apipastries-postman-collection.json:false' 13 | - --microcksURL=http://microcks:8080/api 14 | - --insecure 15 | - --keycloakClientId=foo 16 | - --keycloakClientSecret=bar 17 | 18 | microcks: 19 | image: quay.io/microcks/microcks-uber:nightly 20 | ports: 21 | - "9090:8080" -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/microcks.sh: -------------------------------------------------------------------------------- 1 | docker compose -f microcks-docker-compose.yml up -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-order-service", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/config": "^3.1.1", 25 | "@nestjs/core": "^10.0.0", 26 | "@nestjs/mapped-types": "*", 27 | "@nestjs/platform-express": "^10.0.0", 28 | "reflect-metadata": "^0.1.13", 29 | "rxjs": "^7.8.1" 30 | }, 31 | "devDependencies": { 32 | "@microcks/microcks-testcontainers": "0.2.2", 33 | "@nestjs/cli": "^10.0.0", 34 | "@nestjs/schematics": "^10.0.0", 35 | "@nestjs/testing": "^10.0.0", 36 | "@types/express": "^4.17.17", 37 | "@types/jest": "^29.5.2", 38 | "@types/node": "^20.3.1", 39 | "@types/supertest": "^2.0.12", 40 | "@typescript-eslint/eslint-plugin": "^6.0.0", 41 | "@typescript-eslint/parser": "^6.0.0", 42 | "eslint": "^8.42.0", 43 | "eslint-config-prettier": "^9.0.0", 44 | "eslint-plugin-prettier": "^5.0.0", 45 | "find-free-ports": "^3.1.1", 46 | "jest": "^29.5.0", 47 | "prettier": "^3.0.0", 48 | "source-map-support": "^0.5.21", 49 | "supertest": "^6.3.3", 50 | "ts-jest": "^29.1.0", 51 | "ts-loader": "^9.4.3", 52 | "ts-node": "^10.9.1", 53 | "tsconfig-paths": "^4.2.0", 54 | "typescript": "^5.1.3" 55 | }, 56 | "jest": { 57 | "moduleFileExtensions": [ 58 | "js", 59 | "json", 60 | "ts" 61 | ], 62 | "rootDir": "src", 63 | "testRegex": ".*\\.spec\\.ts$", 64 | "transform": { 65 | "^.+\\.(t|j)s$": "ts-jest" 66 | }, 67 | "collectCoverageFrom": [ 68 | "**/*.(t|j)s" 69 | ], 70 | "coverageDirectory": "../coverage", 71 | "testEnvironment": "node" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { OrderModule } from './order/order.module'; 6 | import { PastryModule } from './pastry/pastry.module'; 7 | 8 | @Module({ 9 | imports: [OrderModule], 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/dto/order-info.dto.ts: -------------------------------------------------------------------------------- 1 | import { ProductQuantity } from "../entities/product-quantity.entity" 2 | 3 | export class OrderInfoDto { 4 | customerId: string 5 | productQuantities: ProductQuantity[] 6 | totalPrice: number 7 | } 8 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/dto/unavailable-product.dto.ts: -------------------------------------------------------------------------------- 1 | export class UnavailableProduct { 2 | productName: string 3 | details: string 4 | 5 | constructor(productName: string, details: string) { 6 | this.productName = productName; 7 | this.details = details; 8 | } 9 | } -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/entities/order-status.ts: -------------------------------------------------------------------------------- 1 | export enum OrderStatus { 2 | CREATED = 'CREATED', 3 | VALIDATED = 'VALIDATED', 4 | CANCELED = 'CANCELED', 5 | FAILED = 'FAILED' 6 | } -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/entities/order.entity.ts: -------------------------------------------------------------------------------- 1 | import { uid } from 'uid'; 2 | 3 | import { OrderStatus } from "./order-status" 4 | import { ProductQuantity } from "./product-quantity.entity" 5 | 6 | export class Order { 7 | id: string = uid() 8 | status: OrderStatus = OrderStatus.CREATED 9 | customerId: string 10 | productQuantities: ProductQuantity[] 11 | totalPrice: number 12 | } 13 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/entities/product-quantity.entity.ts: -------------------------------------------------------------------------------- 1 | export class ProductQuantity { 2 | productName: string 3 | quantity: number 4 | } -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/order.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule } from '@nestjs/config'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | 4 | import { OrderController } from './order.controller'; 5 | import { OrderService } from './order.service'; 6 | import { PastryModule } from '../pastry/pastry.module'; 7 | 8 | describe('OrderController', () => { 9 | let controller: OrderController; 10 | 11 | beforeEach(async () => { 12 | const module: TestingModule = await Test.createTestingModule({ 13 | imports: [ConfigModule, PastryModule], 14 | controllers: [OrderController], 15 | providers: [OrderService], 16 | }).compile(); 17 | 18 | controller = module.get(OrderController); 19 | }); 20 | 21 | it('should be defined', () => { 22 | expect(controller).toBeDefined(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/order.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, Res, HttpStatus } from '@nestjs/common'; 2 | import { Response } from 'express'; 3 | 4 | import { OrderService } from './order.service'; 5 | import { UnavailablePastryError } from './unavailable-pastry.error'; 6 | 7 | import { OrderInfoDto } from './dto/order-info.dto'; 8 | import { UnavailableProduct } from './dto/unavailable-product.dto'; 9 | 10 | 11 | @Controller('orders') 12 | export class OrderController { 13 | constructor(private readonly orderService: OrderService) {} 14 | 15 | @Post() 16 | async create(@Body() orderInfo: OrderInfoDto, @Res() res: Response) { 17 | let result = null; 18 | try { 19 | result = await this.orderService.create(orderInfo); 20 | } catch (err) { 21 | if (err instanceof UnavailablePastryError) { 22 | return res.status(HttpStatus.UNPROCESSABLE_ENTITY) 23 | .json(new UnavailableProduct(err.product, err.message)) 24 | .send(); 25 | } else { 26 | return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(); 27 | } 28 | } 29 | 30 | return res.status(HttpStatus.CREATED).json(result).send(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/order.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { OrderService } from './order.service'; 3 | import { OrderController } from './order.controller'; 4 | import { PastryModule } from '../pastry/pastry.module'; 5 | 6 | @Module({ 7 | imports: [PastryModule], 8 | controllers: [OrderController], 9 | providers: [OrderService], 10 | }) 11 | export class OrderModule {} 12 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/order.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule } from '@nestjs/config'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | 4 | import { OrderService } from './order.service'; 5 | import { PastryModule } from '../pastry/pastry.module'; 6 | 7 | describe('OrderService', () => { 8 | let service: OrderService; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | imports: [ConfigModule, PastryModule], 13 | providers: [OrderService], 14 | }).compile(); 15 | 16 | service = module.get(OrderService); 17 | }); 18 | 19 | it('should be defined', () => { 20 | expect(service).toBeDefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/order/order.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { OrderInfoDto } from './dto/order-info.dto'; 4 | 5 | import { Order } from './entities/order.entity'; 6 | import { UnavailablePastryError } from './unavailable-pastry.error' 7 | 8 | import { Pastry } from '../pastry/pastry.dto'; 9 | import { PastryService } from '../pastry/pastry.service'; 10 | 11 | @Injectable() 12 | export class OrderService { 13 | constructor(private readonly pastryService: PastryService) {} 14 | 15 | /** 16 | * 17 | * @param orderInfo 18 | * @returns 19 | * @throws {UnavailablePastryError} 20 | */ 21 | async create(orderInfo: OrderInfoDto): Promise { 22 | let pastryPromises: Promise[] = []; 23 | 24 | for (var i=0; i[] = await Promise.allSettled(pastryPromises) 30 | for (var i=0; i ({ 9 | 'pastries.baseurl': 'http://localhost:9090/rest/API+Pastries/0.0.1' 10 | })], 11 | })], 12 | providers: [PastryService], 13 | exports: [PastryService] 14 | }) 15 | export class PastryModule {} 16 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/pastry/pastry.service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | 5 | import { MicrocksContainer, StartedMicrocksContainer } from "@microcks/microcks-testcontainers"; 6 | 7 | import { Pastry } from './pastry.dto'; 8 | import { PastryService } from './pastry.service'; 9 | 10 | describe('PastryService', () => { 11 | jest.setTimeout(180_000); 12 | const resourcesDir = path.resolve(__dirname, "../..", "test/resources"); 13 | 14 | let container: StartedMicrocksContainer; 15 | let service: PastryService; 16 | 17 | beforeAll(async () => { 18 | // Start container and load artifacts. 19 | container = await new MicrocksContainer() 20 | .withMainArtifacts([path.resolve(resourcesDir, 'apipastries-openapi.yml')]) 21 | .withSecondaryArtifacts([path.resolve(resourcesDir, 'apipastries-postman-collection.json')]) 22 | .start(); 23 | 24 | const module: TestingModule = await Test.createTestingModule({ 25 | imports: [ConfigModule.forRoot({ 26 | load: [() => ({ 27 | 'pastries.baseurl': container.getRestMockEndpoint('API Pastries', '0.0.1') 28 | })], 29 | })], 30 | providers: [PastryService], 31 | }).compile(); 32 | 33 | service = module.get(PastryService); 34 | }); 35 | 36 | afterAll(async () => { 37 | // Now stop the container. 38 | await container.stop(); 39 | }); 40 | 41 | it('should retrieve pastry by name', async () => { 42 | let pastry: Pastry = await service.getPastry('Millefeuille'); 43 | expect(pastry.name).toBe("Millefeuille"); 44 | expect(pastry.status).toBe("available"); 45 | 46 | pastry = await service.getPastry('Eclair Cafe'); 47 | expect(pastry.name).toBe("Eclair Cafe"); 48 | expect(pastry.status).toBe("available"); 49 | 50 | pastry = await service.getPastry('Eclair Chocolat'); 51 | expect(pastry.name).toBe("Eclair Chocolat"); 52 | expect(pastry.status).toBe("unknown"); 53 | }); 54 | 55 | it('should retrieve pastries by size', async () => { 56 | let pastries: Pastry[] = await service.getPastries('S'); 57 | expect(pastries.length).toBe(1); 58 | 59 | pastries = await service.getPastries('M'); 60 | expect(pastries.length).toBe(2); 61 | 62 | pastries = await service.getPastries('L'); 63 | expect(pastries.length).toBe(2); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/src/pastry/pastry.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Pastry } from './pastry.dto'; 4 | 5 | @Injectable() 6 | export class PastryService { 7 | 8 | private readonly resourceUrl: string; 9 | 10 | constructor(configService: ConfigService) { 11 | this.resourceUrl = configService.get('pastries.baseurl'); 12 | //console.log("this.resourceUrl: " + this.resourceUrl); 13 | } 14 | 15 | async getPastry(name: string): Promise { 16 | //console.log("Fetching " + this.resourceUrl + '/pastries/' + name); 17 | return fetch(this.resourceUrl + '/pastries/' + name) 18 | .then(response => { 19 | if (!response.ok) { 20 | throw new Error('Error while reading Pastry having name ' + name + ', code: ' + response.status); 21 | } 22 | return response.json() as Promise; 23 | }); 24 | } 25 | 26 | async getPastries(size: string): Promise { 27 | return fetch(this.resourceUrl + '/pastries?size=' + size) 28 | .then(response => { 29 | if (!response.ok) { 30 | throw new Error('Error while reading Pastries having size ' + size + ', code: ' + response.status); 31 | } 32 | return response.json() as Promise; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from '../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /shift-left-demo/nest-order-service/test/resources/order-service-postman-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "46a4acbb-b729-4a8d-9ce2-f5ddc186e2c8", 4 | "name": "Order Service API", 5 | "description": "version=0.1.0 - Oorder Service collection", 6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 7 | "_exporter_id": "1278651", 8 | "_collection_link": "https://www.postman.com/lbroudoux/workspace/public-workspace/collection/1278651-46a4acbb-b729-4a8d-9ce2-f5ddc186e2c8?action=share&source=collection_link&creator=1278651" 9 | }, 10 | "item": [ 11 | { 12 | "name": "orders", 13 | "item": [ 14 | { 15 | "name": "Create Order", 16 | "event": [ 17 | { 18 | "listen": "test", 19 | "script": { 20 | "exec": [ 21 | "var requestProductQuantities = JSON.parse(pm.request.body.raw).productQuantities;", 22 | "", 23 | "if (pm.response.code === 201) {", 24 | " pm.test(\"Correct products and quantities in order\", function () {", 25 | " var order = pm.response.json();", 26 | " var productQuantities = order.productQuantities", 27 | " pm.expect(productQuantities).to.be.an(\"array\");", 28 | " pm.expect(productQuantities.length).to.eql(requestProductQuantities.length);", 29 | " for (let i=0; i order(OrderInfo info) { 27 | Order createdOrder = null; 28 | try { 29 | createdOrder = service.placeOrder(info); 30 | } catch (UnavailablePastryException upe) { 31 | // We have to return a 422 (unprocessable) with correct expected type. 32 | //return ResponseBuilder.create(422).build(); 33 | return ResponseBuilder.create(422) 34 | .entity(new UnavailableProduct(upe.getProduct(), upe.getMessage())) 35 | .build(); 36 | } catch (Exception e) { 37 | return ResponseBuilder.serverError().build(); 38 | } 39 | // We can return a 201 with created entity. 40 | return ResponseBuilder.create(Status.CREATED.getStatusCode()) 41 | .entity(createdOrder) 42 | .build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/client/PastryAPIClient.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.client; 2 | 3 | import org.acme.order.client.model.Pastry; 4 | import jakarta.ws.rs.GET; 5 | import jakarta.ws.rs.Path; 6 | import jakarta.ws.rs.PathParam; 7 | import jakarta.ws.rs.QueryParam; 8 | import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * PastryAPIClient is responsible for requesting the product/stock management system (aka the Pastry registry) 14 | * using its REST API. It should take care of serializing entities and Http params as required by the 3rd party API. 15 | * @author laurent 16 | */ 17 | @Path("/pastries") 18 | @RegisterRestClient 19 | public interface PastryAPIClient { 20 | 21 | @GET 22 | @Path("/{name}") 23 | Pastry getPastry(@PathParam("name") String name); 24 | 25 | @GET 26 | List listPastries(@QueryParam("size") String size); 27 | } 28 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/client/model/Pastry.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.client.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public record Pastry(String name, String description, String size, double price, String status) { 7 | } 8 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/service/UnavailablePastryException.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service; 2 | 3 | public class UnavailablePastryException extends Exception { 4 | 5 | private String product; 6 | 7 | public UnavailablePastryException(String product, String message) { 8 | super(message); 9 | this.product = product; 10 | } 11 | 12 | public String getProduct() { 13 | return this.product; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/service/model/Order.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public class Order { 7 | 8 | private String id; 9 | private OrderStatus status; 10 | private String customerId; 11 | private List productQuantities; 12 | private Double totalPrice; 13 | 14 | 15 | public Order() { 16 | this.id = String.valueOf(UUID.randomUUID()); 17 | this.status = OrderStatus.CREATED; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public void setId(String id) { 25 | this.id = id; 26 | } 27 | 28 | public OrderStatus getStatus() { 29 | return status; 30 | } 31 | 32 | public void setStatus(OrderStatus status) { 33 | this.status = status; 34 | } 35 | 36 | public String getCustomerId() { 37 | return customerId; 38 | } 39 | 40 | public void setCustomerId(String customerId) { 41 | this.customerId = customerId; 42 | } 43 | 44 | public List getProductQuantities() { 45 | return productQuantities; 46 | } 47 | 48 | public void setProductQuantities(List productQuantities) { 49 | this.productQuantities = productQuantities; 50 | } 51 | 52 | public Double getTotalPrice() { 53 | return totalPrice; 54 | } 55 | 56 | public void setTotalPrice(Double expectedPrice) { 57 | this.totalPrice = expectedPrice; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/service/model/OrderInfo.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import java.util.List; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public record OrderInfo(String customerId, List productQuantities, Double totalPrice) { 9 | } 10 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/service/model/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | public enum OrderStatus { 4 | CREATED, 5 | VALIDATED, 6 | CANCELED, 7 | FAILED 8 | } 9 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/service/model/ProductQuantity.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | public record ProductQuantity(String productName, int quantity) { 4 | } 5 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/java/org/acme/order/service/model/UnavailableProduct.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | public class UnavailableProduct { 4 | private String productName; 5 | private String details; 6 | 7 | public UnavailableProduct(String productName, String details) { 8 | this.productName = productName; 9 | this.details = details; 10 | } 11 | 12 | public String getProductName() { 13 | return productName; 14 | } 15 | 16 | public String getDetails() { 17 | return details; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # You can customize your test port 2 | quarkus.http.test-port=8282 3 | 4 | # You can explicitly disable microcks devservices. 5 | quarkus.microcks.devservices.enabled=true 6 | 7 | # Now that we have auto-discovery of artifacts in 0.1.3, you no longer need to specify 8 | # primary and secondary artifacts. But you can still use them if you need fine control. 9 | #quarkus.microcks.devservices.artifacts.primaries=target/classes/order-service-openapi.yaml,target/test-classes/third-parties/apipastries-openapi.yaml,target/test-classes/third-parties/hello-v1.proto 10 | #quarkus.microcks.devservices.artifacts.secondaries=target/test-classes/third-parties/apipastries-postman-collection.json,target/test-classes/third-parties/HelloService-postman-collection.json,target/test-classes/third-parties/HelloService-metadata.yml 11 | 12 | quarkus.microcks.devservices.image-name=quay.io/microcks/microcks-uber:nightly 13 | 14 | # Specify here the Mock URL provided by microcks devservices, referencing the quarkus.microcks.default.http 15 | quarkus.rest-client."org.acme.order.client.PastryAPIClient".url=${quarkus.microcks.default.http}/rest/API+Pastries/0.0.1 16 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/test/java/org/acme/order/api/OrderResourceContractTests.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.api; 2 | 3 | import io.github.microcks.testcontainers.MicrocksContainer; 4 | import io.github.microcks.testcontainers.model.TestRequest; 5 | import io.github.microcks.testcontainers.model.TestResult; 6 | import io.github.microcks.testcontainers.model.TestRunnerType; 7 | 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import io.quarkus.test.junit.QuarkusTest; 10 | import jakarta.inject.Inject; 11 | import org.eclipse.microprofile.config.inject.ConfigProperty; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | @QuarkusTest 18 | public class OrderResourceContractTests { 19 | 20 | @ConfigProperty(name= "quarkus.http.test-port") 21 | int quarkusHttpPort; 22 | 23 | @ConfigProperty(name= "quarkus.microcks.default.http") 24 | String microcksContainerUrl; 25 | 26 | @Inject 27 | ObjectMapper mapper; 28 | 29 | @Test 30 | public void testOpenAPIContract() throws Exception { 31 | // Ask for an Open API conformance to be launched. 32 | TestRequest testRequest = new TestRequest.Builder() 33 | .serviceId("Order Service API:0.1.0") 34 | .runnerType(TestRunnerType.OPEN_API_SCHEMA.name()) 35 | .testEndpoint("http://host.testcontainers.internal:" + quarkusHttpPort + "/api") 36 | .build(); 37 | 38 | TestResult testResult = MicrocksContainer.testEndpoint(microcksContainerUrl, testRequest); 39 | 40 | // You may inspect complete response object with following: 41 | //System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testResult)); 42 | 43 | assertTrue(testResult.isSuccess()); 44 | assertEquals(1, testResult.getTestCaseResults().size()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/test/java/org/acme/order/client/PastryAPIClientTests.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.client; 2 | 3 | import org.acme.order.client.model.Pastry; 4 | import org.eclipse.microprofile.rest.client.inject.RestClient; 5 | import org.junit.jupiter.api.Test; 6 | import io.quarkus.test.junit.QuarkusTest; 7 | 8 | import jakarta.inject.Inject; 9 | 10 | import java.util.List; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @QuarkusTest 15 | public class PastryAPIClientTests { 16 | 17 | @Inject 18 | @RestClient 19 | PastryAPIClient client; 20 | 21 | @Test 22 | public void testGetPastries() { 23 | // Test our API client and check that arguments and responses are correctly serialized. 24 | List pastries = client.listPastries("S"); 25 | assertEquals(1, pastries.size()); 26 | 27 | pastries = client.listPastries("M"); 28 | assertEquals(2, pastries.size()); 29 | 30 | pastries = client.listPastries("L"); 31 | assertEquals(2, pastries.size()); 32 | } 33 | 34 | @Test 35 | public void testGetPastry() { 36 | // Test our API client and check that arguments and responses are correctly serialized. 37 | Pastry pastry = client.getPastry("Millefeuille"); 38 | assertEquals("Millefeuille", pastry.name()); 39 | assertEquals("available", pastry.status()); 40 | 41 | pastry = client.getPastry("Eclair Cafe"); 42 | assertEquals("Eclair Cafe", pastry.name()); 43 | assertEquals("available", pastry.status()); 44 | 45 | pastry = client.getPastry("Eclair Chocolat"); 46 | assertEquals("Eclair Chocolat", pastry.name()); 47 | assertEquals("unknown", pastry.status()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/test/resources/third-parties/HelloService-metadata.yml: -------------------------------------------------------------------------------- 1 | apiVersion: mocks.microcks.io/v1alpha1 2 | kind: APIMetadata 3 | metadata: 4 | name: HelloService 5 | version: v1 6 | labels: 7 | domain: samples 8 | status: GA 9 | operations: 10 | 'greeting': 11 | dispatcher: JSON_BODY 12 | dispatcherRules: |- 13 | { 14 | "exp": "/firstname", 15 | "operator": "equals", 16 | "cases": { 17 | "Laurent": "Laurent", 18 | "default": "John" 19 | } 20 | } -------------------------------------------------------------------------------- /shift-left-demo/quarkus-order-service/src/test/resources/third-parties/hello-v1.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package io.github.microcks.grpc.hello.v1; 4 | 5 | option java_multiple_files = true; 6 | 7 | message HelloRequest { 8 | string firstname = 1; 9 | string lastname = 2; 10 | } 11 | 12 | message HelloResponse { 13 | string greeting = 1; 14 | } 15 | 16 | service HelloService { 17 | rpc greeting(HelloRequest) returns (HelloResponse); 18 | } -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/java 3 | { 4 | "name": "Spring Boot Order Service", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", 7 | 8 | "features": { 9 | "ghcr.io/devcontainers/features/java:1": { 10 | "version": "17", 11 | "installMaven": "true", 12 | "installGradle": "false" 13 | }, 14 | "ghcr.io/devcontainers/features/docker-in-docker:2": {} 15 | }, 16 | 17 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 18 | "forwardPorts": [ 19 | 9090 // The Microcks Http port onxe started with postCreateCommand 20 | ], 21 | 22 | // Use 'postCreateCommand' to run commands after the container is created. 23 | "postCreateCommand": "docker compose -f microcks-docker-compose.yml up -d" 24 | 25 | // Configure tool-specific properties. 26 | // "customizations": {}, 27 | 28 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 29 | // "remoteUser": "root" 30 | } 31 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/shift-left-demo/spring-boot-order-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/assets/order-service-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/shift-left-demo/spring-boot-order-service/assets/order-service-architecture.png -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/assets/order-service-ecosystem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/shift-left-demo/spring-boot-order-service/assets/order-service-ecosystem.png -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/microcks-docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | importer: 3 | depends_on: 4 | - microcks 5 | restart: on-failure 6 | image: quay.io/microcks/microcks-cli:latest 7 | volumes: 8 | - "./src/main/resources:/resources" 9 | - "./src/test/resources/third-parties:/third-parties" 10 | entrypoint: 11 | - microcks-cli 12 | - import 13 | - '/resources/order-service-openapi.yaml:true,/third-parties/apipastries-openapi.yaml:true,/third-parties/apipastries-postman-collection.json:false' 14 | - --microcksURL=http://microcks:8080/api 15 | - --insecure 16 | - --keycloakClientId=foo 17 | - --keycloakClientSecret=bar 18 | 19 | microcks: 20 | image: quay.io/microcks/microcks-uber:nightly 21 | ports: 22 | - "9090:8080" -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/microcks-test.sh: -------------------------------------------------------------------------------- 1 | docker run -it quay.io/microcks/microcks-cli:latest microcks-cli test 'Order Service API:0.1.0' \ 2 | http://host.docker.internal:8080/api OPEN_API_SCHEMA \ 3 | --microcksURL=http://host.docker.internal:9090/api/ \ 4 | --keycloakClientId=foo --keycloakClientSecret=bar --insecure --waitFor=8sec 5 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/microcks.sh: -------------------------------------------------------------------------------- 1 | docker compose -f microcks-docker-compose.yml up -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.2 9 | 10 | 11 | org.acme 12 | order-service 13 | 0.0.1-SNAPSHOT 14 | order-service 15 | Simple Spring Boot Order Service 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-test 32 | test 33 | 34 | 35 | 36 | io.github.microcks 37 | microcks-testcontainers 38 | 0.2.1 39 | test 40 | 41 | 42 | org.testcontainers 43 | junit-jupiter 44 | 1.18.3 45 | test 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-maven-plugin 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/OrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | package org.acme.order; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.scheduling.annotation.EnableAsync; 7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 | 9 | import java.util.concurrent.Executor; 10 | 11 | @SpringBootApplication 12 | @EnableAsync 13 | public class OrderServiceApplication { 14 | 15 | @Bean 16 | public Executor asyncExecutor() { 17 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 18 | return executor; 19 | } 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(OrderServiceApplication.class, args); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/api/OrderController.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.api; 2 | 3 | import org.acme.order.service.UnavailablePastryException; 4 | import org.acme.order.service.model.Order; 5 | import org.acme.order.service.model.OrderInfo; 6 | import org.acme.order.service.OrderService; 7 | import org.acme.order.service.model.UnavailableProduct; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | /** 17 | * OrderController is responsible for exposing the REST API for the Order Service. It should take 18 | * care of serialization, business rules mapping to model types and Http status codes. 19 | * @author laurent 20 | */ 21 | @RestController 22 | @RequestMapping("/api/orders") 23 | public class OrderController { 24 | 25 | @Autowired 26 | OrderService service; 27 | 28 | @PostMapping() 29 | public ResponseEntity order(@RequestBody OrderInfo info) { 30 | Order createdOrder = null; 31 | try { 32 | createdOrder = service.placeOrder(info); 33 | } catch (UnavailablePastryException upe) { 34 | // We have to return a 422 (unprocessable) with correct expected type. 35 | //return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); 36 | return new ResponseEntity<>( 37 | new UnavailableProduct(upe.getProduct(), upe.getMessage()), 38 | HttpStatus.UNPROCESSABLE_ENTITY); 39 | } catch (Exception e) { 40 | return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); 41 | } 42 | // We can return a 201 with created entity. 43 | return new ResponseEntity<>(createdOrder, HttpStatus.CREATED); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/client/PastryAPIClient.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.client; 2 | 3 | import org.acme.order.client.model.Pastry; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.core.ParameterizedTypeReference; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * PastryAPIClient is responsible for requesting the product/stock management system (aka the Pastry registry) 15 | * using its REST API. It should take care of serializing entities and Http params as required by the 3rd party API. 16 | * @author laurent 17 | */ 18 | @Component 19 | public class PastryAPIClient { 20 | 21 | RestTemplate restTemplate = new RestTemplate(); 22 | 23 | @Value("${pastries.baseUrl}") 24 | private final String resourceUrl = null; 25 | 26 | public Pastry getPastry(String name) { 27 | return restTemplate.getForObject(resourceUrl + "/pastries/" + name, Pastry.class); 28 | } 29 | 30 | public List listPastries(String size) { 31 | /* 32 | // At first, I thought that uriVariables were working that way... 33 | ResponseEntity> responseEntity = 34 | restTemplate.exchange( 35 | resourceUrl + "/pastries", 36 | HttpMethod.GET, 37 | null, 38 | new ParameterizedTypeReference>() {}, 39 | Map.of("size", size) 40 | ); 41 | */ 42 | 43 | // ... but then realized it was that way! Thanks to unit test. 44 | ResponseEntity> responseEntity = 45 | restTemplate.exchange( 46 | resourceUrl + "/pastries?size={size}", 47 | HttpMethod.GET, 48 | null, 49 | new ParameterizedTypeReference>() {}, 50 | size 51 | ); 52 | return responseEntity.getBody(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/client/model/Pastry.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.client.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public record Pastry(String name, String description, String size, double price, String status) { 7 | } 8 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/UnavailablePastryException.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service; 2 | 3 | public class UnavailablePastryException extends Exception { 4 | 5 | private String product; 6 | 7 | public UnavailablePastryException(String product, String message) { 8 | super(message); 9 | this.product = product; 10 | } 11 | 12 | public String getProduct() { 13 | return this.product; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/model/Order.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public class Order { 7 | 8 | private String id; 9 | private OrderStatus status; 10 | private String customerId; 11 | private List productQuantities; 12 | private Double totalPrice; 13 | 14 | 15 | public Order() { 16 | this.id = String.valueOf(UUID.randomUUID()); 17 | this.status = OrderStatus.CREATED; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public void setId(String id) { 25 | this.id = id; 26 | } 27 | 28 | public OrderStatus getStatus() { 29 | return status; 30 | } 31 | 32 | public void setStatus(OrderStatus status) { 33 | this.status = status; 34 | } 35 | 36 | public String getCustomerId() { 37 | return customerId; 38 | } 39 | 40 | public void setCustomerId(String customerId) { 41 | this.customerId = customerId; 42 | } 43 | 44 | public List getProductQuantities() { 45 | return productQuantities; 46 | } 47 | 48 | public void setProductQuantities(List productQuantities) { 49 | this.productQuantities = productQuantities; 50 | } 51 | 52 | public Double getTotalPrice() { 53 | return totalPrice; 54 | } 55 | 56 | public void setTotalPrice(Double expectedPrice) { 57 | this.totalPrice = expectedPrice; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/model/OrderInfo.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import java.util.List; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public record OrderInfo(String customerId, List productQuantities, Double totalPrice) { 9 | } 10 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/model/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | public enum OrderStatus { 4 | CREATED, 5 | VALIDATED, 6 | CANCELED, 7 | FAILED 8 | } 9 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/model/ProductQuantity.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | public record ProductQuantity(String productName, int quantity) { 4 | } 5 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/java/org/acme/order/service/model/UnavailableProduct.java: -------------------------------------------------------------------------------- 1 | package org.acme.order.service.model; 2 | 3 | public class UnavailableProduct { 4 | private String productName; 5 | private String details; 6 | 7 | public UnavailableProduct(String productName, String details) { 8 | this.productName = productName; 9 | this.details = details; 10 | } 11 | 12 | public String getProductName() { 13 | return productName; 14 | } 15 | 16 | public String getDetails() { 17 | return details; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jackson.default-property-inclusion=non_null 2 | 3 | # This is the base URL for Pastries API (intended to be replaced with environment dependant value) 4 | # On developer laptop, we'll use endpoint served by local Microcks started with `./microcks.sh` script. 5 | pastries.baseUrl=http://localhost:9090/rest/API Pastries/0.0.1 6 | -------------------------------------------------------------------------------- /shift-left-demo/spring-boot-order-service/src/main/resources/order-service-postman-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "46a4acbb-b729-4a8d-9ce2-f5ddc186e2c8", 4 | "name": "Order Service API", 5 | "description": "version=0.1.0 - Oorder Service collection", 6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 7 | "_exporter_id": "1278651", 8 | "_collection_link": "https://www.postman.com/lbroudoux/workspace/public-workspace/collection/1278651-46a4acbb-b729-4a8d-9ce2-f5ddc186e2c8?action=share&source=collection_link&creator=1278651" 9 | }, 10 | "item": [ 11 | { 12 | "name": "orders", 13 | "item": [ 14 | { 15 | "name": "Create Order", 16 | "event": [ 17 | { 18 | "listen": "test", 19 | "script": { 20 | "exec": [ 21 | "var requestProductQuantities = JSON.parse(pm.request.body.raw).productQuantities;", 22 | "", 23 | "if (pm.response.code === 201) {", 24 | " pm.test(\"Correct products and quantities in order\", function () {", 25 | " var order = pm.response.json();", 26 | " var productQuantities = order.productQuantities", 27 | " pm.expect(productQuantities).to.be.an(\"array\");", 28 | " pm.expect(productQuantities.length).to.eql(requestProductQuantities.length);", 29 | " for (let i=0; i url); 41 | } 42 | 43 | @Test 44 | public void testGetPastries() { 45 | // Test our API client and check that arguments and responses are correctly serialized. 46 | List pastries = client.listPastries("S"); 47 | assertEquals(1, pastries.size()); 48 | 49 | pastries = client.listPastries("M"); 50 | assertEquals(2, pastries.size()); 51 | 52 | pastries = client.listPastries("L"); 53 | assertEquals(2, pastries.size()); 54 | } 55 | 56 | @Test 57 | public void testGetPastry() { 58 | // Test our API client and check that arguments and responses are correctly serialized. 59 | Pastry pastry = client.getPastry("Millefeuille"); 60 | assertEquals("Millefeuille", pastry.name()); 61 | assertEquals("available", pastry.status()); 62 | 63 | pastry = client.getPastry("Eclair Cafe"); 64 | assertEquals("Eclair Cafe", pastry.name()); 65 | assertEquals("available", pastry.status()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /user-registration-demo/api-contracts/UserRegistrationAPI-openapi-1.0.0.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.2 3 | info: 4 | title: User registration API 5 | version: 1.0.0 6 | description: APi used for registering new users in the system 7 | paths: 8 | /register: 9 | post: 10 | requestBody: 11 | description: A User registration request 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/UserRegistration' 16 | examples: 17 | chuck: 18 | value: 19 | fullName: Chuck Norris 20 | email: chuck@norris.com 21 | age: 23 22 | required: true 23 | responses: 24 | "201": 25 | content: 26 | application/json: 27 | schema: 28 | $ref: '#/components/schemas/ApprovedUserRegistration' 29 | examples: 30 | chuck: 31 | value: 32 | id: '{{ randomString(32) }}' 33 | fullName: '{{ request.body/fullName }}' 34 | email: '{{ request.body/email }}' 35 | age: 23 36 | registrationDate: '{{ now() }}' 37 | description: An approved User registration completed with id and date 38 | operationId: CreateUserRegistration 39 | summary: Create a new registration 40 | components: 41 | schemas: 42 | UserRegistration: 43 | title: Root Type for UserRegistration 44 | description: "" 45 | type: object 46 | properties: 47 | fullName: 48 | type: string 49 | email: 50 | type: string 51 | age: 52 | type: integer 53 | example: 54 | fullName: Firstname Lastname 55 | email: john@example.com 56 | age: 42 57 | ApprovedUserRegistration: 58 | title: Root Type for UserRegistration 59 | description: "" 60 | required: 61 | - id 62 | - registrationDate 63 | type: object 64 | properties: 65 | fullName: 66 | type: string 67 | email: 68 | type: string 69 | age: 70 | type: integer 71 | id: 72 | type: string 73 | registrationDate: 74 | type: string 75 | example: 76 | id: Wen7JHPUzriJxbmw9btmFhZypjPYUM8B 77 | fullName: Firstname Lastname 78 | email: john@example.com 79 | age: 42 80 | regsitrationDate: "1600244138045" 81 | -------------------------------------------------------------------------------- /user-registration-demo/api-contracts/UserSignedUpAPI-asyncapi-0.1.1.yml: -------------------------------------------------------------------------------- 1 | asyncapi: '2.0.0' 2 | id: 'urn:io.microcks.example.user-signedup' 3 | info: 4 | title: User signed-up API 5 | version: 0.1.1 6 | description: Sample AsyncAPI for user signedup events 7 | defaultContentType: application/json 8 | channels: 9 | user/signedup: 10 | description: The topic on which user signed up events may be consumed 11 | subscribe: 12 | summary: Receive informations about user signed up 13 | operationId: receivedUserSIgnedUp 14 | message: 15 | description: An event describing that a user just signed up. 16 | traits: 17 | - $ref: '#/components/messageTraits/commonHeaders' 18 | payload: 19 | type: object 20 | additionalProperties: false 21 | properties: 22 | id: 23 | type: string 24 | sendAt: 25 | type: string 26 | fullName: 27 | type: string 28 | email: 29 | type: string 30 | format: email 31 | age: 32 | type: integer 33 | minimum: 18 34 | examples: 35 | - laurent: 36 | summary: Example for Laurent user 37 | headers: |- 38 | {"my-app-header": 23} 39 | payload: |- 40 | {"id": "{{randomString(32)}}", "sendAt": "{{now()}}", "fullName": "Laurent Broudoux", "email": "laurent@microcks.io", "age": 41} 41 | - john: 42 | summary: Example for John Doe user 43 | headers: 44 | my-app-header: 24 45 | payload: 46 | id: '{{randomString(32)}}' 47 | sendAt: '{{now()}}' 48 | fullName: John Doe 49 | email: john@microcks.io 50 | age: 36 51 | components: 52 | messageTraits: 53 | commonHeaders: 54 | headers: 55 | type: object 56 | properties: 57 | my-app-header: 58 | type: integer 59 | minimum: 0 60 | maximum: 100 -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .project 3 | .classpath 4 | .settings/ 5 | bin/ 6 | 7 | # IntelliJ 8 | .idea 9 | *.ipr 10 | *.iml 11 | *.iws 12 | 13 | # NetBeans 14 | nb-configuration.xml 15 | 16 | # Visual Studio Code 17 | .vscode 18 | .factorypath 19 | 20 | # OSX 21 | .DS_Store 22 | 23 | # Vim 24 | *.swp 25 | *.swo 26 | 27 | # patch 28 | *.orig 29 | *.rej 30 | 31 | # Maven 32 | target/ 33 | pom.xml.tag 34 | pom.xml.releaseBackup 35 | pom.xml.versionsBackup 36 | release.properties -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/.java-version: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/user-registration-demo/api-implementations/quarkus-user-registration/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/README.md: -------------------------------------------------------------------------------- 1 | # user-registration project 2 | 3 | This project uses Quarkus, the Supersonic Subatomic Java Framework. 4 | 5 | If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . 6 | 7 | ## Running the application in dev mode 8 | 9 | You can run your application in dev mode that enables live coding using: 10 | ``` 11 | ./mvnw quarkus:dev 12 | ``` 13 | 14 | ## Packaging and running the application 15 | 16 | The application can be packaged using `./mvnw package`. 17 | It produces the `user-registration-1.0.0-SNAPSHOT-runner.jar` file in the `/target` directory. 18 | Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory. 19 | 20 | The application is now runnable using `java -jar target/user-registration-1.0.0-SNAPSHOT-runner.jar`. 21 | 22 | ## Creating a native executable 23 | 24 | You can create a native executable using: `./mvnw package -Pnative`. 25 | 26 | Or, if you don't have GraalVM installed, you can run the native executable build in a container using: `./mvnw package -Pnative -Dquarkus.native.container-build=true`. 27 | 28 | You can then execute your native executable with: `./target/user-registration-1.0.0-SNAPSHOT-runner` 29 | 30 | If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image. -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: user-registration 6 | app.kubernetes.io/component: user-registration 7 | app.kubernetes.io/instance: user-registration 8 | app.kubernetes.io/part-of: user-registration 9 | name: user-registration 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: user-registration 15 | template: 16 | metadata: 17 | labels: 18 | app: user-registration 19 | spec: 20 | containers: 21 | - name: user-registration 22 | image: quay.io/microcks/quarkus-user-registration:latest 23 | imagePullPolicy: Always 24 | env: 25 | - name: QUARKUS_PROFILE 26 | value: kube 27 | resources: 28 | requests: 29 | cpu: 250m 30 | memory: 300Mi 31 | limits: 32 | cpu: 500m 33 | memory: 400Mi 34 | ports: 35 | - containerPort: 8383 36 | name: http 37 | protocol: TCP 38 | securityContext: 39 | privileged: false 40 | --- 41 | kind: Service 42 | apiVersion: v1 43 | metadata: 44 | name: user-registration 45 | labels: 46 | app: user-registration 47 | app.kubernetes.io/component: user-registration 48 | app.kubernetes.io/instance: user-registration 49 | app.kubernetes.io/part-of: user-registration 50 | spec: 51 | ports: 52 | - name: 8080-tcp 53 | protocol: TCP 54 | port: 8080 55 | targetPort: 8383 56 | selector: 57 | app: user-registration 58 | type: ClusterIP 59 | sessionAffinity: None 60 | --- 61 | kind: Route 62 | apiVersion: route.openshift.io/v1 63 | metadata: 64 | name: user-registration 65 | labels: 66 | app: user-registration 67 | app.kubernetes.io/component: user-registration 68 | app.kubernetes.io/instance: user-registration 69 | app.kubernetes.io/part-of: user-registration 70 | annotations: 71 | openshift.io/host.generated: 'true' 72 | spec: 73 | to: 74 | kind: Service 75 | name: user-registration 76 | weight: 100 77 | port: 78 | targetPort: 8080-tcp 79 | wildcardPolicy: None -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/docker/Dockerfile.fast-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # mvn package -Dquarkus.package.type=fast-jar 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.fast-jar -t quarkus/user-registration-fast-jar . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/user-registration-fast-jar 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/user-registration-fast-jar 22 | # 23 | ### 24 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 25 | 26 | ARG JAVA_PACKAGE=java-11-openjdk-headless 27 | ARG RUN_JAVA_VERSION=1.3.8 28 | 29 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 30 | 31 | # Install java and the run-java script 32 | # Also set up permissions for user `1001` 33 | RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 34 | && microdnf update \ 35 | && microdnf clean all \ 36 | && mkdir /deployments \ 37 | && chown 1001 /deployments \ 38 | && chmod "g+rwX" /deployments \ 39 | && chown 1001:root /deployments \ 40 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 41 | && chown 1001 /deployments/run-java.sh \ 42 | && chmod 540 /deployments/run-java.sh \ 43 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security 44 | 45 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 46 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 47 | 48 | # We make four distinct layers so if there are application changes the library layers can be re-used 49 | COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ 50 | COPY --chown=1001 target/quarkus-app/*.jar /deployments/ 51 | COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ 52 | COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ 53 | 54 | EXPOSE 8080 55 | USER 1001 56 | 57 | ENTRYPOINT [ "/deployments/run-java.sh" ] -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # mvn package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/user-registration-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/user-registration-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/user-registration-jvm 22 | # 23 | ### 24 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 25 | 26 | ARG JAVA_PACKAGE=java-11-openjdk-headless 27 | ARG RUN_JAVA_VERSION=1.3.8 28 | 29 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 30 | 31 | # Install java and the run-java script 32 | # Also set up permissions for user `1001` 33 | RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 34 | && microdnf update \ 35 | && microdnf clean all \ 36 | && mkdir /deployments \ 37 | && chown 1001 /deployments \ 38 | && chmod "g+rwX" /deployments \ 39 | && chown 1001:root /deployments \ 40 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 41 | && chown 1001 /deployments/run-java.sh \ 42 | && chmod 540 /deployments/run-java.sh \ 43 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security 44 | 45 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 46 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 47 | 48 | COPY target/lib/* /deployments/lib/ 49 | COPY target/*-runner.jar /deployments/app.jar 50 | 51 | EXPOSE 8080 52 | USER 1001 53 | 54 | ENTRYPOINT [ "/deployments/run-java.sh" ] -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # mvn package -Pnative -Dquarkus.native.container-build=true 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quay.io/microcks/quarkus-user-registration . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quay.io/microcks/quarkus-user-registration 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/java/io/github/microcks/samples/registration/ApprovedUserRegistration.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Laurent BROUDOUX 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package io.github.microcks.samples.registration; 25 | 26 | import io.quarkus.runtime.annotations.RegisterForReflection; 27 | 28 | /** 29 | * @author laurent 30 | */ 31 | @RegisterForReflection 32 | public class ApprovedUserRegistration extends UserRegistration { 33 | 34 | private String id; 35 | private String registrationDate; 36 | 37 | public ApprovedUserRegistration(String id, UserRegistration source) { 38 | this.id = id; 39 | this.setFullName(source.getFullName()); 40 | this.setEmail(source.getEmail()); 41 | this.setAge(source.getAge()); 42 | this.registrationDate = String.valueOf(System.currentTimeMillis()); 43 | } 44 | 45 | public ApprovedUserRegistration(String id, String fullName, String email, int age) { 46 | this.id = id; 47 | this.setFullName(fullName); 48 | this.setEmail(email); 49 | this.setAge(age); 50 | this.registrationDate = String.valueOf(System.currentTimeMillis()); 51 | } 52 | 53 | public String getId() { 54 | return id; 55 | } 56 | public void setId(String id) { 57 | this.id = id; 58 | } 59 | 60 | public String getRegistrationDate() { 61 | return registrationDate; 62 | } 63 | public void setRegistrationDate(String registrationDate) { 64 | this.registrationDate = registrationDate; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/java/io/github/microcks/samples/registration/UserRegistration.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Laurent BROUDOUX 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package io.github.microcks.samples.registration; 25 | 26 | import io.quarkus.runtime.annotations.RegisterForReflection; 27 | 28 | /** 29 | * @author laurent 30 | */ 31 | @RegisterForReflection 32 | public class UserRegistration { 33 | 34 | private String fullName; 35 | private String email; 36 | private int age; 37 | 38 | public UserRegistration() { 39 | } 40 | 41 | public String getFullName() { 42 | return fullName; 43 | } 44 | public void setFullName(String fullName) { 45 | this.fullName = fullName; 46 | } 47 | 48 | public String getEmail() { 49 | return email; 50 | } 51 | public void setEmail(String email) { 52 | this.email = email; 53 | } 54 | 55 | public int getAge() { 56 | return age; 57 | } 58 | public void setAge(int age) { 59 | this.age = age; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/java/io/github/microcks/samples/registration/UserSignedUpEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Laurent BROUDOUX 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package io.github.microcks.samples.registration; 25 | 26 | import io.quarkus.runtime.annotations.RegisterForReflection; 27 | /** 28 | * @author laurent 29 | */ 30 | @RegisterForReflection 31 | public class UserSignedUpEvent { 32 | 33 | private String id; 34 | private String fullName; 35 | private String email; 36 | private int age; 37 | private String sendAt; 38 | 39 | public UserSignedUpEvent() { 40 | } 41 | 42 | public String getId() { 43 | return id; 44 | } 45 | public void setId(String id) { 46 | this.id = id; 47 | } 48 | 49 | public String getFullName() { 50 | return fullName; 51 | } 52 | public void setFullName(String fullName) { 53 | this.fullName = fullName; 54 | } 55 | 56 | public String getEmail() { 57 | return email; 58 | } 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public int getAge() { 64 | return age; 65 | } 66 | public void setAge(int age) { 67 | this.age = age; 68 | } 69 | 70 | public String getSendAt() { 71 | return sendAt; 72 | } 73 | public void setSendAt(String sendAt) { 74 | this.sendAt = sendAt; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /user-registration-demo/api-implementations/quarkus-user-registration/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Configuration file 2 | # key = value 3 | quarkus.http.port=8383 4 | 5 | # Enable CORS for being invoked from UI 6 | quarkus.http.cors=true 7 | 8 | # Configure Kafka broker address 9 | kafka.bootstrap-service=localhost:9092 10 | %kube.kafka.bootstrap-service=my-cluster-kafka-bootstrap:9092 -------------------------------------------------------------------------------- /user-registration-demo/assets/user-registration-broker-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/user-registration-demo/assets/user-registration-broker-secret.png -------------------------------------------------------------------------------- /user-registration-demo/assets/user-registration-tekton-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcks/api-lifecycle/24d982e49bb40da188dac5d04e575efd55aa783f/user-registration-demo/assets/user-registration-tekton-pipeline.png -------------------------------------------------------------------------------- /user-registration-demo/kafka-broker-kubernetes.yml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1beta1 2 | kind: Kafka 3 | metadata: 4 | name: my-cluster 5 | spec: 6 | kafka: 7 | config: 8 | offsets.topic.replication.factor: 3 9 | transaction.state.log.replication.factor: 3 10 | transaction.state.log.min.isr: 2 11 | log.message.format.version: '2.5' 12 | version: 2.5.0 13 | storage: 14 | type: ephemeral 15 | replicas: 3 16 | listeners: 17 | plain: {} 18 | tls: {} 19 | external: 20 | type: ingress 21 | configuration: 22 | bootstrap: 23 | host: "my-cluster-kafka-bootstrap-user-registration.KUBE_APPS_URL" 24 | brokers: 25 | - broker: 0 26 | host: "my-cluster-kafka-0-user-registration.KUBE_APPS_URL" 27 | - broker: 1 28 | host: "my-cluster-kafka-0-user-registration.KUBE_APPS_URL" 29 | - broker: 3 30 | host: "my-cluster-kafka-0-user-registration.KUBE_APPS_URL" 31 | entityOperator: 32 | topicOperator: 33 | reconciliationIntervalSeconds: 90 34 | userOperator: 35 | reconciliationIntervalSeconds: 120 36 | zookeeper: 37 | storage: 38 | type: ephemeral 39 | replicas: 3 -------------------------------------------------------------------------------- /user-registration-demo/kafka-broker-openshift-beta2.yml: -------------------------------------------------------------------------------- 1 | kind: Kafka 2 | apiVersion: kafka.strimzi.io/v1beta2 3 | metadata: 4 | name: my-cluster 5 | spec: 6 | entityOperator: 7 | topicOperator: {} 8 | userOperator: {} 9 | kafka: 10 | config: 11 | offsets.topic.replication.factor: 1 12 | transaction.state.log.replication.factor: 1 13 | transaction.state.log.min.isr: 1 14 | listeners: 15 | - name: plain 16 | port: 9092 17 | type: internal 18 | tls: false 19 | - name: tls 20 | port: 9093 21 | type: internal 22 | tls: true 23 | - name: external 24 | port: 9094 25 | tls: true 26 | type: route 27 | replicas: 3 28 | storage: 29 | type: ephemeral 30 | zookeeper: 31 | replicas: 3 32 | storage: 33 | type: ephemeral -------------------------------------------------------------------------------- /user-registration-demo/kafka-broker-openshift.yml: -------------------------------------------------------------------------------- 1 | apiVersion: kafka.strimzi.io/v1beta1 2 | kind: Kafka 3 | metadata: 4 | name: my-cluster 5 | spec: 6 | kafka: 7 | config: 8 | offsets.topic.replication.factor: 3 9 | transaction.state.log.replication.factor: 3 10 | transaction.state.log.min.isr: 2 11 | log.message.format.version: '2.5' 12 | version: 2.5.0 13 | storage: 14 | type: ephemeral 15 | replicas: 3 16 | listeners: 17 | plain: {} 18 | tls: {} 19 | external: 20 | type: route 21 | entityOperator: 22 | topicOperator: 23 | reconciliationIntervalSeconds: 90 24 | userOperator: 25 | reconciliationIntervalSeconds: 120 26 | zookeeper: 27 | storage: 28 | type: ephemeral 29 | replicas: 3 -------------------------------------------------------------------------------- /user-registration-demo/microcks-keycloak-client-secret.yml: -------------------------------------------------------------------------------- 1 | kind: Secret 2 | apiVersion: v1 3 | type: Opaque 4 | metadata: 5 | name: microcks-keycloak-client-secret 6 | stringData: 7 | clientId: microcks-serviceaccount 8 | clientSecret: ab54d329-e435-41ae-a900-ec6b3fe15c54 9 | -------------------------------------------------------------------------------- /user-registration-demo/user-registration-tekton-pipeline.yml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: Pipeline 3 | metadata: 4 | name: user-registration-tekton-pipeline 5 | spec: 6 | tasks: 7 | - name: deploy-app 8 | taskRef: 9 | name: echo-hello-world 10 | - name: test-asyncapi 11 | taskRef: 12 | name: microcks-test 13 | runAfter: 14 | - deploy-app 15 | params: 16 | - name: apiNameAndVersion 17 | value: "User signed-up API:0.1.1" 18 | - name: testEndpoint 19 | value: kafka://my-cluster-kafka-bootstrap-user-registration.KUBE_APPS_URL:443/user-signed-up 20 | - name: runner 21 | value: ASYNC_API_SCHEMA 22 | - name: microcksURL 23 | value: https://microcks-microcks.KUBE_APPS_URL/api/ 24 | - name: waitFor 25 | value: 20sec 26 | - name: secretName 27 | value: user-registration-broker 28 | - name: test-openapi-v1 29 | taskRef: 30 | name: microcks-test 31 | runAfter: 32 | - deploy-app 33 | params: 34 | - name: apiNameAndVersion 35 | value: "User registration API:1.0.0" 36 | - name: testEndpoint 37 | value: http://user-registration-user-registration.KUBE_APPS_URL 38 | - name: runner 39 | value: OPEN_API_SCHEMA 40 | - name: microcksURL 41 | value: https://microcks-microcks.KUBE_APPS_URL/api/ 42 | - name: waitFor 43 | value: 8sec 44 | - name: test-openapi-v2 45 | taskRef: 46 | name: microcks-test 47 | runAfter: 48 | - test-openapi-v1 49 | params: 50 | - name: apiNameAndVersion 51 | value: "User registration API:1.0.0" 52 | - name: testEndpoint 53 | value: http://user-registration-user-registration.KUBE_APPS_URL 54 | - name: runner 55 | value: OPEN_API_SCHEMA 56 | - name: microcksURL 57 | value: https://microcks-microcks.KUBE_APPS_URL/api/ 58 | - name: waitFor 59 | value: 8sec 60 | - name: promote-app 61 | taskRef: 62 | name: echo-hello-world 63 | runAfter: 64 | - test-asyncapi 65 | - test-openapi-v2 --------------------------------------------------------------------------------