From 6bf5d058516390ce6b119287d8d36685fbeec7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Mass=C3=A9?= Date: Fri, 10 Apr 2020 15:06:35 +0200 Subject: [PATCH] pif paf pouf --- .gitignore | 1 + Jenkinsfile | 216 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 57 +++++++++++- cleanup/cleanup.yml | 87 ++++++++++++++++++ 4 files changed, 356 insertions(+), 5 deletions(-) create mode 100644 Jenkinsfile create mode 100644 cleanup/cleanup.yml diff --git a/.gitignore b/.gitignore index 7df82f6..c54d15a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .settings .project target +*.retry diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..6cb0a52 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,216 @@ +#!groovy + +library identifier: '3scale-toolbox-jenkins@openapi3', + retriever: modernSCM([$class: 'GitSCMSource', + remote: 'https://github.com/nmasse-itix/3scale-toolbox-jenkins.git', + traits: [[$class: 'jenkins.plugins.git.traits.BranchDiscoveryTrait']]]) + +def service = null + +node("maven") { + stage('Checkout Source') { + checkout scm + //git url: "https://github.com/nmasse-itix/library-api.git" + } + + stage('Pre-requisites') { + if (! fileExists('openapi.json')) { + + echo """ + There is no OpenAPI Specification file! + """ + + error("Nothing to deploy!") + } + + // Install pre-requisites + sh """ + curl -L -o /tmp/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 + chmod 755 /tmp/jq + """ + } + + stage("Deploy API in Dev") { + // Prepare + service = toolbox.prepareThreescaleService( + openapi: [filename: "openapi.json" ], + environment: [ baseSystemName: "library", + environmentName: "dev", + oidcIssuerEndpoint: params.OIDC_ISSUER_ENDPOINT, + publicBasePath: "/", + privateBasePath: "/", + privateBaseUrl: params.PRIVATE_BASE_URL ], + toolbox: [ openshiftProject: params.NAMESPACE, + destination: params.TARGET_INSTANCE, + image: "quay.io/redhat/3scale-toolbox:v0.16.2", + activeDeadlineSeconds: 180, + insecure: params.DISABLE_TLS_VALIDATION == "yes", + secretName: params.SECRET_NAME], + service: [:], + applications: [ + [ name: "petstore-app", description: "This is used for tests", plan: "test", account: "john" ] + ], + applicationPlans: [ + [ systemName: "test", name: "Test", defaultPlan: true, published: true ] + ] + ) + + // A pre-release version needs to use the mock backend + //if (service.openapi.majorVersion == "0") { + // service.environment.privateBaseUrl = params.MOCK_SERVER + // service.environment.privateBasePath = params.MOCK_URL + //} + + // Import OpenAPI + service.importOpenAPI() + echo "Service with system_name ${service.environment.targetSystemName} created !" + + // Create an Application Plan + service.applyApplicationPlans() + + // Create an Application + service.applyApplication() + + // Promote to production + service.promoteToProduction() + } + + // Terminate the pipeline earlier if the API is not yet production ready + if (service.openapi.majorVersion == "0") { + currentBuild.result = 'SUCCESS' + return + } + + stage("Deploy API in Test") { + // Prepare + service = toolbox.prepareThreescaleService( + openapi: [filename: "openapi.json" ], + environment: [ baseSystemName: "library", + environmentName: "test", + oidcIssuerEndpoint: params.OIDC_ISSUER_ENDPOINT, + publicBasePath: "/", + privateBasePath: "/", + privateBaseUrl: params.PRIVATE_BASE_URL ], + toolbox: [ openshiftProject: params.NAMESPACE, + destination: params.TARGET_INSTANCE, + image: "quay.io/redhat/3scale-toolbox:v0.16.2", + activeDeadlineSeconds: 180, + insecure: params.DISABLE_TLS_VALIDATION == "yes", + secretName: params.SECRET_NAME], + service: [:], + applications: [ + [ name: "petstore-app", description: "This is used for tests", plan: "test", account: "john" ] + ], + applicationPlans: [ + [ systemName: "test", name: "Test", defaultPlan: true, published: true ] + ] + ) + + // Import OpenAPI + service.importOpenAPI() + echo "Service with system_name ${service.environment.targetSystemName} created !" + + // Create an Application Plan + service.applyApplicationPlans() + + // Create an Application + service.applyApplication() + + // Run integration tests + //runIntegrationTests(service) + + // Promote to production + service.promoteToProduction() + } + + stage("Deploy API in Prod") { + // Prepare + service = toolbox.prepareThreescaleService( + openapi: [filename: "openapi.json" ], + environment: [ baseSystemName: "library", + environmentName: "prod", + oidcIssuerEndpoint: params.OIDC_ISSUER_ENDPOINT, + publicBasePath: "/", + privateBasePath: "/", + privateBaseUrl: params.PRIVATE_BASE_URL ], + toolbox: [ openshiftProject: params.NAMESPACE, + destination: params.TARGET_INSTANCE, + image: "quay.io/redhat/3scale-toolbox:v0.16.2", + activeDeadlineSeconds: 180, + insecure: params.DISABLE_TLS_VALIDATION == "yes", + secretName: params.SECRET_NAME], + service: [:], + applications: [ + [ name: "petstore-app", description: "This is used for tests", plan: "silver", account: "john" ] + ], + applicationPlans: [ + [ systemName: "gold", name: "Gold", defaultPlan: false, published: true ], + [ systemName: "silver", name: "Silver", defaultPlan: true, published: true ] + ] + ) + + // Import OpenAPI + service.importOpenAPI() + echo "Service with system_name ${service.environment.targetSystemName} created !" + + // Create an Application Plan + service.applyApplicationPlans() + + // Create an Application + service.applyApplication() + + // Promote to production + service.promoteToProduction() + } + +} + +def runIntegrationTests(def service) { + // To run the integration tests when using APIcast SaaS instances, we need + // to fetch the proxy definition to extract the staging public url + def proxy = service.readProxy("sandbox") + + // The integration tests will be a bit different depending on the security scheme + // declared in the OpenAPI Specification file + def getCredentialsCodeSnippet = null + if (service.openapi.securityScheme.name() == "OPEN") { + getCredentialsCodeSnippet = """ + credential_header="x-dummy: dummy" + echo "no credential will be used" + """ + } else if (service.openapi.securityScheme.name() == "APIKEY") { + def userkey = service.applications[0].userkey + getCredentialsCodeSnippet = """ + credential_header="api-key: ${userkey}" + echo "userkey is ${userkey}" + """ + } else if (service.openapi.securityScheme.name() == "OIDC") { + def tokenEndpoint = getTokenEndpoint(params.OIDC_ISSUER_ENDPOINT) + def clientId = service.applications[0].clientId + def clientSecret = service.applications[0].clientSecret + getCredentialsCodeSnippet = """ + echo "token endpoint is ${tokenEndpoint}" + echo "client_id=${clientId}" + echo "client_secret=${clientSecret}" + curl -sfk "${tokenEndpoint}" -d client_id="${clientId}" -d client_secret="${clientSecret}" -d scope=openid -d grant_type=client_credentials -o response.json + TOKEN="\$(/tmp/jq -r .access_token response.json)" + echo "Received access_token '\$TOKEN'" + credential_header="Authorization: Bearer \$TOKEN" + """ + } + + // Run the actual tests + sh """set -e + echo "Public Staging Base URL is ${proxy.sandbox_endpoint}" + ${getCredentialsCodeSnippet} + curl -sfk -w "findPets: %{http_code}\n" -o /dev/null "${proxy.sandbox_endpoint}/pets" -H "\$credential_header" + curl -sfk -w "findPetById: %{http_code}\n" -o /dev/null "${proxy.sandbox_endpoint}/pets/1" -H "\$credential_header" + # This one is only present in v1.1 and onwards + curl -sk -w "updatePet: %{http_code}\n" -o /dev/null "${proxy.sandbox_endpoint}/pets/1" -H "Content-Type: application/json" -XPUT -d '{"id":1,"name":"Raspoutine","tag":"dog"}' -H "\$credential_header" + """ +} + +def getTokenEndpoint(String oidcIssuerEndpoint) { + def m = (oidcIssuerEndpoint =~ /(https?:\/\/)[^:]+:[^@]+@(.*)/) + return "${m[0][1]}${m[0][2]}/protocol/openid-connect/token" +} \ No newline at end of file diff --git a/README.md b/README.md index b511ff1..86260b9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,58 @@ # Library API -``` +## Setup + +Create a project and deploy the Library API Backend. + +```sh oc new-project library-api -oc create secret generic 3scale-toolbox -n library-api --from-file="$HOME/.3scalerc.yaml" -oc new-app -n library-api --template=jenkins-ephemeral --name=jenkins -p MEMORY_LIMIT=2Gi -oc set env -n library-api dc/jenkins JENKINS_OPTS=--sessionTimeout=86400 -oc import-image openjdk-8-rhel8 --from=registry.redhat.io/openjdk/openjdk-8-rhel8 --confirm -n openshift --reference-policy=local +oc import-image -n openshift openjdk-8-rhel8 --from=registry.redhat.io/openjdk/openjdk-8-rhel8 --confirm --reference-policy=local oc new-app -n library-api -i openjdk-8-rhel8 https://github.com/nmasse-itix/library-api.git --name=library-api oc expose -n library-api svc/library-api --hostname="library-api.apps.ocp4.itix.fr" ``` + +Deploy a Jenkins master. + +```sh +oc new-app -n library-api --template=jenkins-ephemeral --name=jenkins -p MEMORY_LIMIT=2Gi +oc set env -n library-api dc/jenkins JENKINS_OPTS=--sessionTimeout=86400 +``` + +Create a secret containing your 3scale toolbox remotes. + +```sh +oc create -n library-api secret generic 3scale-toolbox --from-file="$HOME/.3scalerc.yaml" +``` + +Add a new Build Config to run the Jenkins pipeline. + +```sh +oc new-build -n library-api --strategy=pipeline --name=library-pipeline https://github.com/nmasse-itix/library-api.git -e PRIVATE_BASE_URL=http://library-api.apps.ocp4.itix.fr -e NAMESPACE=library-api -e TARGET_INSTANCE=3scale-saas -e SECRET_NAME=3scale-toolbox -e OIDC_ISSUER_ENDPOINT=https://zync:[REDACTED]@sso.apps.ocp4.itix.fr/auth/realms/3scale-saas -e DISABLE_TLS_VALIDATION=yes -e MOCK_SERVER=https://microcks.apps.ocp4.itix.fr -e MOCK_URL=/rest/Library/0.9.0 +``` + +## Reset + +Remove the Jenkins pipeline: + +```sh +oc delete -n library-api bc/library-pipeline +``` + +Remove the 3scale services: + +```sh +ansible-playbook cleanup/cleanup.yml -e admin_portal_hostname=[TENANT]-admin.3scale.net -e threescale_token=[REDACTED] +``` + +Go to the [Apicurio Studio](https://apicurio-studio.app.itix.fr/apis) and remove the **Library** service. + +Go to the Microcks console and remove the **Library** service. + +Set the OpenAPI Specification file back to version 0.9: + +```sh +mv api-contracts/openapi-0.9.json openapi.json +git add openapi.json +git commit -m 'reset the demo' +git push +``` diff --git a/cleanup/cleanup.yml b/cleanup/cleanup.yml new file mode 100644 index 0000000..8472b4d --- /dev/null +++ b/cleanup/cleanup.yml @@ -0,0 +1,87 @@ +--- + +- name: Delete petstore services of a 3scale tenant + hosts: localhost + gather_facts: no + vars: + ansible_connection: local + threescale_api: https://{{ admin_portal_hostname }}/admin/api + find_in_systemname: library + validate_certs: no + tasks: + - assert: + that: + - threescale_token is defined + - admin_portal_hostname is defined + msg: > + Please pass your 3scale Access Token in the 'threescale_token' extra var + and your 3scale Admin Portal hostname in the 'admin_portal_hostname' extra var. + + - name: Find Services + uri: + url: '{{ threescale_api }}/services.json?access_token={{ threescale_token }}' + validate_certs: '{{ validate_certs }}' + register: find_services_response + changed_when: false + + - name: Delete all matching services + uri: + url: '{{ threescale_api }}/services/{{ item.id }}.json?access_token={{ threescale_token }}' + method: DELETE + status_code: "200,404" + validate_certs: '{{ validate_certs }}' + register: delete_service_response + changed_when: delete_service_response.status == 200 + with_items: '{{ services }}' + when: > + find_in_systemname in item.system_name + vars: + services: '{{ find_services_response.json|json_query(query) }}' + query: > + services[?service.system_name != `api`].{"id": service.id, "system_name": service.system_name} + + - name: Find API Backends + uri: + url: '{{ threescale_api }}/backend_apis.json?access_token={{ threescale_token }}' + validate_certs: '{{ validate_certs }}' + register: find_backends_response + changed_when: false + + - name: Delete all matching API Backends + uri: + url: '{{ threescale_api }}/backend_apis/{{ item.id }}.json?access_token={{ threescale_token }}' + method: DELETE + status_code: "200,404" + validate_certs: '{{ validate_certs }}' + register: delete_service_response + changed_when: delete_service_response.status == 200 + with_items: '{{ services }}' + when: > + find_in_systemname in item.system_name + vars: + services: '{{ find_backends_response.json|json_query(query) }}' + query: > + backend_apis[?backend_api.system_name != `api`].{"id": backend_api.id, "system_name": backend_api.system_name} + + - name: Find ActiveDocs + uri: + url: '{{ threescale_api }}/active_docs.json?access_token={{ threescale_token }}' + validate_certs: '{{ validate_certs }}' + register: find_active_docs_response + changed_when: false + + - name: Delete all matching ActiveDocs + uri: + url: '{{ threescale_api }}/active_docs/{{ item.id }}.json?access_token={{ threescale_token }}' + method: DELETE + status_code: "200,404" + validate_certs: '{{ validate_certs }}' + register: delete_active_docs_response + changed_when: delete_active_docs_response.status == 200 + with_items: '{{ active_docs }}' + when: > + find_in_systemname in item.system_name + vars: + active_docs: '{{ find_active_docs_response.json|json_query(query) }}' + query: > + api_docs[?api_doc.system_name != `echo`].{"id": api_doc.id, "system_name": api_doc.system_name}