diff --git a/.travis.yml b/.travis.yml index 186a84e..02f9244 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,14 @@ install: - pip install jmespath script: - ansible-playbook tests/write-inventory-files.yml -- ansible-playbook -i tests/inventory tests/3scale-saas-with-hosted-apicast-apikey.yml -- ansible-playbook -i tests/inventory tests/3scale-saas-with-hosted-apicast-oidc.yml -- ansible-playbook -i tests/inventory tests/3scale-saas-with-hosted-apicast-with-basePath.yml +- ansible-playbook -v -i tests/inventory tests/3scale-saas-with-hosted-apicast-apikey.yml +- ansible-playbook -v -i tests/inventory tests/3scale-saas-with-hosted-apicast-oidc.yml +- ansible-playbook -v -i tests/inventory tests/3scale-saas-with-hosted-apicast-with-basePath.yml +- ansible-playbook -v -i tests/inventory tests/3scale-saas-with-hosted-apicast-multi-environment.yml env: global: # travis encrypt "THREESCALE_INVENTORY=$(yaml2json tests/3scale-inventory.yaml|base64)" - secure: "YKSBZJonKq/RwBtrg1wrlbw9GUaQhY0LmbUpShIPMeD1DqLQtOz9OIijAP2Uvtnn5F8josyzyYeZoRz4HOWT6DNdbZoclIT86FdT9yp9pIBuKYqaGOMmdwhTl+BXudeTCAvuj4k6eNux24WY+AqWZoXgr4E0rRJQJyD/G7gn4CMDQmn+an+RK43nhkXgMYNm6SHbR3c3wBypdWJivfgvdBfJb2VY8Q3XXQNh+ivUNW5us4Sf+G+UsjbQKeTF6G1rz8FUxGc2tElWcc+6fXWDTWI1WkZVfNm7f6cGii7X22OBDgOkoUjUKeVj/vmpgp7uOSk3XiWIC+gNsT/cS9/6XzZCnfhGmPal3QP5hXnsP5gBfKYzy8zZEp9H8NLyGA8K7M0cZGuFDdxg0HnIyXNnn7Denjyt3TopFR9ENsNOEbBar8XEb4oZicRWrXC9O/Sse4rCm5vGffXt+lcoAuypmxhASDZKNKsQRXhG+JRMZ2ONB8QOdH22mQ+JEOhOFNtiY2O9eEQOdtU92B9vWYWOztSGZ8/+AQ729bCzOw7AfxtxUyYadFnEdibAeWsl+xnlAdwmo0sNoCTGBKFMXlyP/gmcE7NdCLqWS6MSM2u4ARuQt7IS29wwlRseaMUli69vp0SqvjT6lelx4bonLFi74treKIQVLoxqSQaiU7KVTr0=" + secure: "SKrCC5Nd1lXFU9mCrmGUSbqmEFGzT6/3KTXGQ/bASgSx4r0AuDHt48cI/XPQ6XGCIGaxAt2oRWzJZJ00+Y+5A1TCYAXI4X75mTVl+mgZ3ul5hSK1/KfPPoLciZIcv678FLmmpryNRapK+zxG+OKR1puNFQm9himhF9x0JICigFZSVLMLGnpvHDo2GguFv+4aO1tkdZMT5IzBlPD11Kn98QVCruF/dHiBXtSxuo5ja0/uDsGotMcUQRNa637WIQ5D7YgDREpeLrHzmbpW2zr7HI30oA68k+BxZFqlQ/cyI7f5ogNE73ID+FBSSxVXqcn7TD2nmYL3NQpMztTCzM6YlODIuAvdWUxggeBJflpIVoza0HLP7CB76GAmRSkvwGbnGAHWvCOtvczmJ/hXGgAEdRL5q3eJiGebRvhb6SAVMZ3LOH9LlLU9fKDVGqzolFi4+Jaxami600zgBB/yGkFckpapUZLEK2O0QdHBu3bjd7+9C0EgYONrbyMMkMoWr8TiX/y0qTHg3SclOEacDqLw3kb0MAe9V9WtE+MKOAM38lkXN1v1J9x2izeEqKBDDuzxMOsRxQwfSlA5MVW1kOiKaQgKl37F5t+msfIsPDlr2DRM4JTsIBaQKMY9E50tQ0cMW+vU7P+kn8UlNdTh53TLwKMjPcU99XG1f95fGAMocAA=" notifications: webhooks: https://galaxy.ansible.com/api/v1/notifications/ branches: diff --git a/README.md b/README.md index b30eb28..86a6cfd 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ securityDefinitions: In this Swagger file, the following fields are used: -- `x-threescale-system-name` is used as system_name for the configuration objects in 3scale. +- `x-threescale-system-name` is used as a basis for the system_name for the + configuration objects in 3scale. - `title` is used as the name of the service definition. - `version` is used for proper versioning and follows the [semver scheme](https://semver.org/). - `host` is the DNS name of the existing API backend to expose. @@ -128,12 +129,6 @@ ansible-playbook -i inventory deploy-api.yaml ## Inventory -Three kinds of systems can be declared in the inventory and used with this role: - -- 3scale Admin Portal -- Red Hat SSO -- APIcast instances - The 3scale Admin Portal that will be provisionned is the one that is referenced in the playbook that includes this role. For instance, in the previous example, the provisioned 3scale Admin Portal will be `-admin.3scale.net` because @@ -175,60 +170,31 @@ And you can also define it globally, for instance as playbook vars: threescale_cicd_access_token: 123...456 ``` -The Red Hat SSO instance (currently there can only be one), is taken by convention -from the `sso` group. The `client_id`/`client_secret` used by Zync to synchronize -the 3scale applications are fetched from the inventory variables, as well as the -scheme (`http`/`https`) and the target realm. - -Example: - -```ini -[sso] -sso.acme.corp client_id=3scale client_secret=123 realm=acme scheme=https -``` - -Otherwise, if you don't want to follow this convention, you can use the -corresponding extra variable: `threescale_cicd_sso_issuer_endpoint`. For -the previous example, the variable would be: - -```ini -threescale_cicd_sso_issuer_endpoint=https://3scale:123@sso.acme.corp/auth/realms/acme -``` +The Red Hat SSO instance (currently there can only be one), is defined by +the `threescale_cicd_sso_issuer_endpoint` variable of the `threescale` group. -If both the `sso` group and the `threescale_cicd_sso_issuer_endpoint` extra -variable are specified, the extra variable has precedence over the inventory. - -The APIcast instances are fetched from the `apicast-sandbox` and `apicast-production` -groups. There can only be one hostname in each group and it is the public hostname -of each APIcast cluster (not each individual member). +Its syntax is `https://:@hostname/auth/realms/`. +The `client_id`/`client_secret` are used by Zync to synchronize the 3scale +applications with Red Hat SSO. Example: ```ini -[apicast-sandbox] -api-test.acme.corp scheme=http - -[apicast-production] -api.acme.corp scheme=https +threescale_cicd_sso_issuer_endpoint=https://3scale:123@sso.acme.corp/auth/realms/acme ``` -If you do not want to follow this convention, you can use the corresponding extra -variables: +The APIcast instances are defined from the following extra variables: - `threescale_cicd_apicast_sandbox_endpoint` - `threescale_cicd_apicast_production_endpoint` -For the previous example, the variables would be: +Example: ```ini threescale_cicd_apicast_sandbox_endpoint=http://api-test.acme.corp threescale_cicd_apicast_production_endpoint=https://api.acme.corp ``` -If both the `apicast-*` groups and the `threescale_cicd_apicast_*_endpoint` -extra variables are specified, the extra variables have precedence over the -inventory. - ## OpenAPI Specification fields This role currently supports only OpenAPI Specifications v2.0 (aka. Swagger 2.0). @@ -245,7 +211,7 @@ The following extended fields of the OpenAPI Specifications can be used: If the extended fields cannot be used (if for instance you do not want to alter your API Contract), you can use the corresponding extra variable: -- `threescale_cicd_api_system_name` +- `threescale_cicd_api_base_system_name` - `threescale_cicd_openapi_smoketest_operation` Here is an example of an OpenAPI Specification using those extended fields: @@ -284,7 +250,7 @@ To achieve the same effect without the OpenAPI extended fields, you would have to pass the following extra variables: ```ini -threescale_cicd_api_system_name=echo-api +threescale_cicd_api_base_system_name=echo-api threescale_cicd_openapi_smoketest_operation=Echo # The operationId of the "GET /" method ``` @@ -389,14 +355,28 @@ Defines the system_name of the 3scale Service that will be provisioned. - **Syntax:** lower case alphanumeric + underscore - **Required:** no -- **Default value:** if not defined, the system_name is taken from the OpenAPI - Specification `x-threescale-system-name` extended field, suffixed by the - API major version number. If no `x-threescale-system-name` extended field - can be found, the `title` field is sanitized and then used. +- **Default value:** if not defined, the system_name is taken from the + `threescale_cicd_api_base_system_name` variable. This base system_name + is then suffixed by the API major version number and prefixed by the + environment name (only if `threescale_cicd_api_environment_name` is defined). +- **Example:** `dev_my_wonderful_service_1` + +### `threescale_cicd_api_base_system_name` + +Is used as a basis to compute the `threescale_cicd_api_system_name`. + +- **Syntax:** lower case alphanumeric + underscore +- **Required:** no +- **Default value:** if not defined, the OpenAPI Specification + `x-threescale-system-name` extended field or as a last resort, the `title` + field is sanitized and then used. If no title can be found, the default value `API` is used. If no version number can be found, `0` is used. - **Example:** `my_wonderful_service` +Note: If both `threescale_cicd_api_base_system_name` and `threescale_cicd_api_system_name` +are set, the later has precedence. + ### `threescale_cicd_wildcard_domain` Automatically defines the APIcast public URLs based on a scheme. @@ -414,7 +394,7 @@ Automatically defines the APIcast public URLs based on a scheme. ```ini threescale_cicd_wildcard_domain=acme.corp - threescale_cicd_api_system_name=my_wonderful_service + threescale_cicd_api_base_system_name=my_wonderful_service ``` are equivalent to: @@ -479,7 +459,7 @@ when deploying the same API multiple times on the same 3scale instance. ### Miscellaneous variables -Miscellaneous variables defined in [defaults/main.yml](defaults/main.yml]) +Miscellaneous variables defined in [defaults/main.yml](defaults/main.yml) provide sensible defaults. Have a look at them. ## Dependencies diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..fc43e98 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +jinja2_extensions = jinja2.ext.do diff --git a/defaults/main.yml b/defaults/main.yml index 60969d2..b1fe5ed 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -3,8 +3,6 @@ threescale_cicd_openapi_file_format: YAML threescale_cicd_delay: 10 threescale_cicd_retries: 50 threescale_cicd_throttling: 2 -threescale_cicd_default_application_name: 'Ansible smoke-tests default application' -threescale_cicd_default_application_description: 'This app is used to run smoke tests during the deployment phase. It will be automatically recreated if you delete it.' threescale_cicd_staging_environment_name: sandbox threescale_cicd_production_environment_name: production threescale_cicd_default_staging_suffix: -staging @@ -18,3 +16,34 @@ threescale_cicd_application_plans: default: false state: hidden name: Ansible Test Plan + +# APIcast public base URLs +threescale_cicd_apicast_sandbox_endpoint: '{{ lookup(''template'', ''openapi/apicast_sandbox_endpoint.j2'') }}' +threescale_cicd_apicast_production_endpoint: '{{ lookup(''template'', ''openapi/apicast_production_endpoint.j2'') }}' + +# SSO Issuer Endpoint +threescale_cicd_sso_issuer_endpoint: '{{ lookup(''template'', ''openapi/sso_issuer_endpoint.j2'') }}' +## +## Default Application (used for Smoke Tests) +## +threescale_cicd_default_application_name: 'Ansible smoke-tests default application' +threescale_cicd_default_application_description: 'This app is used to run smoke tests during the deployment phase. It will be automatically recreated if you delete it.' + +# The application plan to pick for the default application (the one used for +# smoke tests) +threescale_cicd_default_application_plan: '{{ (threescale_cicd_application_plans|first).system_name }}' + +# Compute the default application's appid. By default, we are using a combination +# of app, api and environment data, hashed toghether to produce a stable id. +threescale_cicd_default_application_appid: '{{ (threescale_cicd_default_application_name ~ threescale_cicd_api_system_name ~ threescale_cicd_access_token)|hash(''sha1'') }}' +threescale_cicd_default_application_appsecret: '{{ (''secret'' ~ threescale_cicd_default_application_name ~ threescale_cicd_api_system_name ~ threescale_cicd_access_token)|hash(''sha1'') }}' + +# The OpenAPI Operation to use for the smoketest +threescale_cicd_openapi_smoketest_operation: '{{ threescale_cicd_openapi_file_content|json_query(''paths.*.get[? "x-threescale-smoketests-operation" ].operationId|[0]'')|default("")|regex_replace(''[^0-9a-zA-Z_]+'', ''_'') }}' + +## +## OpenAPI Specification File parsing +## +threescale_cicd_api_base_system_name: '{{ lookup(''template'', ''openapi/generate_base_system_name.j2'') }}' +threescale_cicd_api_system_name: '{{ lookup(''template'', ''openapi/generate_final_system_name.j2'') }}' +threescale_cicd_private_base_url: '{{ lookup(''template'', ''openapi/private_base_url.j2'') }}' diff --git a/tasks/api-calls/create_activedoc.yml b/tasks/api-calls/create_activedoc.yml new file mode 100644 index 0000000..b88f277 --- /dev/null +++ b/tasks/api-calls/create_activedoc.yml @@ -0,0 +1,23 @@ +--- + +- debug: + var: threescale_cicd_create_activedoc_payload + verbosity: 1 + +- name: Create the ActiveDocs + uri: + url: https://{{ inventory_hostname }}/admin/api/active_docs.json + validate_certs: no + method: POST + body: '{{ threescale_cicd_create_activedoc_payload }}' + status_code: 201 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- set_fact: + threescale_cicd_existing_activedocs: '{{ threescale_cicd_existing_activedocs|union([ threescale_cicd_tmpresponse.json.api_doc.system_name ]) }}' + threescale_cicd_existing_activedocs_details: '{{ threescale_cicd_existing_activedocs_details|union([ { ''id'': threescale_cicd_tmpresponse.json.api_doc.id, ''system_name'': threescale_cicd_tmpresponse.json.api_doc.system_name } ]) }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/create_application.yml b/tasks/api-calls/create_application.yml new file mode 100644 index 0000000..d584c98 --- /dev/null +++ b/tasks/api-calls/create_application.yml @@ -0,0 +1,22 @@ +--- + +- debug: + var: threescale_cicd_create_application_payload + verbosity: 1 + +- name: Create the application + uri: + url: https://{{ inventory_hostname }}/admin/api/accounts/{{ threescale_cicd_default_account_id }}/applications.json + validate_certs: no + method: POST + body: '{{ threescale_cicd_create_application_payload }}' + status_code: 201 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- set_fact: + threescale_cicd_default_application_details: '{{ threescale_cicd_tmpresponse.json.application }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/create_application_plan.yml b/tasks/api-calls/create_application_plan.yml new file mode 100644 index 0000000..791e41b --- /dev/null +++ b/tasks/api-calls/create_application_plan.yml @@ -0,0 +1,23 @@ +--- + +- debug: + var: threescale_cicd_create_application_plan_payload + verbosity: 1 + +- name: Create the application plan + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/application_plans.json + validate_certs: no + method: POST + body: '{{ threescale_cicd_create_application_plan_payload }}' + status_code: 201 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- set_fact: + threescale_cicd_existing_application_plans: '{{ threescale_cicd_existing_application_plans|union([ threescale_cicd_application_plan.system_name ]) }}' + threescale_cicd_existing_application_plans_details: '{{ threescale_cicd_existing_application_plans_details|union([{ "system_name": threescale_cicd_application_plan.system_name, "id": threescale_cicd_tmpresponse.json.application_plan.id }]) }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/create_mapping_rule.yml b/tasks/api-calls/create_mapping_rule.yml new file mode 100644 index 0000000..13c32e3 --- /dev/null +++ b/tasks/api-calls/create_mapping_rule.yml @@ -0,0 +1,19 @@ +--- + +- debug: + var: threescale_cicd_create_mapping_rule_payload + verbosity: 1 + +- name: Create the mapping rule + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules.json + validate_certs: no + method: POST + body: '{{ threescale_cicd_create_mapping_rule_payload }}' + status_code: 201 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/create_method.yml b/tasks/api-calls/create_method.yml new file mode 100644 index 0000000..db4285b --- /dev/null +++ b/tasks/api-calls/create_method.yml @@ -0,0 +1,23 @@ +--- + +- debug: + var: threescale_cicd_create_method_payload + verbosity: 1 + +- name: Create the method + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics/{{ threescale_cicd_metric_id }}/methods.json + validate_certs: no + method: POST + body: '{{ threescale_cicd_create_method_payload }}' + status_code: 201 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- set_fact: + threescale_cicd_existing_metrics: '{{ threescale_cicd_existing_metrics|union([ threescale_cicd_api_operation.key ]) }}' + threescale_cicd_existing_metrics_details: '{{ threescale_cicd_existing_metrics_details|union([ { "system_name": threescale_cicd_api_operation.key, "id": threescale_cicd_tmpresponse.json|json_query("method.id") } ]) }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/create_service.yml b/tasks/api-calls/create_service.yml new file mode 100644 index 0000000..33f2ca7 --- /dev/null +++ b/tasks/api-calls/create_service.yml @@ -0,0 +1,24 @@ +--- + +- debug: + var: threescale_cicd_create_service_payload + verbosity: 1 + +- name: Create the service + uri: + url: https://{{ inventory_hostname }}/admin/api/services.json + validate_certs: no + method: POST + body: '{{ threescale_cicd_create_service_payload }}' + status_code: 201 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- set_fact: + threescale_cicd_existing_services: '{{ threescale_cicd_existing_services|union([ threescale_cicd_tmpresponse.json.service.system_name ]) }}' + threescale_cicd_existing_services_details: '{{ threescale_cicd_existing_services_details|union([ { ''id'': threescale_cicd_tmpresponse.json.service.id, ''system_name'': threescale_cicd_tmpresponse.json.service.system_name } ]) }}' + cacheable: true + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/delete_mapping_rule.yml b/tasks/api-calls/delete_mapping_rule.yml new file mode 100644 index 0000000..efbe168 --- /dev/null +++ b/tasks/api-calls/delete_mapping_rule.yml @@ -0,0 +1,14 @@ +--- + +- name: Delete the unused mapping rules + uri: + url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules/{{ threescale_cicd_existing_mapping_rules[threescale_cicd_mapping_rule] }}.json?access_token={{ threescale_cicd_access_token|urlencode }}" + validate_certs: no + method: DELETE + status_code: 200,404 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/delete_metric.yml b/tasks/api-calls/delete_metric.yml new file mode 100644 index 0000000..fe7898a --- /dev/null +++ b/tasks/api-calls/delete_metric.yml @@ -0,0 +1,17 @@ +--- + +- debug: + msg: "Deleting unused metric {{ threescale_cicd_metric.system_name }}..." + +- name: Delete the metric + uri: + url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics/{{ threescale_cicd_metric_id }}/methods/{{ threescale_cicd_metric.id }}.json?access_token={{ threescale_cicd_access_token|urlencode }}" + validate_certs: no + method: DELETE + status_code: 200,404 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/find_application.yml b/tasks/api-calls/find_application.yml new file mode 100644 index 0000000..6ed39e6 --- /dev/null +++ b/tasks/api-calls/find_application.yml @@ -0,0 +1,13 @@ +--- + +- name: Check if the default application exists + uri: + url: 'https://{{ inventory_hostname }}/admin/api/applications/find.json?{{ threescale_cicd_find_application_payload }}' + validate_certs: no + method: GET + status_code: 200,404 + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_default_application_id: '{{ threescale_cicd_tmpresponse.json.application.id }}' + when: 'threescale_cicd_tmpresponse.status == 200' diff --git a/tasks/api-calls/find_first_account.yml b/tasks/api-calls/find_first_account.yml new file mode 100644 index 0000000..5726d05 --- /dev/null +++ b/tasks/api-calls/find_first_account.yml @@ -0,0 +1,10 @@ +--- + +- name: Get the default (first) account + uri: + url: https://{{ inventory_hostname }}/admin/api/accounts.json?access_token={{ threescale_cicd_access_token|urlencode }}&state=approved&page=1&per_page=1 + validate_certs: no + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_default_account_id: '{{ threescale_cicd_tmpresponse.json.accounts[0].account.id }}' diff --git a/tasks/api-calls/get_proxy_version.yml b/tasks/api-calls/get_proxy_version.yml new file mode 100644 index 0000000..88bbc6e --- /dev/null +++ b/tasks/api-calls/get_proxy_version.yml @@ -0,0 +1,20 @@ +--- + +- name: Get the version of the staging proxy definition + uri: + url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/configs/{{ threescale_cicd_staging_environment_name }}/latest.json?access_token={{ threescale_cicd_access_token|urlencode }}' + validate_certs: no + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_staging_proxy_version: '{{ threescale_cicd_tmpresponse.json.proxy_config.version }}' + +- name: Get the version of the production proxy definition + uri: + url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/configs/{{ threescale_cicd_production_environment_name }}/latest.json?access_token={{ threescale_cicd_access_token|urlencode }}' + validate_certs: no + status_code: 200,404 + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_production_proxy_version: '{{ threescale_cicd_tmpresponse.json.proxy_config.version if threescale_cicd_tmpresponse.status == 200 else ''NONE'' }}' diff --git a/tasks/api-calls/keycloak/authenticate.yml b/tasks/api-calls/keycloak/authenticate.yml new file mode 100644 index 0000000..e9b3995 --- /dev/null +++ b/tasks/api-calls/keycloak/authenticate.yml @@ -0,0 +1,22 @@ +--- + +- debug: + var: threescale_cicd_authenticate_to_keycloak_payload + verbosity: 1 + +- name: Authenticate to RH-SSO + uri: + url: '{{ threescale_cicd_sso_realm_endpoint }}/protocol/openid-connect/token' + body: '{{ threescale_cicd_authenticate_to_keycloak_payload }}' + method: POST + validate_certs: no + return_content: yes + register: threescale_cicd_tmpresponse + retries: '{{ threescale_cicd_retries }}' + delay: '{{ threescale_cicd_delay }}' + # temporary fix for https://github.com/ansible/ansible/issues/28078 + until: 'threescale_cicd_tmpresponse is success' + +- name: Extract the access_token + set_fact: + threescale_cicd_keycloak_access_token: '{{ threescale_cicd_tmpresponse.json |json_query("access_token") }}' diff --git a/tasks/api-calls/keycloak/patch_client.yml b/tasks/api-calls/keycloak/patch_client.yml new file mode 100644 index 0000000..003ba17 --- /dev/null +++ b/tasks/api-calls/keycloak/patch_client.yml @@ -0,0 +1,23 @@ +--- + +- debug: + var: threescale_cicd_patch_keycloak_client_payload + verbosity: 1 + +- name: Patch the client in RH-SSO to support the "client_credentials" and "password" grant_type. + uri: + url: '{{ threescale_cicd_sso_admin_endpoint }}/clients/{{ threescale_cicd_default_application_sso_id|urlencode }}' + method: PUT + validate_certs: no + body: '{{ threescale_cicd_patch_keycloak_client_payload }}' + body_format: json + status_code: '200,204' + headers: + Authorization: 'Bearer {{ threescale_cicd_keycloak_access_token }}' + Content-Type: 'application/json' + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/keycloak/wait_for_client.yml b/tasks/api-calls/keycloak/wait_for_client.yml new file mode 100644 index 0000000..e6d764e --- /dev/null +++ b/tasks/api-calls/keycloak/wait_for_client.yml @@ -0,0 +1,18 @@ +--- + +- name: Wait for the new client to appear in RH-SSO + uri: + url: '{{ threescale_cicd_sso_admin_endpoint }}/clients?clientId={{ threescale_cicd_default_application_appid|urlencode }}' + method: GET + validate_certs: no + return_content: yes + headers: + Authorization: 'Bearer {{ threescale_cicd_keycloak_access_token }}' + register: threescale_cicd_tmpresponse + retries: '{{ threescale_cicd_retries }}' + delay: '{{ threescale_cicd_delay }}' + until: 'threescale_cicd_tmpresponse is success and threescale_cicd_tmpresponse.json|length > 0' + +- set_fact: + threescale_cicd_default_application_sso_id: '{{ threescale_cicd_tmpresponse.json[0].id }}' + threescale_cicd_default_application_sso_body: '{{ threescale_cicd_tmpresponse.json[0] }}' diff --git a/tasks/api-calls/promote_proxy.yml b/tasks/api-calls/promote_proxy.yml new file mode 100644 index 0000000..e3b0b8d --- /dev/null +++ b/tasks/api-calls/promote_proxy.yml @@ -0,0 +1,19 @@ +--- + +- debug: + var: threescale_cicd_promote_proxy_payload + verbosity: 1 + +- name: Promote to production + uri: + url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/configs/{{ threescale_cicd_staging_environment_name }}/{{ threescale_cicd_staging_proxy_version }}/promote.json' + body: '{{ threescale_cicd_promote_proxy_payload }}' + status_code: 201 + validate_certs: no + method: POST + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 201' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/smoke_test.yml b/tasks/api-calls/smoke_test.yml new file mode 100644 index 0000000..c0afa49 --- /dev/null +++ b/tasks/api-calls/smoke_test.yml @@ -0,0 +1,20 @@ +--- + +- debug: + var: threescale_cicd_smoke_test_headers + verbosity: 1 + +- debug: + msg: "Starting a smoke test on '{{ threescale_cicd_smoke_test_url }}'..." + +- name: Running smoke tests ! + uri: + url: '{{ threescale_cicd_smoke_test_url }}' + headers: '{{ threescale_cicd_smoke_test_headers }}' + validate_certs: no + method: GET + register: threescale_cicd_tmpresponse + retries: '{{ threescale_cicd_retries }}' + delay: '{{ threescale_cicd_delay }}' + # temporary fix for https://github.com/ansible/ansible/issues/28078 + until: 'threescale_cicd_tmpresponse is success' diff --git a/tasks/api-calls/update_activedoc.yml b/tasks/api-calls/update_activedoc.yml new file mode 100644 index 0000000..0cbfd17 --- /dev/null +++ b/tasks/api-calls/update_activedoc.yml @@ -0,0 +1,19 @@ +--- + +- debug: + var: threescale_cicd_update_activedoc_payload + verbosity: 1 + +- name: Update the ActiveDocs + uri: + url: 'https://{{ inventory_hostname }}/admin/api/active_docs/{{ threescale_cicd_api_activedocs_id }}.json' + validate_certs: no + method: PUT + body: '{{ threescale_cicd_update_activedoc_payload }}' + status_code: 200 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/update_application.yml b/tasks/api-calls/update_application.yml new file mode 100644 index 0000000..8bd9901 --- /dev/null +++ b/tasks/api-calls/update_application.yml @@ -0,0 +1,21 @@ +--- + +- debug: + var: threescale_cicd_update_application_payload + verbosity: 1 + +- name: Update the application + uri: + url: https://{{ inventory_hostname }}/admin/api/accounts/{{ threescale_cicd_default_account_id }}/applications/{{ threescale_cicd_default_application_id }}.json + validate_certs: no + method: PUT + body: '{{ threescale_cicd_update_application_payload }}' + status_code: 200 + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_default_application_details: '{{ threescale_cicd_tmpresponse.json.application }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/update_application_plan.yml b/tasks/api-calls/update_application_plan.yml new file mode 100644 index 0000000..e6f8e81 --- /dev/null +++ b/tasks/api-calls/update_application_plan.yml @@ -0,0 +1,19 @@ +--- + +- debug: + var: threescale_cicd_update_application_plan_payload + verbosity: 1 + +- name: Update the application plan + uri: + url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/application_plans/{{ (threescale_cicd_existing_application_plans_details|selectattr("system_name", "equalto", threescale_cicd_application_plan.system_name)|first).id }}.json' + validate_certs: no + method: PUT + body: '{{ threescale_cicd_update_application_plan_payload }}' + status_code: 200 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/update_mapping_rule.yml b/tasks/api-calls/update_mapping_rule.yml new file mode 100644 index 0000000..483d27e --- /dev/null +++ b/tasks/api-calls/update_mapping_rule.yml @@ -0,0 +1,21 @@ +--- + +- debug: + var: threescale_cicd_update_mapping_rule_payload + verbosity: 1 + +- name: Update the mapping rule + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules/{{ threescale_cicd_mapping_rule_id }}.json + validate_certs: no + method: PUT + body: '{{ threescale_cicd_update_mapping_rule_payload }}' + status_code: 200 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + vars: + threescale_cicd_mapping_rule_id: '{{ threescale_cicd_existing_mapping_rules[threescale_cicd_mapping_rule] }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/update_method.yml b/tasks/api-calls/update_method.yml new file mode 100644 index 0000000..5247b02 --- /dev/null +++ b/tasks/api-calls/update_method.yml @@ -0,0 +1,18 @@ +--- + +- debug: + var: threescale_cicd_update_method_payload + verbosity: 1 + +- name: Update the method + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics/{{ threescale_cicd_metric_id }}/methods/{{ (threescale_cicd_existing_metrics_details|selectattr('system_name', 'equalto', threescale_cicd_api_operation.key)|first).id }}.json + validate_certs: no + method: PATCH + body: '{{ threescale_cicd_update_method_payload }}' + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/update_proxy.yml b/tasks/api-calls/update_proxy.yml new file mode 100644 index 0000000..a5a09bf --- /dev/null +++ b/tasks/api-calls/update_proxy.yml @@ -0,0 +1,23 @@ +--- + +- debug: + var: threescale_cicd_update_proxy_payload + verbosity: 1 + +- name: Update the proxy definition + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy.json + validate_certs: no + method: PATCH + body: '{{ threescale_cicd_update_proxy_payload }}' + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Extract the staging and production gateway endpoint from the proxy definition + set_fact: + threescale_cicd_apicast_discovered_sandbox_endpoint: '{{ threescale_cicd_tmpresponse.json.proxy.sandbox_endpoint }}' + threescale_cicd_apicast_discovered_production_endpoint: '{{ threescale_cicd_tmpresponse.json.proxy.endpoint }}' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/api-calls/update_service.yml b/tasks/api-calls/update_service.yml new file mode 100644 index 0000000..98c6909 --- /dev/null +++ b/tasks/api-calls/update_service.yml @@ -0,0 +1,19 @@ +--- + +- debug: + var: threescale_cicd_update_service_payload + verbosity: 1 + +- name: Update the service + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}.json + validate_certs: no + method: PUT + body: '{{ threescale_cicd_update_service_payload }}' + status_code: 200 + register: threescale_cicd_tmpresponse + changed_when: 'threescale_cicd_tmpresponse.status == 200' + +- name: Wait for a couple seconds + pause: + seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/check_requirements.yml b/tasks/check_requirements.yml deleted file mode 100644 index 6e44c93..0000000 --- a/tasks/check_requirements.yml +++ /dev/null @@ -1,32 +0,0 @@ ---- - -- name: Verify that Ansible version is >= 2.4 - assert: - that: "ansible_version.full is version_compare('2.4', '>=')" - msg: This module requires at least Ansible 2.4 - -- name: Check if jmespath is installed locally - debug: msg={{dummy|json_query('@')}} - register: check_jmespath - ignore_errors: yes - vars: - dummy: Hello World - -- name: Check if jinja 2.8 is installed locally - debug: msg={{(dummy|selectattr("id", "equalto", "hello")|first)['value']}} - vars: - dummy: - - id: hello - value: Hello World - register: check_jinja28 - ignore_errors: yes - -- assert: - that: - - 'check_jmespath is success' - msg: "The JMESPath library is required by this role. Please install the JMESPath library with 'pip install jmespath'." - -- assert: - that: - - 'check_jinja28 is success' - msg: "At least Jinja v2.8 is required by this role. Please update Jinja with 'pip install -U Jinja2'." diff --git a/tests/cleanup.yaml b/tasks/cleanup.yaml similarity index 99% rename from tests/cleanup.yaml rename to tasks/cleanup.yaml index 377c3ed..390e653 100644 --- a/tests/cleanup.yaml +++ b/tasks/cleanup.yaml @@ -9,6 +9,7 @@ register: threescale_cicd_tmpresponse changed_when: 'threescale_cicd_tmpresponse.status == 200' when: 'threescale_cicd_api_service_id is defined' + - name: Delete the created ActiveDocs uri: url: 'https://{{ inventory_hostname }}/admin/api/active_docs/{{ threescale_cicd_api_activedocs_id }}.json?access_token={{ threescale_cicd_access_token|urlencode }}' diff --git a/tasks/create_activedocs.yml b/tasks/create_activedocs.yml deleted file mode 100644 index 9c0fee8..0000000 --- a/tasks/create_activedocs.yml +++ /dev/null @@ -1,113 +0,0 @@ ---- - -- name: Retrieve existing ActiveDocs from the 3scale Admin Portal - uri: - url: "https://{{ inventory_hostname }}/admin/api/active_docs.json?access_token={{ threescale_cicd_access_token|urlencode }}" - validate_certs: no - register: threescale_cicd_tmp_allactivedocs - -- set_fact: - threescale_cicd_existing_activedocs: '{{ threescale_cicd_tmp_allactivedocs.json|json_query(''api_docs[*].api_doc.system_name'') }}' - threescale_cicd_existing_activedocs_details: '{{ threescale_cicd_tmp_allactivedocs.json|json_query(''api_docs[].{"system_name": api_doc.system_name, "id": api_doc.id}'') }}' - -- name: Get the production gateway endpoint from the proxy definition - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy.json?access_token={{ threescale_cicd_access_token|urlencode }} - validate_certs: no - method: GET - register: threescale_cicd_tmpresponse - when: "threescale_cicd_apicast_production_endpoint is not defined" - -- name: Extract the production gateway endpoint from the proxy definition - set_fact: - threescale_cicd_apicast_production_endpoint: '{{ threescale_cicd_tmpresponse.json|json_query(''proxy.endpoint'') }}' - when: "threescale_cicd_apicast_production_endpoint is not defined" - -- set_fact: - threescale_cicd_apicast_production_scheme: '{{ threescale_cicd_apicast_production_endpoint|regex_findall(''^(\w+)://'')|first }}' - threescale_cicd_apicast_production_hostname: '{{ threescale_cicd_apicast_production_endpoint|regex_findall(''^\w+://(.+)$'')|first }}' - -- set_fact: - threescale_cicd_openapi_rewritten: '{{ threescale_cicd_openapi_file_content }}' - -- name: Rewrite the OpenAPI file (schemes) - set_fact: - threescale_cicd_openapi_rewritten: '{{ threescale_cicd_openapi_rewritten|combine({ ''schemes'': [ threescale_cicd_apicast_production_scheme ] }) }}' - -- name: Rewrite the OpenAPI file (host) - set_fact: - threescale_cicd_openapi_rewritten: '{{ threescale_cicd_openapi_rewritten|combine({ ''host'': threescale_cicd_apicast_production_hostname }) }}' - -- name: Rewrite the Swagger file (swagger version as string) - set_fact: - threescale_cicd_openapi_rewritten: '{{ threescale_cicd_openapi_rewritten|combine({ ''swagger'': threescale_cicd_openapi_rewritten.swagger ~ "" }) }}' - -- name: Add the RH-SSO endpoints to the OpenAPI securityDefinitions - set_fact: - threescale_cicd_api_security_definitions: '{{ threescale_cicd_api_security_definitions|combine({ threescale_cicd_api_security_scheme_name: (threescale_cicd_api_security_definitions[threescale_cicd_api_security_scheme_name]|combine({ ''authorizationUrl'': threescale_cicd_sso_realm_endpoint ~ ''/protocol/openid-connect/auth'', ''tokenUrl'': threescale_cicd_sso_realm_endpoint ~ ''/protocol/openid-connect/token'' })) }) }}' - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- name: Add the RH-SSO default scope to the OpenAPI securityDefinitions - set_fact: - threescale_cicd_api_security_definitions: '{{ threescale_cicd_api_security_definitions|combine({ threescale_cicd_api_security_scheme_name: (threescale_cicd_api_security_definitions[threescale_cicd_api_security_scheme_name]|combine({ ''scopes'': threescale_cicd_default_oauth_scopes})) }) }}' - when: 'threescale_cicd_api_security_scheme.type == ''oauth2'' and ''scopes'' not in threescale_cicd_api_security_scheme' - -- name: Rewrite the OpenAPI file (securityDefinitions) - set_fact: - threescale_cicd_openapi_rewritten: '{{ threescale_cicd_openapi_rewritten|combine({ ''securityDefinitions'': threescale_cicd_api_security_definitions }) }}' - -- set_fact: - threescale_cicd_tmp_activedoc_payload: - name: '{{ threescale_cicd_api_name }}' - system_name: '{{ threescale_cicd_api_system_name }}' - body: '{{ threescale_cicd_openapi_rewritten|to_nice_json }}' - description: '{{ threescale_cicd_api_description }}' - published: 'true' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_tmp_activedoc_payload }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- set_fact: - threescale_cicd_api_activedocs_id: '{{ (threescale_cicd_existing_activedocs_details|selectattr(''system_name'', ''equalto'', threescale_cicd_api_system_name)|first).id }}' - when: 'threescale_cicd_api_system_name in threescale_cicd_existing_activedocs' - -- name: Update the ActiveDocs - uri: - url: 'https://{{ inventory_hostname }}/admin/api/active_docs/{{ threescale_cicd_api_activedocs_id }}.json' - validate_certs: no - method: PUT - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 200 - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - when: 'threescale_cicd_api_system_name in threescale_cicd_existing_activedocs' - -- name: Create the ActiveDocs - uri: - url: https://{{ inventory_hostname }}/admin/api/active_docs.json - validate_certs: no - method: POST - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 201 - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 201' - when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_activedocs' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' - -- set_fact: - threescale_cicd_api_activedocs_id: '{{ threescale_cicd_tmpresponse.json.api_doc.id }}' - when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_activedocs' - -- set_fact: - threescale_cicd_existing_services: '{{ threescale_cicd_existing_activedocs|union([ threescale_cicd_tmpresponse.json.api_doc.system_name ]) }}' - threescale_cicd_existing_services_details: '{{ threescale_cicd_existing_activedocs_details|union([ { ''id'': threescale_cicd_tmpresponse.json.api_doc.id, ''system_name'': threescale_cicd_tmpresponse.json.api_doc.system_name } ]) }}' - when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_activedocs' diff --git a/tasks/create_application_plans.yml b/tasks/create_application_plans.yml deleted file mode 100644 index c0e53ac..0000000 --- a/tasks/create_application_plans.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_tmp_plan }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- name: Update the application plan - uri: - url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/application_plans/{{ (threescale_cicd_existing_application_plans_details|selectattr("system_name", "equalto", threescale_cicd_tmp_plan.system_name)|first).id }}.json' - validate_certs: no - method: PUT - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 200 - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_tmp_plan.system_name in threescale_cicd_existing_application_plans' - -- name: Create the application plan - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/application_plans.json - validate_certs: no - method: POST - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 201 - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_tmp_plan.system_name not in threescale_cicd_existing_application_plans' - -- set_fact: - threescale_cicd_existing_application_plans: '{{ threescale_cicd_existing_application_plans|union([ threescale_cicd_tmp_plan.system_name ]) }}' - threescale_cicd_existing_application_plans_details: '{{ threescale_cicd_existing_application_plans_details|union([{ "system_name": threescale_cicd_tmp_plan.system_name, "id": threescale_cicd_tmpresponse.json.application_plan.id }]) }}' - when: 'threescale_cicd_tmp_plan.system_name not in threescale_cicd_existing_application_plans' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/create_default_application.yml b/tasks/create_default_application.yml deleted file mode 100644 index efb51fc..0000000 --- a/tasks/create_default_application.yml +++ /dev/null @@ -1,89 +0,0 @@ ---- - -- name: Get the default (first) account - uri: - url: https://{{ inventory_hostname }}/admin/api/accounts.json?access_token={{ threescale_cicd_access_token|urlencode }}&state=approved&page=1&per_page=1 - validate_certs: no - register: threescale_cicd_tmp_allaccounts - when: 'threescale_cicd_default_account_id is not defined' - -- set_fact: - threescale_cicd_default_account_id: '{{ threescale_cicd_tmp_allaccounts.json.accounts[0].account.id }}' - when: 'threescale_cicd_default_account_id is not defined' - -- name: Pick the first given application plan if no default application plan is given - set_fact: - threescale_cicd_default_application_plan: '{{ (threescale_cicd_application_plans|first).system_name }}' - when: 'threescale_cicd_default_application_plan is not defined and threescale_cicd_application_plans is defined and threescale_cicd_application_plans|length > 0' - -- name: Find the application plan id - set_fact: - threescale_cicd_default_application_plan_id: '{{ (threescale_cicd_existing_application_plans_details|selectattr("system_name", "equalto", threescale_cicd_default_application_plan)|first).id }}' - when: 'threescale_cicd_default_application_plan is defined' - -- name: Compute the appid for the default application - set_fact: - # The default appid is a SHA1 hash of the application name, api name and salted with the 3scale access token so that it cannot be guessed - threescale_cicd_default_application_appid: '{{ (threescale_cicd_default_application_name ~ threescale_cicd_api_system_name ~ threescale_cicd_access_token)|hash(''sha1'') }}' - when: 'threescale_cicd_default_application_appid is not defined' - -- set_fact: - threescale_cicd_tmp_search_criteria: 'app_id' - threescale_cicd_tmp_app_id_field: 'application_id' - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- set_fact: - threescale_cicd_tmp_search_criteria: 'user_key' - threescale_cicd_tmp_app_id_field: 'user_key' - when: 'threescale_cicd_api_security_scheme.type == ''apiKey''' - -- name: Check if the default application exists - uri: - url: 'https://{{ inventory_hostname }}/admin/api/applications/find.json?access_token={{ threescale_cicd_access_token|urlencode }}&{{ threescale_cicd_tmp_search_criteria }}={{ threescale_cicd_default_application_appid|urlencode }}' - validate_certs: no - method: GET - status_code: 200,404 - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_default_application_id is not defined and threescale_cicd_default_application_appid is defined' - -- set_fact: - threescale_cicd_default_application_id: '{{ threescale_cicd_tmpresponse.json.application.id }}' - when: 'threescale_cicd_default_application_id is not defined and threescale_cicd_default_application_appid is defined and threescale_cicd_tmpresponse.status == 200' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ "access_token=" ~ (threescale_cicd_access_token|urlencode) ~ "&plan_id=" ~ threescale_cicd_default_application_plan_id ~ "&name=" ~ (threescale_cicd_default_application_name|urlencode) ~ "&description=" ~ (threescale_cicd_default_application_description|urlencode) ~ "&" ~ threescale_cicd_tmp_app_id_field ~ "=" ~ (threescale_cicd_default_application_appid|urlencode) }}' - -- name: Create the application - uri: - url: https://{{ inventory_hostname }}/admin/api/accounts/{{ threescale_cicd_default_account_id }}/applications.json - validate_certs: no - method: POST - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 201 - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_default_application_id is not defined' - -- set_fact: - threescale_cicd_default_application_details: '{{ threescale_cicd_tmpresponse.json.application }}' - when: 'threescale_cicd_default_application_id is not defined' - -- name: Update the application - uri: - url: https://{{ inventory_hostname }}/admin/api/accounts/{{ threescale_cicd_default_account_id }}/applications/{{ threescale_cicd_default_application_id }}.json - validate_certs: no - method: PUT - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 200 - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_default_application_id is defined' - -- set_fact: - threescale_cicd_default_application_details: '{{ threescale_cicd_tmpresponse.json.application }}' - when: 'threescale_cicd_default_application_id is defined' - -- include_tasks: patch_default_application_for_oauth.yml - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/create_mapping_rule.yml b/tasks/create_mapping_rule.yml deleted file mode 100644 index f6fa095..0000000 --- a/tasks/create_mapping_rule.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_tmp_wanted_mapping_rules[threescale_cicd_tmp_mapping_rule_to_create] }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- set_fact: - # Add the metric_id to the payload - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ "metric_id=" ~ ((threescale_cicd_existing_metrics_details|selectattr("system_name", "equalto", threescale_cicd_tmp_mapping_rule_to_create)|first).id|urlencode) }}' - -- name: Create the mapping rule - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules.json - validate_certs: no - method: POST - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 201 - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 201' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/create_service.yml b/tasks/create_service.yml deleted file mode 100644 index 3da7932..0000000 --- a/tasks/create_service.yml +++ /dev/null @@ -1,58 +0,0 @@ ---- - -- name: Retrieve existing Services from the 3scale Admin Portal - uri: - url: "https://{{ inventory_hostname }}/admin/api/services.json?access_token={{ threescale_cicd_access_token|urlencode }}" - validate_certs: no - register: threescale_cicd_tmp_allservices - -- set_fact: - threescale_cicd_existing_services: '{{ threescale_cicd_tmp_allservices.json|json_query(''services[*].service.system_name'') }}' - threescale_cicd_existing_services_details: '{{ threescale_cicd_tmp_allservices.json|json_query(''services[].{"system_name": service.system_name, "id": service.id}'') }}' - -- set_fact: - threescale_cicd_tmp_body_create_svc: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_create_svc: '{{ threescale_cicd_tmp_body_create_svc ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_api_service_definition }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- set_fact: - threescale_cicd_api_service_id: '{{ (threescale_cicd_existing_services_details|selectattr(''system_name'', ''equalto'', threescale_cicd_api_system_name)|first)[''id''] }}' - when: 'threescale_cicd_api_system_name in threescale_cicd_existing_services' - -- name: Update the service - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}.json - validate_certs: no - method: PUT - body: '{{ threescale_cicd_tmp_body_create_svc }}' - status_code: 200 - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - when: 'threescale_cicd_api_system_name in threescale_cicd_existing_services' - -- name: Create the service - uri: - url: https://{{ inventory_hostname }}/admin/api/services.json - validate_certs: no - method: POST - body: '{{ threescale_cicd_tmp_body_create_svc }}' - status_code: 201 - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_services' - -- set_fact: - threescale_cicd_api_service_id: '{{ threescale_cicd_tmpresponse.json.service.id }}' - when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_services' - -- set_fact: - threescale_cicd_existing_services: '{{ threescale_cicd_existing_services|union([ threescale_cicd_tmpresponse.json.service.system_name ]) }}' - threescale_cicd_existing_services_details: '{{ threescale_cicd_existing_services_details|union([ { ''id'': threescale_cicd_tmpresponse.json.service.id, ''system_name'': threescale_cicd_tmpresponse.json.service.system_name } ]) }}' - when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_services' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/delete_unused_metrics.yml b/tasks/delete_unused_metrics.yml deleted file mode 100644 index 10424c2..0000000 --- a/tasks/delete_unused_metrics.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -- set_fact: - threescale_cicd_tmp_metrics_to_delete: [] - -- set_fact: - threescale_cicd_tmp_metrics_to_delete: '{{ threescale_cicd_tmp_metrics_to_delete|union([threescale_cicd_tmp_metric.id]) }}' - with_items: '{{ threescale_cicd_existing_metrics_details }}' - loop_control: - loop_var: threescale_cicd_tmp_metric - when: 'threescale_cicd_tmp_metric.system_name != "hits" and threescale_cicd_tmp_metric.system_name not in threescale_cicd_api_operations' - -- name: Delete the method - uri: - url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics/{{ threescale_cicd_metric_id }}/methods/{{ threescale_cicd_tmp_metric }}.json?access_token={{ threescale_cicd_access_token|urlencode }}" - validate_certs: no - method: DELETE - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - with_items: '{{ threescale_cicd_tmp_metrics_to_delete }}' - loop_control: - loop_var: threescale_cicd_tmp_metric - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/main.yml b/tasks/main.yml index f9498a7..785f33b 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,143 +1,53 @@ --- -- import_tasks: check_requirements.yml +# Make sure we have everything we need to run this playbook +- import_tasks: steps/requirements.yml -- name: Ensure pre-requisites are met - assert: - that: - - "threescale_cicd_access_token is defined" - - "threescale_cicd_openapi_file is defined" - msg: |- - This module requires at least two variables: - - threescale_cicd_access_token that contains an Access Token with Read/Write privileges on the 3scale Account Management API. This variable is usually set in your inventory file. - - threescale_cicd_openapi_file that is the path to the OpenAPI file you want to deploy in 3scale. This variable is usually passed as an extra variable (-e threescale_cicd_openapi_file=...) - -- name: Set the threescale_cicd_sso_issuer_endpoint variable from the inventory - set_fact: - threescale_cicd_sso_issuer_endpoint: '{{ (hostvars[groups[''sso''][0]].scheme|default(''https'')) ~ ''://'' ~ hostvars[groups[''sso''][0]].client_id ~ '':'' ~ hostvars[groups[''sso''][0]].client_secret ~ ''@'' ~ groups[''sso''][0] ~ ''/auth/realms/'' ~ hostvars[groups[''sso''][0]].realm }}' - when: 'threescale_cicd_sso_issuer_endpoint is not defined and ''sso'' in groups and groups[''sso''] > 0' - -- name: Set the threescale_cicd_sso_realm_endpoint variable from the threescale_cicd_sso_issuer_endpoint - set_fact: - threescale_cicd_sso_realm_endpoint: '{{ (threescale_cicd_sso_issuer_endpoint|urlsplit(''scheme'')) ~ ''://'' ~ (threescale_cicd_sso_issuer_endpoint|urlsplit(''hostname'')) ~ (threescale_cicd_sso_issuer_endpoint|urlsplit(''path'')) }}' - when: 'threescale_cicd_sso_realm_endpoint is not defined and threescale_cicd_sso_issuer_endpoint is defined' - -- name: Set the threescale_cicd_sso_admin_endpoint variable from the threescale_cicd_sso_realm_endpoint - set_fact: - threescale_cicd_sso_admin_endpoint: '{{ threescale_cicd_sso_realm_endpoint|replace(''/auth/realms/'', ''/auth/admin/realms/'') }}' - when: 'threescale_cicd_sso_admin_endpoint is not defined and threescale_cicd_sso_realm_endpoint is defined' - -- name: Set the threescale_cicd_apicast_sandbox_endpoint variable from the inventory - set_fact: - threescale_cicd_apicast_sandbox_endpoint: '{{ (hostvars[groups[''apicast-sandbox''][0]].scheme|default(''https'')) ~ ''://'' ~ groups[''apicast-sandbox''][0] }}' - when: 'threescale_cicd_apicast_sandbox_endpoint is not defined and ''apicast-sandbox'' in groups and groups[''apicast-sandbox''] > 0' - -- name: Set the threescale_cicd_apicast_production_endpoint variable from the inventory - set_fact: - threescale_cicd_apicast_production_endpoint: '{{ (hostvars[groups[''apicast-production''][0]].scheme|default(''https'')) ~ ''://'' ~ groups[''apicast-production''][0] }}' - when: 'threescale_cicd_apicast_production_endpoint is not defined and ''apicast-production'' in groups and groups[''apicast-production''] > 0' +# Warn the user about those deprecated features +- import_tasks: steps/variables_from_inventory.yml # Load the API definition from the provided OpenAPI file -- import_tasks: read_openapi_file.yml - - -- name: Compute the service system_name - set_fact: - threescale_cicd_api_system_name: '{{ threescale_cicd_api_environment_name ~ "_" ~ threescale_cicd_api_system_name }}' - when: 'threescale_cicd_api_environment_name is defined' - -- debug: - msg: "Will work on service with system_name = {{ threescale_cicd_api_system_name }}" - -- set_fact: - threescale_cicd_api_deployment_type: 'self_managed' - when: 'threescale_cicd_api_deployment_type is not defined and (threescale_cicd_apicast_sandbox_endpoint is defined or threescale_cicd_apicast_production_endpoint is defined)' - -- set_fact: - threescale_cicd_api_deployment_type: 'hosted' - when: 'threescale_cicd_api_deployment_type is not defined' +- import_tasks: steps/read_openapi.yml -- set_fact: - threescale_cicd_api_service_definition: - name: '{{ threescale_cicd_api_name }}' - deployment_option: '{{ threescale_cicd_api_deployment_type }}' - system_name: '{{ threescale_cicd_api_system_name }}' - backend_version: '{{ threescale_cicd_api_backend_version }}' +# Discover the current state of the platform +- import_tasks: steps/discover.yml -# Create the service definition -- import_tasks: create_service.yml +# Create or update the service definition +- import_tasks: steps/service.yml -- set_fact: - threescale_cicd_api_credentials_location: '{{ ''headers'' if threescale_cicd_api_security_scheme.in == ''header'' else threescale_cicd_api_security_scheme.in }}' - when: 'threescale_cicd_api_security_scheme.type == ''apiKey''' +# Create or update the methods +- import_tasks: steps/methods.yml -- set_fact: - threescale_cicd_api_credentials_location: 'headers' - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- set_fact: - threescale_cicd_api_proxy_definition: - credentials_location: '{{ threescale_cicd_api_credentials_location }}' - api_backend: '{{ threescale_cicd_private_base_url }}' - -- set_fact: - threescale_cicd_api_proxy_definition: '{{ threescale_cicd_api_proxy_definition|combine({ ''auth_user_key'': threescale_cicd_api_security_scheme.name }) }}' - when: 'threescale_cicd_api_security_scheme.type == ''apiKey''' - -- set_fact: - threescale_cicd_api_proxy_definition: '{{ threescale_cicd_api_proxy_definition|combine({ ''oidc_issuer_endpoint'': threescale_cicd_sso_issuer_endpoint }) }}' - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- set_fact: - threescale_cicd_api_proxy_definition: '{{ threescale_cicd_api_proxy_definition|combine({ ''sandbox_endpoint'': threescale_cicd_apicast_sandbox_endpoint }) }}' - when: 'threescale_cicd_apicast_sandbox_endpoint is defined' - -- set_fact: - threescale_cicd_api_proxy_definition: '{{ threescale_cicd_api_proxy_definition|combine({ ''endpoint'': threescale_cicd_apicast_production_endpoint }) }}' - when: 'threescale_cicd_apicast_production_endpoint is defined' - -# Update the metrics -- import_tasks: update_metrics.yml - -# Update the mapping rules -- import_tasks: update_mapping_rules.yml +# Create, update or delete the mapping rules +- import_tasks: steps/mapping_rules.yml # Update the proxy -- import_tasks: update_proxy.yml +- import_tasks: steps/proxy.yml -- name: Get the list of existing application plans - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/application_plans.json?access_token={{ threescale_cicd_access_token|urlencode }} - validate_certs: no - register: threescale_cicd_tmpresponse +# Create or update application plans +- import_tasks: steps/application_plans.yml -- set_fact: - threescale_cicd_existing_application_plans: '{{ threescale_cicd_tmpresponse.json|json_query(''plans[*].application_plan.system_name'') }}' - threescale_cicd_existing_application_plans_details: '{{ threescale_cicd_tmpresponse.json|json_query(''plans[].{"system_name": application_plan.system_name, "id": application_plan.id}'') }}' - -# Create application plans if needed -- include_tasks: create_application_plans.yml - with_items: '{{ threescale_cicd_application_plans|default([]) }}' - loop_control: - loop_var: threescale_cicd_tmp_plan +# Create or update the default application if smoke tests are needed +- include_tasks: steps/default_application.yml + when: 'threescale_cicd_openapi_smoketest_path is defined and threescale_cicd_application_plans is defined' # Run smoke tests on the staging gateway -- include_tasks: smoke_tests.yml +- include_tasks: steps/smoke_test.yml vars: - threescale_cicd_env: staging + threescale_cicd_smoke_test_env: staging when: 'threescale_cicd_openapi_smoketest_path is defined and threescale_cicd_application_plans is defined' # Promote to production -- import_tasks: promote.yml +- import_tasks: steps/promote.yml # Run smoke tests on the production gateway -- include_tasks: smoke_tests.yml +- include_tasks: steps/smoke_test.yml vars: - threescale_cicd_env: production + threescale_cicd_smoke_test_env: production when: 'threescale_cicd_openapi_smoketest_path is defined and threescale_cicd_application_plans is defined' # Delete the metrics that are not needed anymore -- import_tasks: delete_unused_metrics.yml +- import_tasks: steps/cleanup_metrics.yml # Publish the OpenAPI Specifications file on the 3scale Admin Portal -- import_tasks: create_activedocs.yml +- import_tasks: steps/activedoc.yml diff --git a/tasks/patch_default_application_for_oauth.yml b/tasks/patch_default_application_for_oauth.yml deleted file mode 100644 index c5743e5..0000000 --- a/tasks/patch_default_application_for_oauth.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- - -- name: Prepare the OAuth Request to RH-SSO (static params) - set_fact: - threescale_cicd_tmp_body: "" - -- name: Prepare the OAuth Request to RH-SSO (urlencode dynamic params) - set_fact: - threescale_cicd_tmp_body: '{{ threescale_cicd_tmp_body ~ "&" ~ threescale_cicd_tmp_param.key ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: - client_id: '{{ threescale_cicd_sso_issuer_endpoint|urlsplit(''username'') }}' - client_secret: '{{ threescale_cicd_sso_issuer_endpoint|urlsplit(''password'') }}' - scope: '{{ threescale_cicd_openapi_smoketest_default_scope }}' - grant_type: client_credentials - loop_control: - loop_var: threescale_cicd_tmp_param - -- name: Authenticate to RH-SSO using the 3scale service account - uri: - url: '{{ threescale_cicd_sso_realm_endpoint }}/protocol/openid-connect/token' - body: '{{ threescale_cicd_tmp_body }}' - method: POST - validate_certs: no - return_content: yes - register: threescale_cicd_tmpresponse - retries: '{{ threescale_cicd_retries }}' - delay: '{{ threescale_cicd_delay }}' - # temporary fix for https://github.com/ansible/ansible/issues/28078 - until: 'threescale_cicd_tmpresponse|success' - -- name: Extract the access_token - set_fact: - threescale_cicd_openapi_tmp_access_token: '{{ threescale_cicd_tmpresponse.json |json_query("access_token") }}' - -- name: Wait for the new client to appear in RH-SSO - uri: - url: '{{ threescale_cicd_sso_admin_endpoint }}/clients?clientId={{ threescale_cicd_default_application_appid|urlencode }}' - method: GET - validate_certs: no - return_content: yes - headers: - Authorization: 'Bearer {{ threescale_cicd_openapi_tmp_access_token }}' - register: threescale_cicd_tmpresponse - retries: '{{ threescale_cicd_retries }}' - delay: '{{ threescale_cicd_delay }}' - until: 'threescale_cicd_tmpresponse|success and threescale_cicd_tmpresponse.json|length > 0' - -- set_fact: - threescale_cicd_default_application_sso_id: '{{ threescale_cicd_tmpresponse.json[0].id }}' - threescale_cicd_tmp_body: '{{ threescale_cicd_tmpresponse.json[0]|combine({ ''serviceAccountsEnabled'': true, ''standardFlowEnabled'': false, ''implicitFlowEnabled'': false, ''directAccessGrantsEnabled'': true }) }}' - -- name: Patch the client in RH-SSO to support the "client_credentials" and "password" grant_type. - uri: - url: '{{ threescale_cicd_sso_admin_endpoint }}/clients/{{ threescale_cicd_default_application_sso_id|urlencode }}' - method: PUT - validate_certs: no - body: '{{ threescale_cicd_tmp_body|to_json }}' - status_code: '200,204' - headers: - Authorization: 'Bearer {{ threescale_cicd_openapi_tmp_access_token }}' - Content-Type: 'application/json' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/promote.yml b/tasks/promote.yml deleted file mode 100644 index 76fcc79..0000000 --- a/tasks/promote.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- - -- name: Get the version of the staging proxy definition - uri: - url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/configs/{{ threescale_cicd_staging_environment_name }}/latest.json?access_token={{ threescale_cicd_access_token|urlencode }}' - validate_certs: no - register: threescale_cicd_tmpresponse - -- set_fact: - threescale_cicd_tmp_staging_proxy_version: '{{ threescale_cicd_tmpresponse.json.proxy_config.version }}' - -- name: Get the version of the production proxy definition - uri: - url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/configs/{{ threescale_cicd_production_environment_name }}/latest.json?access_token={{ threescale_cicd_access_token|urlencode }}' - validate_certs: no - status_code: 200,404 - register: threescale_cicd_tmpresponse - -- set_fact: - threescale_cicd_tmp_production_proxy_version: '{{ threescale_cicd_tmpresponse.json.proxy_config.version }}' - when: 'threescale_cicd_tmpresponse.status == 200' - -- set_fact: - threescale_cicd_tmp_production_proxy_version: 'NONE' - when: 'threescale_cicd_tmpresponse.status == 404' - -- set_fact: - threescale_cicd_tmp_body_create_svc: 'access_token={{ threescale_cicd_access_token|urlencode }}&to={{ threescale_cicd_production_environment_name|urlencode }}' - -- name: Promote to production - uri: - url: 'https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/configs/{{ threescale_cicd_staging_environment_name }}/{{ threescale_cicd_tmp_staging_proxy_version }}/promote.json' - body: '{{ threescale_cicd_tmp_body_create_svc }}' - status_code: 201 - validate_certs: no - method: POST - register: threescale_cicd_tmpresponse - when: 'threescale_cicd_tmp_staging_proxy_version != threescale_cicd_tmp_production_proxy_version' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/read_openapi_file.yml b/tasks/read_openapi_file.yml deleted file mode 100644 index 3fe784f..0000000 --- a/tasks/read_openapi_file.yml +++ /dev/null @@ -1,188 +0,0 @@ ---- -- name: Parse the OpenAPI file (YAML format) - set_fact: - threescale_cicd_openapi_file_content: '{{ lookup(''file'', threescale_cicd_openapi_file) |from_yaml }}' - when: "threescale_cicd_openapi_file_format|upper == 'YAML'" - -- name: Parse the OpenAPI file (JSON format) - set_fact: - threescale_cicd_openapi_file_content: '{{ lookup(''file'', threescale_cicd_openapi_file) |from_json }}' - when: "threescale_cicd_openapi_file_format|upper == 'JSON'" - -- name: Extract the OpenAPI format version - set_fact: - threescale_cicd_openapi_file_version: '{{ threescale_cicd_openapi_file_content|json_query(''swagger'') }}' - -- name: Check the OpenAPI format version - assert: - that: - - "threescale_cicd_openapi_file_version == '2.0'" - msg: "Currently only the OpenAPI/Swagger 2.0 is handled. If needed, fill an issue or submit a pull request!" - -# TODO rewrite this in a more "Ansible compatible" way -- name: Extract API Methods - set_fact: - threescale_cicd_api_name: '{{ threescale_cicd_openapi_file_content.info.title|default("API") }}' - threescale_cicd_api_description: '{{ threescale_cicd_openapi_file_content.info.description|default("") }}' - threescale_cicd_api_version: '{{ threescale_cicd_openapi_file_content.info.version|default("0.0.1") }}' - threescale_cicd_api_basepath: '{{ threescale_cicd_openapi_file_content.basePath|default("") }}' - threescale_cicd_api_operations: >- - {% set operations = {} -%} - {% if 'paths' in threescale_cicd_openapi_file_content -%} - {% for path, verbs in threescale_cicd_openapi_file_content['paths'].items() -%} - {% if path.startswith('/') -%} - {% for verb, method_description in verbs.items() -%} - {% if verb != '$ref' and verb != 'parameters' -%} - {% if 'operationId' in method_description -%} - {% set operation_id = method_description['operationId'] -%} - {% else -%} - {% set operation_id = verb.upper() + path -%} - {% endif -%} - {% set operation_id = operation_id|regex_replace('[^0-9a-zA-Z_]+', '_') -%} - {% set operation = { operation_id: { 'path': path, 'verb': verb } } -%} - {% if 'summary' in method_description -%} - {% if operation[operation_id].update({ 'friendly_name': method_description.summary }) -%}{% endif -%} - {% endif -%} - {% if 'description' in method_description -%} - {% if operation[operation_id].update({ 'description': method_description.description }) -%}{% endif -%} - {% endif -%} - {% if operations.update(operation) -%}{% endif -%} - {% endif -%} - {% endfor -%} - {% endif -%} - {% endfor -%} - {% endif -%} - {{ operations }} - -- name: Extract components from the version number - set_fact: - threescale_cicd_api_version_components: '{{ threescale_cicd_api_version.split(".") }}' - -- name: Find the major version - set_fact: - threescale_cicd_api_version_major: '{{ threescale_cicd_api_version_components|first }}' - -- name: Compute the system_name suffix to append to the generated system_name - set_fact: - threescale_cicd_api_system_name_suffix: '{{ (threescale_cicd_api_system_name is not defined)|ternary("_" ~ (threescale_cicd_api_version_major|regex_replace(''[^a-zA-Z0-9_]+'', ''_'')), "") }}' - -- name: Compute the system_name prefix to prepend to the generated system_name - set_fact: - threescale_cicd_api_system_name_prefix: '{{ (threescale_cicd_api_system_name is not defined and threescale_cicd_api_environment_name is defined)|ternary((threescale_cicd_api_environment_name|default("")|regex_replace(''[^a-zA-Z0-9_]+'', ''_'')) ~ "_", "") }}' - -- name: Extract the wanted system_name from OpenAPI - set_fact: - threescale_cicd_api_system_name: '{{ threescale_cicd_openapi_file_content.info[''x-threescale-system-name'']|regex_replace(''[^a-zA-Z0-9_]+'', ''_'')|lower }}' - when: 'threescale_cicd_api_system_name is not defined and ''x-threescale-system-name'' in threescale_cicd_openapi_file_content.info' - -- name: Generate a system_name from the API title - set_fact: - threescale_cicd_api_system_name: '{{ threescale_cicd_openapi_file_content.info[''title'']|default(''api'')|regex_replace(''[^a-zA-Z0-9_]+'', ''_'')|lower }}' - when: 'threescale_cicd_api_system_name is not defined' - -- name: Append the major version to the system_name - set_fact: - threescale_cicd_api_system_name: '{{ threescale_cicd_api_system_name }}{{ threescale_cicd_api_system_name_suffix }}' - -- name: Set the threescale_cicd_apicast_{sandbox,production}_endpoint variable from the wildcard domain - set_fact: - threescale_cicd_apicast_sandbox_endpoint: '{{ threescale_cicd_default_apicast_scheme }}://{{ threescale_cicd_api_system_name|regex_replace(''[^a-zA-Z0-9-]+'', ''-'')|lower }}{{ threescale_cicd_default_staging_suffix }}.{{ threescale_cicd_wildcard_domain }}' - threescale_cicd_apicast_production_endpoint: '{{ threescale_cicd_default_apicast_scheme }}://{{ threescale_cicd_api_system_name|regex_replace(''[^a-zA-Z0-9-]+'', ''-'')|lower }}{{ threescale_cicd_default_production_suffix }}.{{ threescale_cicd_wildcard_domain }}' - when: 'threescale_cicd_wildcard_domain is defined' - -- name: Prefix the system_name with the environment - set_fact: - threescale_cicd_api_system_name: '{{ threescale_cicd_api_system_name_prefix }}{{ threescale_cicd_api_system_name }}' - -- name: Append the full version to the API title - set_fact: - threescale_cicd_api_name: '{{ threescale_cicd_api_name }} (v{{ threescale_cicd_api_version }})' - when: 'threescale_cicd_api_environment_name is not defined' - -- name: Append the full version and the environment to the API title - set_fact: - threescale_cicd_api_name: '{{ threescale_cicd_api_name }} ({{ threescale_cicd_api_environment_name|upper }}, v{{ threescale_cicd_api_version }})' - when: 'threescale_cicd_api_environment_name is defined' - -- name: Extract the security definitions and requirements from OpenAPI - set_fact: - threescale_cicd_api_security_requirements: '{{ threescale_cicd_openapi_file_content.security|default([]) }}' - threescale_cicd_api_security_definitions: '{{ threescale_cicd_openapi_file_content.securityDefinitions|default({}) }}' - -- name: Make sure there is one and exactly one security requirement - assert: - that: - - 'threescale_cicd_api_security_requirements|length == 1' - msg: 'You have {{ threescale_cicd_api_security_requirements|length }} global security requirements. There must be one and only one security requirement.' - -- name: Find the security requirement to use - set_fact: - threescale_cicd_api_security_scheme_name: '{{ threescale_cicd_api_security_requirements[0].keys()[0] }}' - -- name: Make sure the requested security definition exists - assert: - that: - - 'threescale_cicd_api_security_scheme_name in threescale_cicd_api_security_definitions' - -- name: Find the security definition to use - set_fact: - threescale_cicd_api_security_scheme: '{{ threescale_cicd_api_security_definitions[threescale_cicd_api_security_scheme_name] }}' - -- name: Make sure the security scheme is consistent with 3scale - assert: - that: - - 'threescale_cicd_api_security_scheme.type == ''apiKey'' or (threescale_cicd_api_security_scheme.type == ''oauth2'' and threescale_cicd_sso_issuer_endpoint is defined)' - -- name: Find the correct backend_version to use - set_fact: - threescale_cicd_api_backend_version: '1' - when: 'threescale_cicd_api_security_scheme.type == ''apiKey''' - -- name: Find the correct backend_version to use - set_fact: - threescale_cicd_api_backend_version: 'oidc' - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- name: Extract the backend hostname from OpenAPI - set_fact: - threescale_cicd_api_backend_hostname: '{{ threescale_cicd_openapi_file_content.host }}' - when: 'threescale_cicd_api_backend_hostname is not defined and ''host'' in threescale_cicd_openapi_file_content' - -- name: Extract the backend scheme from OpenAPI - set_fact: - threescale_cicd_api_backend_scheme: '{{ threescale_cicd_openapi_file_content.schemes|default(["http"])|first }}' - when: 'threescale_cicd_api_backend_scheme is not defined' - -- name: Compute the private base url from the OpenAPI file - set_fact: - threescale_cicd_private_base_url: '{{ threescale_cicd_api_backend_scheme ~ ''://'' ~ threescale_cicd_api_backend_hostname }}' - when: threescale_cicd_api_backend_hostname is defined and threescale_cicd_private_base_url is not defined - -- assert: - that: - - 'threescale_cicd_private_base_url is defined' - msg: 'Either the private base url or the tuple backend hostname/scheme must be declared as extra variables (either threescale_cicd_private_base_url or threescale_cicd_api_backend_scheme / threescale_cicd_api_backend_hostname)' - -- name: Find the smoke-test flagged operation - set_fact: - threescale_cicd_openapi_smoketest_operation: '{{ threescale_cicd_openapi_file_content|json_query(''paths.*.get[? "x-threescale-smoketests-operation" ].operationId|[0]'') }}' - when: 'threescale_cicd_openapi_smoketest_operation is not defined' - -- assert: - that: - # Operation must exists - - 'threescale_cicd_openapi_smoketest_operation in threescale_cicd_api_operations' - # Must be a GET - - 'threescale_cicd_api_operations[threescale_cicd_openapi_smoketest_operation].verb == ''get''' - # Must NOT have a placeholder in the path - - 'threescale_cicd_api_operations[threescale_cicd_openapi_smoketest_operation].path.find("{") == -1' - msg: "The smoketest operation {{ threescale_cicd_openapi_smoketest_operation }} must be a GET and cannot have a placeholder in its path." - when: 'threescale_cicd_openapi_smoketest_operation is defined and threescale_cicd_openapi_smoketest_operation|length > 0' - -- set_fact: - threescale_cicd_openapi_smoketest_operation: '{{ threescale_cicd_openapi_smoketest_operation|regex_replace(''[^0-9a-zA-Z_]+'', ''_'') }}' - when: 'threescale_cicd_openapi_smoketest_operation is defined and threescale_cicd_openapi_smoketest_operation|length > 0' - -- set_fact: - threescale_cicd_openapi_smoketest_path: '{{ threescale_cicd_api_basepath }}{{ threescale_cicd_api_operations[threescale_cicd_openapi_smoketest_operation].path }}' - when: 'threescale_cicd_openapi_smoketest_operation is defined and threescale_cicd_openapi_smoketest_operation|length > 0' diff --git a/tasks/smoke_tests.yml b/tasks/smoke_tests.yml deleted file mode 100644 index 356107a..0000000 --- a/tasks/smoke_tests.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- - -- import_tasks: create_default_application.yml - -- set_fact: - threescale_cicd_tmp_gateway_endpoint: "" - -- name: Try to get the staging gateway url from extra var / inventory - set_fact: - threescale_cicd_tmp_gateway_endpoint: '{{ threescale_cicd_apicast_sandbox_endpoint }}' - when: "threescale_cicd_apicast_sandbox_endpoint is defined and threescale_cicd_env == 'staging'" - -- name: Try to get the production gateway url from extra var / inventory - set_fact: - threescale_cicd_tmp_gateway_endpoint: '{{ threescale_cicd_apicast_production_endpoint }}' - when: "threescale_cicd_apicast_production_endpoint is defined and threescale_cicd_env == 'production'" - -- name: Get the gateway endpoint from the proxy definition - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy.json?access_token={{ threescale_cicd_access_token|urlencode }} - validate_certs: no - method: GET - register: threescale_cicd_tmpresponse - when: "threescale_cicd_tmp_gateway_endpoint|length == 0" - -- name: Extract the staging gateway endpoint from the proxy definition - set_fact: - threescale_cicd_tmp_gateway_endpoint: '{{ threescale_cicd_tmpresponse.json|json_query(''proxy.sandbox_endpoint'') }}' - when: "threescale_cicd_tmp_gateway_endpoint|length == 0 and threescale_cicd_env == 'staging'" - -- name: Extract the production gateway endpoint from the proxy definition - set_fact: - threescale_cicd_tmp_gateway_endpoint: '{{ threescale_cicd_tmpresponse.json|json_query(''proxy.endpoint'') }}' - when: "threescale_cicd_tmp_gateway_endpoint|length == 0 and threescale_cicd_env == 'production'" - -- set_fact: - threescale_cicd_openapi_smoketest_querystring: "" - threescale_cicd_openapi_smoketest_headers: {} - -- include_tasks: smoke_tests_apikey.yml - when: 'threescale_cicd_api_security_scheme.type == ''apiKey''' - -- include_tasks: smoke_tests_oauth.yml - when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' - -- debug: - msg: "Starting a smoke test on '{{ threescale_cicd_tmp_gateway_endpoint }}{{ threescale_cicd_openapi_smoketest_path }}'..." - -- name: Running smoke tests ! - uri: - url: '{{ threescale_cicd_tmp_gateway_endpoint }}{{ threescale_cicd_openapi_smoketest_path }}{{ threescale_cicd_openapi_smoketest_querystring }}' - headers: '{{ threescale_cicd_openapi_smoketest_headers }}' - validate_certs: no - method: GET - register: threescale_cicd_tmpresponse - retries: '{{ threescale_cicd_retries }}' - delay: '{{ threescale_cicd_delay }}' - # temporary fix for https://github.com/ansible/ansible/issues/28078 - until: 'threescale_cicd_tmpresponse|success' diff --git a/tasks/smoke_tests_apikey.yml b/tasks/smoke_tests_apikey.yml deleted file mode 100644 index b6c1ed8..0000000 --- a/tasks/smoke_tests_apikey.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- - -- set_fact: - threescale_cicd_openapi_smoketest_querystring: "?{{ threescale_cicd_api_security_scheme.name|urlencode }}={{ threescale_cicd_default_application_details.user_key }}" - when: 'threescale_cicd_api_credentials_location == "query"' - -- set_fact: - threescale_cicd_openapi_smoketest_headers: "{{ threescale_cicd_openapi_smoketest_headers|combine({ threescale_cicd_api_security_scheme.name|urlencode: threescale_cicd_default_application_details.user_key}) }}" - when: 'threescale_cicd_api_credentials_location == "headers"' diff --git a/tasks/smoke_tests_oauth.yml b/tasks/smoke_tests_oauth.yml deleted file mode 100644 index 94b792c..0000000 --- a/tasks/smoke_tests_oauth.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- - -- name: Prepare the OAuth Request to RH-SSO (static params) - set_fact: - threescale_cicd_tmp_body: "" - -- name: Prepare the OAuth Request to RH-SSO (urlencode dynamic params) - set_fact: - threescale_cicd_tmp_body: '{{ threescale_cicd_tmp_body ~ "&" ~ threescale_cicd_tmp_param.key ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: - client_id: '{{ threescale_cicd_default_application_details.client_id }}' - client_secret: '{{ threescale_cicd_default_application_details.client_secret }}' - scope: '{{ threescale_cicd_openapi_smoketest_default_scope }}' - grant_type: client_credentials - loop_control: - loop_var: threescale_cicd_tmp_param - -- name: Authenticate to RH-SSO using the default application credentials - uri: - url: '{{ threescale_cicd_sso_realm_endpoint }}/protocol/openid-connect/token' - body: '{{ threescale_cicd_tmp_body }}' - method: POST - validate_certs: no - return_content: yes - register: threescale_cicd_tmpresponse - retries: '{{ threescale_cicd_retries }}' - delay: '{{ threescale_cicd_delay }}' - # temporary fix for https://github.com/ansible/ansible/issues/28078 - until: 'threescale_cicd_tmpresponse|success' - -- name: Extract the access_token - set_fact: - threescale_cicd_openapi_smoketest_access_token: '{{ threescale_cicd_tmpresponse.json |json_query("access_token") }}' - -- set_fact: - threescale_cicd_openapi_smoketest_headers: "{{ threescale_cicd_openapi_smoketest_headers|combine({ 'Authorization': 'Bearer ' ~ threescale_cicd_openapi_smoketest_access_token }) }}" diff --git a/tasks/steps/activedoc.yml b/tasks/steps/activedoc.yml new file mode 100644 index 0000000..0b367e0 --- /dev/null +++ b/tasks/steps/activedoc.yml @@ -0,0 +1,10 @@ +--- +- debug: + var: threescale_cicd_openapi_rewritten + verbosity: 1 + +- include_tasks: api-calls/update_activedoc.yml + when: 'threescale_cicd_api_system_name in threescale_cicd_existing_activedocs' + +- include_tasks: api-calls/create_activedoc.yml + when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_activedocs' diff --git a/tasks/steps/application_plan.yml b/tasks/steps/application_plan.yml new file mode 100644 index 0000000..0953092 --- /dev/null +++ b/tasks/steps/application_plan.yml @@ -0,0 +1,8 @@ +--- + +- include_tasks: api-calls/update_application_plan.yml + when: 'threescale_cicd_application_plan.system_name in threescale_cicd_existing_application_plans' + +- include_tasks: api-calls/create_application_plan.yml + when: 'threescale_cicd_application_plan.system_name not in threescale_cicd_existing_application_plans' + diff --git a/tasks/steps/application_plans.yml b/tasks/steps/application_plans.yml new file mode 100644 index 0000000..2607a7f --- /dev/null +++ b/tasks/steps/application_plans.yml @@ -0,0 +1,6 @@ +--- + +- include_tasks: steps/application_plan.yml + with_items: '{{ threescale_cicd_application_plans|default([]) }}' + loop_control: + loop_var: threescale_cicd_application_plan diff --git a/tasks/steps/cleanup_metrics.yml b/tasks/steps/cleanup_metrics.yml new file mode 100644 index 0000000..08a9ab0 --- /dev/null +++ b/tasks/steps/cleanup_metrics.yml @@ -0,0 +1,6 @@ +--- + +- include_tasks: "api-calls/delete_metric.yml" + with_items: '{{ threescale_cicd_metrics_to_delete }}' + loop_control: + loop_var: threescale_cicd_metric diff --git a/tasks/steps/default_application.yml b/tasks/steps/default_application.yml new file mode 100644 index 0000000..860032b --- /dev/null +++ b/tasks/steps/default_application.yml @@ -0,0 +1,32 @@ +--- + +- import_tasks: "api-calls/find_first_account.yml" + when: 'threescale_cicd_default_account_id is not defined' + +- import_tasks: "api-calls/find_application.yml" + +- import_tasks: "api-calls/update_application.yml" + when: 'threescale_cicd_default_application_id is defined' + +- import_tasks: "api-calls/create_application.yml" + when: 'threescale_cicd_default_application_id is not defined' + +## +## When using OAuth / OIDC authentication, we need to patch the Keycloak client +## to support the client_credentials grant. +## + +- include_tasks: api-calls/keycloak/authenticate.yml + when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' + vars: + oauth_payload: + client_id: '{{ threescale_cicd_sso_issuer_endpoint|urlsplit(''username'') }}' + client_secret: '{{ threescale_cicd_sso_issuer_endpoint|urlsplit(''password'') }}' + scope: '{{ threescale_cicd_openapi_smoketest_default_scope }}' + grant_type: 'client_credentials' + +- include_tasks: api-calls/keycloak/wait_for_client.yml + when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' + +- include_tasks: api-calls/keycloak/patch_client.yml + when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' diff --git a/tasks/steps/discover.yml b/tasks/steps/discover.yml new file mode 100644 index 0000000..1ead278 --- /dev/null +++ b/tasks/steps/discover.yml @@ -0,0 +1,59 @@ +--- + +- name: Retrieve existing Services from the 3scale Admin Portal + uri: + url: "https://{{ inventory_hostname }}/admin/api/services.json?access_token={{ threescale_cicd_access_token|urlencode }}" + validate_certs: no + register: threescale_cicd_tmpresponse + when: threescale_cicd_existing_services is not defined + +- set_fact: + threescale_cicd_existing_services: '{{ threescale_cicd_tmpresponse.json|json_query(''services[*].service.system_name'') }}' + threescale_cicd_existing_services_details: '{{ threescale_cicd_tmpresponse.json|json_query(''services[].{"system_name": service.system_name, "id": service.id}'') }}' + cacheable: true + when: threescale_cicd_existing_services is not defined + +- debug: + msg: "Found {{ threescale_cicd_existing_services|length }} services" + verbosity: 1 + +- debug: + var: threescale_cicd_existing_services_details + verbosity: 1 + +- name: Get the list of existing application plans + uri: + url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/application_plans.json?access_token={{ threescale_cicd_access_token|urlencode }} + validate_certs: no + register: threescale_cicd_tmpresponse + when: threescale_cicd_api_system_name in threescale_cicd_existing_services + +- set_fact: + threescale_cicd_existing_application_plans: '{{ threescale_cicd_tmpresponse.json|json_query(''plans[*].application_plan.system_name'') if threescale_cicd_api_system_name in threescale_cicd_existing_services else [] }}' + threescale_cicd_existing_application_plans_details: '{{ threescale_cicd_tmpresponse.json|json_query(''plans[].{"system_name": application_plan.system_name, "id": application_plan.id}'') if threescale_cicd_api_system_name in threescale_cicd_existing_services else [] }}' + +- debug: + msg: "Found {{ threescale_cicd_existing_application_plans|length }} application plans" + verbosity: 1 + +- debug: + var: threescale_cicd_existing_application_plans_details + verbosity: 1 + +- name: Retrieve existing ActiveDocs from the 3scale Admin Portal + uri: + url: "https://{{ inventory_hostname }}/admin/api/active_docs.json?access_token={{ threescale_cicd_access_token|urlencode }}" + validate_certs: no + register: threescale_cicd_tmp_allactivedocs + +- set_fact: + threescale_cicd_existing_activedocs: '{{ threescale_cicd_tmp_allactivedocs.json|json_query(''api_docs[*].api_doc.system_name'') }}' + threescale_cicd_existing_activedocs_details: '{{ threescale_cicd_tmp_allactivedocs.json|json_query(''api_docs[].{"system_name": api_doc.system_name, "id": api_doc.id}'') }}' + +- debug: + msg: "Found {{ threescale_cicd_existing_activedocs|length }} active docs" + verbosity: 1 + +- debug: + var: threescale_cicd_existing_activedocs_details + verbosity: 1 diff --git a/tasks/steps/mapping_rules.yml b/tasks/steps/mapping_rules.yml new file mode 100644 index 0000000..299cbe7 --- /dev/null +++ b/tasks/steps/mapping_rules.yml @@ -0,0 +1,25 @@ +--- + +- name: Retrieve existing mapping rules from the 3scale Admin Portal + uri: + url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules.json?access_token={{ threescale_cicd_access_token|urlencode }}" + validate_certs: no + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_existing_mapping_rules_details: '{{ threescale_cicd_tmpresponse.json|json_query(''mapping_rules[].{"metric_id": mapping_rule.metric_id, "id": mapping_rule.id}'') }}' + +- include_tasks: "api-calls/create_mapping_rule.yml" + with_items: '{{ threescale_cicd_mapping_rules_to_create }}' + loop_control: + loop_var: threescale_cicd_mapping_rule + +- include_tasks: "api-calls/update_mapping_rule.yml" + with_items: '{{ threescale_cicd_mapping_rules_to_update }}' + loop_control: + loop_var: threescale_cicd_mapping_rule + +- include_tasks: "api-calls/delete_mapping_rule.yml" + with_items: '{{ threescale_cicd_mapping_rules_to_delete }}' + loop_control: + loop_var: threescale_cicd_mapping_rule diff --git a/tasks/steps/method.yml b/tasks/steps/method.yml new file mode 100644 index 0000000..cd7911e --- /dev/null +++ b/tasks/steps/method.yml @@ -0,0 +1,7 @@ +--- + +- include_tasks: api-calls/update_method.yml + when: 'threescale_cicd_api_operation.key in threescale_cicd_existing_metrics' + +- include_tasks: api-calls/create_method.yml + when: 'threescale_cicd_api_operation.key not in threescale_cicd_existing_metrics' diff --git a/tasks/steps/methods.yml b/tasks/steps/methods.yml new file mode 100644 index 0000000..abb9db8 --- /dev/null +++ b/tasks/steps/methods.yml @@ -0,0 +1,16 @@ +--- + +- name: Retrieve existing metrics from the 3scale Admin Portal + uri: + url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics.json?access_token={{ threescale_cicd_access_token|urlencode }}" + validate_certs: no + register: threescale_cicd_tmpresponse + +- set_fact: + threescale_cicd_existing_metrics: '{{ threescale_cicd_tmpresponse.json|json_query(''metrics[*].metric.system_name'') }}' + threescale_cicd_existing_metrics_details: '{{ threescale_cicd_tmpresponse.json|json_query(''metrics[].{"system_name": metric.system_name, "id": metric.id}'') }}' + +- include_tasks: "steps/method.yml" + with_dict: '{{ threescale_cicd_api_operations }}' + loop_control: + loop_var: threescale_cicd_api_operation diff --git a/tasks/steps/promote.yml b/tasks/steps/promote.yml new file mode 100644 index 0000000..daf8d79 --- /dev/null +++ b/tasks/steps/promote.yml @@ -0,0 +1,6 @@ +--- + +- import_tasks: "api-calls/get_proxy_version.yml" + +- include_tasks: "api-calls/promote_proxy.yml" + when: 'threescale_cicd_staging_proxy_version != threescale_cicd_production_proxy_version' diff --git a/tasks/steps/proxy.yml b/tasks/steps/proxy.yml new file mode 100644 index 0000000..9a9ae13 --- /dev/null +++ b/tasks/steps/proxy.yml @@ -0,0 +1,3 @@ +--- + +- import_tasks: api-calls/update_proxy.yml diff --git a/tasks/steps/read_openapi.yml b/tasks/steps/read_openapi.yml new file mode 100644 index 0000000..a7fbfd1 --- /dev/null +++ b/tasks/steps/read_openapi.yml @@ -0,0 +1,42 @@ +--- + +- name: Check the OpenAPI format version + assert: + that: + - "threescale_cicd_openapi_file_version == '2.0'" + msg: "Currently only the OpenAPI/Swagger 2.0 is handled. If needed, fill an issue or submit a pull request!" + +- name: Make sure there is one and exactly one security requirement + assert: + that: + - 'threescale_cicd_api_security_requirements|length == 1' + msg: 'You have {{ threescale_cicd_api_security_requirements|length }} global security requirements. There must be one and only one security requirement.' + +- name: Make sure the security scheme is consistent with 3scale + assert: + that: + - '''type'' in threescale_cicd_api_security_scheme and threescale_cicd_api_security_scheme.type == ''apiKey'' or (threescale_cicd_api_security_scheme.type == ''oauth2'' and threescale_cicd_sso_issuer_endpoint is defined)' + msg: |- + The embedded security definition {{ threescale_cicd_api_security_scheme_name }} is not compatible with 3scale. + Please make sure you chose an "apiKey" or "oauth2" scheme. + Also, if you chose "oauth2", you will need to pass the threescale_cicd_sso_issuer_endpoint extra variable. + The security definition you chose: {{ threescale_cicd_api_security_scheme|to_nice_json }} + +- assert: + that: + - 'threescale_cicd_private_base_url is defined' + msg: 'Either the private base url or the tuple backend hostname/scheme must be declared as extra variables (either threescale_cicd_private_base_url or threescale_cicd_api_backend_scheme / threescale_cicd_api_backend_hostname)' + +- assert: + that: + # Operation must exists + - 'threescale_cicd_openapi_smoketest_operation in threescale_cicd_api_operations' + # Must be a GET + - 'threescale_cicd_api_operations[threescale_cicd_openapi_smoketest_operation].verb == ''get''' + # Must NOT have a placeholder in the path + - 'threescale_cicd_api_operations[threescale_cicd_openapi_smoketest_operation].path.find("{") == -1' + msg: "The smoketest operation {{ threescale_cicd_openapi_smoketest_operation }} must be a GET and cannot have a placeholder in its path." + when: 'threescale_cicd_openapi_smoketest_operation|length > 0' + +- debug: + msg: "Will work on service with system_name = {{ threescale_cicd_api_system_name }}" diff --git a/tasks/steps/requirements.yml b/tasks/steps/requirements.yml new file mode 100644 index 0000000..f458d5d --- /dev/null +++ b/tasks/steps/requirements.yml @@ -0,0 +1,64 @@ +--- + +- name: Ensure pre-requisites are met + assert: + that: + - "threescale_cicd_access_token is defined" + - "threescale_cicd_openapi_file is defined" + msg: |- + This module requires at least two variables: + - threescale_cicd_access_token that contains an Access Token with Read/Write privileges on the 3scale Account Management API. This variable is usually set in your inventory file. + - threescale_cicd_openapi_file that is the path to the OpenAPI file you want to deploy in 3scale. This variable is usually passed as an extra variable (-e threescale_cicd_openapi_file=...) + +- name: Make sure the OpenAPI File Format is YAML or JSON + assert: + that: + - threescale_cicd_openapi_file_format|upper == 'JSON' or threescale_cicd_openapi_file_format|upper == 'YAML' + msg: |- + The threescale_cicd_openapi_file_format parameter needs to be either 'JSON' or 'YAML' + +- name: Verify that Ansible version is >= 2.4 + assert: + that: "ansible_version.full is version_compare('2.4', '>=')" + msg: This module requires at least Ansible 2.4 + +- name: Check if jmespath is installed locally + debug: msg={{dummy|json_query('@')}} + register: check_jmespath + ignore_errors: yes + vars: + dummy: Hello World + +- name: Check if jinja 2.8 is installed locally + debug: msg={{(dummy|selectattr("id", "equalto", "hello")|first)['value']}} + vars: + dummy: + - id: hello + value: Hello World + register: check_jinja28 + ignore_errors: yes + +- name: Check if the "do" jinja extension is enabled + debug: msg={% do {}.update({}) %}{{ success }} + vars: + success: 'The do extension is enabled' + register: check_jinja_do_ext + ignore_errors: yes + +- assert: + that: + - 'check_jmespath is success' + msg: "The JMESPath library is required by this role. Please install the JMESPath library with 'pip install jmespath'." + +- assert: + that: + - 'check_jinja28 is success' + msg: "At least Jinja v2.8 is required by this role. Please update Jinja with 'pip install -U Jinja2'." + +- assert: + that: + - 'check_jinja_do_ext is success' + msg: |- + You need to enable the 'do' extension of Jinja in your ansible.cfg: + [default] + jinja2_extensions = jinja2.ext.do diff --git a/tasks/steps/service.yml b/tasks/steps/service.yml new file mode 100644 index 0000000..4a58bf6 --- /dev/null +++ b/tasks/steps/service.yml @@ -0,0 +1,7 @@ +--- + +- include_tasks: api-calls/update_service.yml + when: 'threescale_cicd_api_system_name in threescale_cicd_existing_services' + +- include_tasks: api-calls/create_service.yml + when: 'threescale_cicd_api_system_name not in threescale_cicd_existing_services' diff --git a/tasks/steps/smoke_test.yml b/tasks/steps/smoke_test.yml new file mode 100644 index 0000000..aaa181a --- /dev/null +++ b/tasks/steps/smoke_test.yml @@ -0,0 +1,14 @@ +--- + +# Retrieve a valid access token if the API is secured with OAuth/OIDC +- include_tasks: api-calls/keycloak/authenticate.yml + when: 'threescale_cicd_api_security_scheme.type == ''oauth2''' + vars: + oauth_payload: + client_id: '{{ threescale_cicd_default_application_details.client_id }}' + client_secret: '{{ threescale_cicd_default_application_details.client_secret }}' + scope: '{{ threescale_cicd_openapi_smoketest_default_scope }}' + grant_type: 'client_credentials' + +# Do the smoke test +- import_tasks: api-calls/smoke_test.yml diff --git a/tasks/steps/variables_from_inventory.yml b/tasks/steps/variables_from_inventory.yml new file mode 100644 index 0000000..5c94bea --- /dev/null +++ b/tasks/steps/variables_from_inventory.yml @@ -0,0 +1,28 @@ +--- + +- name: Abort on deprecated feature -> the "sso" inventory group + fail: + msg: > + You are currently using a deprecated feature (the 'sso' group in your inventory). + Please replace it with the 'threescale_cicd_sso_issuer_endpoint' variable. + Alternatively, you can also bypass this warning by setting the 'threescale_cicd_deprecated_features' + extra variable to 'true'. + when: 'threescale_cicd_sso_issuer_endpoint|default("")|length > 0 and ''sso'' in groups and groups[''sso''] > 0 and threescale_cicd_api_backend_version == ''oidc'' and not threescale_cicd_deprecated_features|default(false)|bool' + +- name: Abort on deprecated feature -> the "apicast-sandbox" inventory group + fail: + msg: > + You are currently using a deprecated feature (the 'apicast-sandbox' group in your inventory). + Please replace it with the 'threescale_cicd_apicast_sandbox_endpoint' variable. + Alternatively, you can also bypass this warning by setting the 'threescale_cicd_deprecated_features' + extra variable to 'true'. + when: 'threescale_cicd_apicast_sandbox_endpoint|default("")|length > 0 and ''apicast-sandbox'' in groups and groups[''apicast-sandbox''] > 0 and not threescale_cicd_deprecated_features|default(false)|bool' + +- name: Abort on deprecated feature -> the "apicast-production" inventory group + fail: + msg: > + You are currently using a deprecated feature (the 'apicast-production' group in your inventory). + Please replace it with the 'threescale_cicd_apicast_production_endpoint' variable. + Alternatively, you can also bypass this warning by setting the 'threescale_cicd_deprecated_features' + extra variable to 'true'. + when: 'threescale_cicd_apicast_production_endpoint|default("")|length > 0 and ''apicast-production'' in groups and groups[''apicast-production''] > 0 and not threescale_cicd_deprecated_features|default(false)|bool' diff --git a/tasks/update_mapping_rule.yml b/tasks/update_mapping_rule.yml deleted file mode 100644 index a11a965..0000000 --- a/tasks/update_mapping_rule.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_tmp_wanted_mapping_rules[threescale_cicd_tmp_mapping_rule_to_update] }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- set_fact: - # Add the metric_id to the payload - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ "metric_id=" ~ ((threescale_cicd_existing_metrics_details|selectattr("system_name", "equalto", threescale_cicd_tmp_mapping_rule_to_update)|first).id|urlencode) }}' - # The ID of the mapping rule to update - threescale_cicd_tmp_mapping_rule_id: '{{ threescale_cicd_tmp_existing_mapping_rules[threescale_cicd_tmp_mapping_rule_to_update] }}' - -- name: Update the mapping rule - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules/{{ threescale_cicd_tmp_mapping_rule_id }}.json - validate_certs: no - method: PUT - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 200 - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/update_mapping_rules.yml b/tasks/update_mapping_rules.yml deleted file mode 100644 index 00d2cc8..0000000 --- a/tasks/update_mapping_rules.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- - -- name: Retrieve existing mapping rules from the 3scale Admin Portal - uri: - url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules.json?access_token={{ threescale_cicd_access_token|urlencode }}" - validate_certs: no - register: threescale_cicd_tmp_allmappingrules - -- set_fact: - threescale_cicd_existing_mappingrules_details: '{{ threescale_cicd_tmp_allmappingrules.json|json_query(''mapping_rules[].{"metric_id": mapping_rule.metric_id, "id": mapping_rule.id}'') }}' - threescale_cicd_tmp_wanted_mapping_rules: {} - threescale_cicd_tmp_existing_mapping_rules: {} - -- name: Build a list of our expected/wanted mapping rules - set_fact: - threescale_cicd_tmp_wanted_mapping_rules: '{{ threescale_cicd_tmp_wanted_mapping_rules|combine({ threescale_cicd_tmp_operation.key: { "http_method": threescale_cicd_tmp_operation.value.verb.upper(), "pattern": threescale_cicd_api_basepath ~ threescale_cicd_tmp_operation.value.path ~ "$", "delta": 1 } }) }}' - with_dict: '{{ threescale_cicd_api_operations }}' - loop_control: - loop_var: threescale_cicd_tmp_operation - -- name: Map metric id to system_name - set_fact: - threescale_cicd_tmp_existing_mapping_rules: '{{ threescale_cicd_tmp_existing_mapping_rules|combine({ (threescale_cicd_existing_metrics_details|selectattr("id", "equalto", threescale_cicd_tmp_metric.metric_id)|first).system_name: threescale_cicd_tmp_metric.id}) }}' - with_items: '{{ threescale_cicd_existing_mappingrules_details }}' - loop_control: - loop_var: threescale_cicd_tmp_metric - -- set_fact: - # create the items that we want but don't have yet - threescale_cicd_tmp_mapping_rules_to_create: '{{ threescale_cicd_tmp_wanted_mapping_rules.keys()|difference(threescale_cicd_tmp_existing_mapping_rules.keys()) }}' - # delete the items that we don't want but we have - threescale_cicd_tmp_mapping_rules_to_delete: '{{ threescale_cicd_tmp_existing_mapping_rules.keys()|difference(threescale_cicd_tmp_wanted_mapping_rules.keys()) }}' - # update the items that we want and we have - threescale_cicd_tmp_mapping_rules_to_update: '{{ threescale_cicd_tmp_existing_mapping_rules.keys()|intersect(threescale_cicd_tmp_wanted_mapping_rules.keys()) }}' - -- include_tasks: "create_mapping_rule.yml" - with_items: '{{ threescale_cicd_tmp_mapping_rules_to_create }}' - loop_control: - loop_var: threescale_cicd_tmp_mapping_rule_to_create - -- include_tasks: "update_mapping_rule.yml" - with_items: '{{ threescale_cicd_tmp_mapping_rules_to_update }}' - loop_control: - loop_var: threescale_cicd_tmp_mapping_rule_to_update - -- name: Delete the unused mapping rules - uri: - url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy/mapping_rules/{{ threescale_cicd_tmp_existing_mapping_rules[threescale_cicd_tmp_mapping_rule_to_delete] }}.json?access_token={{ threescale_cicd_access_token|urlencode }}" - validate_certs: no - method: DELETE - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - with_items: '{{ threescale_cicd_tmp_mapping_rules_to_delete }}' - loop_control: - loop_var: threescale_cicd_tmp_mapping_rule_to_delete - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/update_method.yml b/tasks/update_method.yml deleted file mode 100644 index 19e1d93..0000000 --- a/tasks/update_method.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- - -- set_fact: - threescale_cicd_api_method_definition: - system_name: '{{ threescale_cicd_tmp_operation.key }}' - friendly_name: '{{ threescale_cicd_tmp_operation.value.friendly_name|default(threescale_cicd_tmp_operation.key) }}' - description: '{{ threescale_cicd_tmp_operation.value.description|default('''') }}' - unit: 'hits' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_update_method: '{{ threescale_cicd_tmp_body_update_method ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_api_method_definition }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- name: Update the method - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics/{{ threescale_cicd_metric_id }}/methods/{{ (threescale_cicd_existing_metrics_details|selectattr('system_name', 'equalto', threescale_cicd_tmp_operation.key)|first).id }}.json - validate_certs: no - method: PATCH - body: '{{ threescale_cicd_tmp_body_update_method }}' - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - when: 'threescale_cicd_tmp_operation.key in threescale_cicd_existing_metrics' - -- name: Create the method - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics/{{ threescale_cicd_metric_id }}/methods.json - validate_certs: no - method: POST - body: '{{ threescale_cicd_tmp_body_update_method }}' - status_code: 201 - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 201' - when: 'threescale_cicd_tmp_operation.key not in threescale_cicd_existing_metrics' - -- set_fact: - threescale_cicd_existing_metrics: '{{ threescale_cicd_existing_metrics|union([ threescale_cicd_tmp_operation.key ]) }}' - threescale_cicd_existing_metrics_details: '{{ threescale_cicd_existing_metrics_details|union([ { "system_name": threescale_cicd_tmp_operation.key, "id": threescale_cicd_tmpresponse.json|json_query("method.id") } ]) }}' - when: 'threescale_cicd_tmp_operation.key not in threescale_cicd_existing_metrics' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/tasks/update_metrics.yml b/tasks/update_metrics.yml deleted file mode 100644 index 7c2ff6c..0000000 --- a/tasks/update_metrics.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- - -- name: Retrieve existing metrics from the 3scale Admin Portal - uri: - url: "https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/metrics.json?access_token={{ threescale_cicd_access_token|urlencode }}" - validate_certs: no - register: threescale_cicd_tmp_allmetrics - -- set_fact: - threescale_cicd_existing_metrics: '{{ threescale_cicd_tmp_allmetrics.json|json_query(''metrics[*].metric.system_name'') }}' - threescale_cicd_existing_metrics_details: '{{ threescale_cicd_tmp_allmetrics.json|json_query(''metrics[].{"system_name": metric.system_name, "id": metric.id}'') }}' - -- name: Find the "hits" metric id - set_fact: - threescale_cicd_metric_id: '{{ (threescale_cicd_existing_metrics_details|selectattr(''system_name'', ''equalto'', ''hits'')|first).id }}' - -- include_tasks: "update_method.yml" - with_dict: '{{ threescale_cicd_api_operations }}' - loop_control: - loop_var: threescale_cicd_tmp_operation diff --git a/tasks/update_proxy.yml b/tasks/update_proxy.yml deleted file mode 100644 index 34d4d13..0000000 --- a/tasks/update_proxy.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- - -- set_fact: - threescale_cicd_tmp_body_update_proxy: '{{ "access_token=" ~ threescale_cicd_access_token|urlencode }}' - -- set_fact: - threescale_cicd_tmp_body_update_proxy: '{{ threescale_cicd_tmp_body_update_proxy ~ "&" ~ (threescale_cicd_tmp_param.key|urlencode) ~ "=" ~ (threescale_cicd_tmp_param.value|urlencode) }}' - with_dict: '{{ threescale_cicd_api_proxy_definition }}' - loop_control: - loop_var: threescale_cicd_tmp_param - -- name: Update the proxy - uri: - url: https://{{ inventory_hostname }}/admin/api/services/{{ threescale_cicd_api_service_id }}/proxy.json - validate_certs: no - method: PATCH - body: '{{ threescale_cicd_tmp_body_update_proxy }}' - register: threescale_cicd_tmpresponse - changed_when: 'threescale_cicd_tmpresponse.status == 200' - -- name: Wait for a couple seconds - pause: - seconds: '{{ threescale_cicd_throttling }}' diff --git a/templates/api-calls/create_activedoc.j2 b/templates/api-calls/create_activedoc.j2 new file mode 100644 index 0000000..1c38736 --- /dev/null +++ b/templates/api-calls/create_activedoc.j2 @@ -0,0 +1,11 @@ +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode, + 'name=' ~ threescale_cicd_api_name|urlencode, + 'description=' ~ threescale_cicd_api_description|urlencode, + 'system_name=' ~ threescale_cicd_api_system_name|urlencode, + 'body=' ~ threescale_cicd_openapi_rewritten|to_nice_json|urlencode, + 'published=true', + ] +%} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/create_application.j2 b/templates/api-calls/create_application.j2 new file mode 100644 index 0000000..1426a42 --- /dev/null +++ b/templates/api-calls/create_application.j2 @@ -0,0 +1,16 @@ +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode, + 'plan_id=' ~ threescale_cicd_default_application_plan_id|urlencode, + 'name=' ~ threescale_cicd_default_application_name|urlencode, + 'description=' ~ threescale_cicd_default_application_description|urlencode + ] +%} +{% if threescale_cicd_api_security_scheme.type == 'oauth2' %} +{% do payload.append("application_id=" ~ threescale_cicd_default_application_appid|urlencode) %} +{% do payload.append("application_key=" ~ threescale_cicd_default_application_appsecret|urlencode) %} +{% endif %} +{% if threescale_cicd_api_security_scheme.type == 'apiKey' %} +{% do payload.append("user_key=" ~ threescale_cicd_default_application_appid|urlencode) %} +{% endif %} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/create_application_plan.j2 b/templates/api-calls/create_application_plan.j2 new file mode 100644 index 0000000..105d20e --- /dev/null +++ b/templates/api-calls/create_application_plan.j2 @@ -0,0 +1,9 @@ +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode + ] +%} +{% for key, value in threescale_cicd_application_plan.items() %} +{% do payload.append(key ~ "=" ~ value|urlencode) %} +{% endfor %} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/create_mapping_rule.j2 b/templates/api-calls/create_mapping_rule.j2 new file mode 100644 index 0000000..a7398fd --- /dev/null +++ b/templates/api-calls/create_mapping_rule.j2 @@ -0,0 +1,10 @@ +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode, + 'metric_id=' ~ ((threescale_cicd_existing_metrics_details|selectattr("system_name", "equalto", threescale_cicd_mapping_rule)|first).id|urlencode) + ] +%} +{% for key, value in threescale_cicd_wanted_mapping_rules[threescale_cicd_mapping_rule].items() %} +{% do payload.append(key ~ "=" ~ value|urlencode) %} +{% endfor %} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/create_method.j2 b/templates/api-calls/create_method.j2 new file mode 100644 index 0000000..c08ba83 --- /dev/null +++ b/templates/api-calls/create_method.j2 @@ -0,0 +1,10 @@ +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode, + 'friendly_name=' ~ threescale_cicd_api_operation.value.friendly_name|default(threescale_cicd_api_operation.key)|urlencode, + 'description=' ~ threescale_cicd_api_operation.value.description|default('')|urlencode, + 'system_name=' ~ threescale_cicd_api_operation.key|urlencode, + 'unit=hits' + ] +%} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/create_service.j2 b/templates/api-calls/create_service.j2 new file mode 100644 index 0000000..e28d770 --- /dev/null +++ b/templates/api-calls/create_service.j2 @@ -0,0 +1,15 @@ +{% if threescale_cicd_apicast_sandbox_endpoint|default("")|length > 0 or threescale_cicd_apicast_production_endpoint|default("")|length > 0 %} +{% set deployment_type = "self_managed" %} +{% else %} +{% set deployment_type = "hosted" %} +{% endif %} +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode, + 'name=' ~ threescale_cicd_api_name|urlencode, + 'deployment_option=' ~ deployment_type|urlencode, + 'system_name=' ~ threescale_cicd_api_system_name|urlencode, + 'backend_version=' ~ threescale_cicd_api_backend_version|urlencode + ] +%} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/find_application.j2 b/templates/api-calls/find_application.j2 new file mode 100644 index 0000000..b2dec63 --- /dev/null +++ b/templates/api-calls/find_application.j2 @@ -0,0 +1,7 @@ +access_token={{ threescale_cicd_access_token|urlencode }} +{%- if threescale_cicd_api_security_scheme.type == 'oauth2' -%} +&app_id={{ threescale_cicd_default_application_appid|urlencode }} +{%- endif -%} +{%- if threescale_cicd_api_security_scheme.type == 'apiKey' -%} +&user_key={{ threescale_cicd_default_application_appid|urlencode }} +{%- endif -%} \ No newline at end of file diff --git a/templates/api-calls/keycloak/authenticate.j2 b/templates/api-calls/keycloak/authenticate.j2 new file mode 100644 index 0000000..52ffe49 --- /dev/null +++ b/templates/api-calls/keycloak/authenticate.j2 @@ -0,0 +1,5 @@ +{% set payload = [ ] %} +{% for key, value in oauth_payload.items() %} +{% do payload.append(key ~ "=" ~ value|urlencode) %} +{% endfor %} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/keycloak/patch_client.j2 b/templates/api-calls/keycloak/patch_client.j2 new file mode 100644 index 0000000..1721809 --- /dev/null +++ b/templates/api-calls/keycloak/patch_client.j2 @@ -0,0 +1 @@ +{{ threescale_cicd_default_application_sso_body|combine({ 'serviceAccountsEnabled': true, 'standardFlowEnabled': false, 'implicitFlowEnabled': false, 'directAccessGrantsEnabled': true }) }} diff --git a/templates/api-calls/promote_proxy.j2 b/templates/api-calls/promote_proxy.j2 new file mode 100644 index 0000000..e1f60ae --- /dev/null +++ b/templates/api-calls/promote_proxy.j2 @@ -0,0 +1 @@ +access_token={{ threescale_cicd_access_token|urlencode }}&to={{ threescale_cicd_production_environment_name|urlencode }} \ No newline at end of file diff --git a/templates/api-calls/smoke-test/headers.j2 b/templates/api-calls/smoke-test/headers.j2 new file mode 100644 index 0000000..d03e293 --- /dev/null +++ b/templates/api-calls/smoke-test/headers.j2 @@ -0,0 +1,8 @@ +{% set headers = {} %} +{% if threescale_cicd_api_security_scheme.type == "apiKey" and threescale_cicd_api_credentials_location == "headers" %} +{% do headers.update({ threescale_cicd_api_security_scheme.name|urlencode: threescale_cicd_default_application_details.user_key }) %} +{% endif %} +{% if threescale_cicd_api_security_scheme.type == "oauth2" and threescale_cicd_api_credentials_location == "headers" %} +{% do headers.update({ 'Authorization': 'Bearer ' ~ threescale_cicd_keycloak_access_token }) %} +{% endif %} +{{ headers }} \ No newline at end of file diff --git a/templates/api-calls/smoke-test/url.j2 b/templates/api-calls/smoke-test/url.j2 new file mode 100644 index 0000000..a57e274 --- /dev/null +++ b/templates/api-calls/smoke-test/url.j2 @@ -0,0 +1,10 @@ +{%- if threescale_cicd_smoke_test_env == "staging" -%} +{{ threescale_cicd_apicast_discovered_sandbox_endpoint }} +{%- endif -%} +{%- if threescale_cicd_smoke_test_env == "production" -%} +{{ threescale_cicd_apicast_discovered_production_endpoint }} +{%- endif -%} +{{ threescale_cicd_openapi_smoketest_path }} +{%- if threescale_cicd_api_security_scheme.type == "apiKey" and threescale_cicd_api_credentials_location == "query" -%} +?{{ threescale_cicd_api_security_scheme.name|urlencode }}={{ threescale_cicd_default_application_details.user_key }} +{%- endif -%} \ No newline at end of file diff --git a/templates/api-calls/update_activedoc.j2 b/templates/api-calls/update_activedoc.j2 new file mode 120000 index 0000000..bd584b4 --- /dev/null +++ b/templates/api-calls/update_activedoc.j2 @@ -0,0 +1 @@ +create_activedoc.j2 \ No newline at end of file diff --git a/templates/api-calls/update_application.j2 b/templates/api-calls/update_application.j2 new file mode 120000 index 0000000..036dd22 --- /dev/null +++ b/templates/api-calls/update_application.j2 @@ -0,0 +1 @@ +create_application.j2 \ No newline at end of file diff --git a/templates/api-calls/update_application_plan.j2 b/templates/api-calls/update_application_plan.j2 new file mode 120000 index 0000000..3712a75 --- /dev/null +++ b/templates/api-calls/update_application_plan.j2 @@ -0,0 +1 @@ +create_application_plan.j2 \ No newline at end of file diff --git a/templates/api-calls/update_mapping_rule.j2 b/templates/api-calls/update_mapping_rule.j2 new file mode 120000 index 0000000..f03cd71 --- /dev/null +++ b/templates/api-calls/update_mapping_rule.j2 @@ -0,0 +1 @@ +create_mapping_rule.j2 \ No newline at end of file diff --git a/templates/api-calls/update_method.j2 b/templates/api-calls/update_method.j2 new file mode 120000 index 0000000..7969ddb --- /dev/null +++ b/templates/api-calls/update_method.j2 @@ -0,0 +1 @@ +create_method.j2 \ No newline at end of file diff --git a/templates/api-calls/update_proxy.j2 b/templates/api-calls/update_proxy.j2 new file mode 100644 index 0000000..99022af --- /dev/null +++ b/templates/api-calls/update_proxy.j2 @@ -0,0 +1,20 @@ +{% + set payload = [ + 'access_token=' ~ threescale_cicd_access_token|urlencode, + 'credentials_location=' ~ threescale_cicd_api_credentials_location|urlencode, + 'api_backend=' ~ threescale_cicd_private_base_url|urlencode + ] +%} +{% if threescale_cicd_api_security_scheme.type == 'apiKey' %} +{% do payload.append('auth_user_key=' ~ threescale_cicd_api_security_scheme.name|urlencode) %} +{% endif %} +{% if threescale_cicd_api_security_scheme.type == 'oauth2' %} +{% do payload.append('oidc_issuer_endpoint=' ~ threescale_cicd_sso_issuer_endpoint|urlencode) %} +{% endif %} +{% if threescale_cicd_apicast_sandbox_endpoint|default("")|length > 0 %} +{% do payload.append('sandbox_endpoint=' ~ threescale_cicd_apicast_sandbox_endpoint|urlencode) %} +{% endif %} +{% if threescale_cicd_apicast_production_endpoint|default("")|length > 0 %} +{% do payload.append('endpoint=' ~ threescale_cicd_apicast_production_endpoint|urlencode) %} +{% endif %} +{{ payload|join("&") }} \ No newline at end of file diff --git a/templates/api-calls/update_service.j2 b/templates/api-calls/update_service.j2 new file mode 120000 index 0000000..baa93b9 --- /dev/null +++ b/templates/api-calls/update_service.j2 @@ -0,0 +1 @@ +create_service.j2 \ No newline at end of file diff --git a/templates/existing_mapping_rules.j2 b/templates/existing_mapping_rules.j2 new file mode 100644 index 0000000..79ae498 --- /dev/null +++ b/templates/existing_mapping_rules.j2 @@ -0,0 +1,5 @@ +{% set mapping_rules = {} %} +{% for value in threescale_cicd_existing_mapping_rules_details %} +{% do mapping_rules.update({ (threescale_cicd_existing_metrics_details|selectattr("id", "equalto", value.metric_id)|first).system_name: value.id }) %} +{% endfor %} +{{ mapping_rules }} \ No newline at end of file diff --git a/templates/metrics_to_delete.j2 b/templates/metrics_to_delete.j2 new file mode 100644 index 0000000..d7c2047 --- /dev/null +++ b/templates/metrics_to_delete.j2 @@ -0,0 +1,7 @@ +{% set to_delete = [] %} +{% for metric in threescale_cicd_existing_metrics_details %} +{% if metric.system_name != "hits" and metric.system_name not in threescale_cicd_api_operations %} +{% do to_delete.append(metric) %} +{% endif %} +{% endfor %} +{{ to_delete }} \ No newline at end of file diff --git a/templates/openapi/apicast_production_endpoint.j2 b/templates/openapi/apicast_production_endpoint.j2 new file mode 100644 index 0000000..77b9d03 --- /dev/null +++ b/templates/openapi/apicast_production_endpoint.j2 @@ -0,0 +1,5 @@ +{%- if threescale_cicd_wildcard_domain is defined -%} +{{ threescale_cicd_default_apicast_scheme }}://{{ (threescale_cicd_api_base_system_name ~ "-" ~ threescale_cicd_api_version_major)|regex_replace('[^a-zA-Z0-9-]+', '-')|lower }}{{ threescale_cicd_default_production_suffix }}.{{ threescale_cicd_wildcard_domain }} +{%- elif 'apicast-production' in groups and groups['apicast-production'] > 0 -%} +{{ (hostvars[groups['apicast-production'][0]].scheme|default('https')) ~ '://' ~ groups['apicast-production'][0] }} +{%- endif -%} \ No newline at end of file diff --git a/templates/openapi/apicast_sandbox_endpoint.j2 b/templates/openapi/apicast_sandbox_endpoint.j2 new file mode 100644 index 0000000..02fe17b --- /dev/null +++ b/templates/openapi/apicast_sandbox_endpoint.j2 @@ -0,0 +1,5 @@ +{%- if threescale_cicd_wildcard_domain is defined -%} +{{ threescale_cicd_default_apicast_scheme }}://{{ (threescale_cicd_api_base_system_name ~ "-" ~ threescale_cicd_api_version_major)|regex_replace('[^a-zA-Z0-9-]+', '-')|lower }}{{ threescale_cicd_default_staging_suffix }}.{{ threescale_cicd_wildcard_domain }} +{%- elif 'apicast-sandbox' in groups and groups['apicast-sandbox'] > 0 -%} +{{ (hostvars[groups['apicast-sandbox'][0]].scheme|default('https')) ~ '://' ~ groups['apicast-sandbox'][0] }} +{%- endif -%} \ No newline at end of file diff --git a/templates/openapi/generate_base_system_name.j2 b/templates/openapi/generate_base_system_name.j2 new file mode 100644 index 0000000..63ffd79 --- /dev/null +++ b/templates/openapi/generate_base_system_name.j2 @@ -0,0 +1,6 @@ +{% if 'x-threescale-system-name' in threescale_cicd_openapi_file_content.info %} +{% set extracted_system_name = threescale_cicd_openapi_file_content.info['x-threescale-system-name']|regex_replace('[^a-zA-Z0-9_]+', '_')|lower %} +{% else %} +{% set extracted_system_name = threescale_cicd_openapi_file_content.info['title']|default('api')|regex_replace('[^a-zA-Z0-9_]+', '_')|lower %} +{% endif %} +{{ extracted_system_name }} \ No newline at end of file diff --git a/templates/openapi/generate_final_system_name.j2 b/templates/openapi/generate_final_system_name.j2 new file mode 100644 index 0000000..b001ec1 --- /dev/null +++ b/templates/openapi/generate_final_system_name.j2 @@ -0,0 +1,4 @@ +{%- if threescale_cicd_api_environment_name is defined -%} +{%- set system_name_prefix = threescale_cicd_api_environment_name|default("")|regex_replace('[^a-zA-Z0-9_]+', '_') ~ "_" -%} +{%- endif -%} +{{ system_name_prefix|default("") }}{{ threescale_cicd_api_base_system_name }}_{{ threescale_cicd_api_version_major|regex_replace('[^a-zA-Z0-9_]+', '_') }} \ No newline at end of file diff --git a/templates/openapi/openapi_operations.j2 b/templates/openapi/openapi_operations.j2 new file mode 100644 index 0000000..0beced1 --- /dev/null +++ b/templates/openapi/openapi_operations.j2 @@ -0,0 +1,26 @@ +{% set operations = {} -%} +{% if 'paths' in threescale_cicd_openapi_file_content -%} + {% for path, verbs in threescale_cicd_openapi_file_content['paths'].items() -%} + {% if path.startswith('/') -%} + {% for verb, method_description in verbs.items() -%} + {% if verb != '$ref' and verb != 'parameters' -%} + {% if 'operationId' in method_description -%} + {% set operation_id = method_description['operationId'] -%} + {% else -%} + {% set operation_id = verb.upper() + path -%} + {% endif -%} + {% set operation_id = operation_id|regex_replace('[^0-9a-zA-Z_]+', '_') -%} + {% set operation = { operation_id: { 'path': path, 'verb': verb } } -%} + {% if 'summary' in method_description -%} + {% do operation[operation_id].update({ 'friendly_name': method_description.summary }) -%} + {% endif -%} + {% if 'description' in method_description -%} + {% do operation[operation_id].update({ 'description': method_description.description }) -%} + {% endif -%} + {% do operations.update(operation) -%} + {% endif -%} + {% endfor -%} + {% endif -%} + {% endfor -%} +{% endif -%} +{{ operations }} \ No newline at end of file diff --git a/templates/openapi/private_base_url.j2 b/templates/openapi/private_base_url.j2 new file mode 100644 index 0000000..d6ca592 --- /dev/null +++ b/templates/openapi/private_base_url.j2 @@ -0,0 +1,11 @@ +{%- if threescale_cicd_api_backend_hostname is not defined and 'host' in threescale_cicd_openapi_file_content -%} +{%- set backend_hostname = threescale_cicd_openapi_file_content.host -%} +{%- else -%} +{%- set backend_hostname = threescale_cicd_api_backend_hostname -%} +{%- endif -%} +{%- if threescale_cicd_api_backend_scheme is not defined -%} +{%- set backend_scheme = threescale_cicd_openapi_file_content.schemes|default(["http"])|first -%} +{%- else -%} +{%- set backend_scheme = threescale_cicd_api_backend_scheme -%} +{%- endif -%} +{{ backend_scheme }}://{{ backend_hostname }} \ No newline at end of file diff --git a/templates/openapi/service_name.j2 b/templates/openapi/service_name.j2 new file mode 100644 index 0000000..8ffc392 --- /dev/null +++ b/templates/openapi/service_name.j2 @@ -0,0 +1,5 @@ +{%- if threescale_cicd_api_environment_name is defined -%} +{{ threescale_cicd_api_default_name }} ({{ threescale_cicd_api_environment_name|upper }}, v{{ threescale_cicd_api_version }}) +{%- else -%} +{{ threescale_cicd_api_default_name }} (v{{ threescale_cicd_api_version }}) +{%- endif -%} \ No newline at end of file diff --git a/templates/openapi/sso_issuer_endpoint.j2 b/templates/openapi/sso_issuer_endpoint.j2 new file mode 100644 index 0000000..34f3f24 --- /dev/null +++ b/templates/openapi/sso_issuer_endpoint.j2 @@ -0,0 +1,3 @@ +{%- if 'sso' in groups and groups['sso'] > 0 -%} +{{ (hostvars[groups['sso'][0]].scheme|default('https')) ~ '://' ~ hostvars[groups['sso'][0]].client_id ~ ':' ~ hostvars[groups['sso'][0]].client_secret ~ '@' ~ groups['sso'][0] ~ '/auth/realms/' ~ hostvars[groups['sso'][0]].realm }} +{%- endif -%} \ No newline at end of file diff --git a/templates/rewritten_openapi.j2 b/templates/rewritten_openapi.j2 new file mode 100644 index 0000000..a19e4dd --- /dev/null +++ b/templates/rewritten_openapi.j2 @@ -0,0 +1,22 @@ +{% set security_definitions = threescale_cicd_api_security_definitions %} +{% set new_openapi = threescale_cicd_openapi_file_content %} +{# Add the RH-SSO endpoints to the OpenAPI securityDefinitions #} +{% if threescale_cicd_api_security_scheme.type == "oauth2" %} +{% do security_definitions[threescale_cicd_api_security_scheme_name].update({ "authorizationUrl": threescale_cicd_sso_realm_endpoint ~ "/protocol/openid-connect/auth", "tokenUrl": threescale_cicd_sso_realm_endpoint ~ "/protocol/openid-connect/token" }) %} +{% endif %} +{# Add the RH-SSO default scope to the OpenAPI securityDefinitions #} +{% if threescale_cicd_api_security_scheme.type == "oauth2" and "scopes" not in threescale_cicd_api_security_scheme %} +{% do security_definitions[threescale_cicd_api_security_scheme_name].update({ "scopes": threescale_cicd_default_oauth_scopes }) %} +{% endif %} +{# Update the security definitions #} +{% do new_openapi.update({ "securityDefinitions": security_definitions }) %} +{# Update the "schemes" and "hostname" fields with the public apicast production URL #} +{% set apicast_production_scheme = threescale_cicd_apicast_discovered_production_endpoint|urlsplit('scheme') %} +{% set apicast_production_hostname = threescale_cicd_apicast_discovered_production_endpoint|urlsplit('hostname') %} +{% do new_openapi.update({ + "schemes": [ apicast_production_scheme ], + "host": apicast_production_hostname +}) %} +{# Make sure the swagger version is a string and not a number #} +{% do new_openapi.update({ "swagger": new_openapi.swagger ~ "" }) %} +{{ new_openapi }} \ No newline at end of file diff --git a/templates/wanted_mapping_rules.j2 b/templates/wanted_mapping_rules.j2 new file mode 100644 index 0000000..45e9ac8 --- /dev/null +++ b/templates/wanted_mapping_rules.j2 @@ -0,0 +1,5 @@ +{% set mapping_rules = {} %} +{% for key, value in threescale_cicd_api_operations.items() %} +{% do mapping_rules.update({ key: { "http_method": value.verb.upper(), "pattern": threescale_cicd_api_basepath ~ value.path ~ "$", "delta": 1 } }) %} +{% endfor %} +{{ mapping_rules }} \ No newline at end of file diff --git a/tests/3scale-saas-with-hosted-apicast-apikey.yml b/tests/3scale-saas-with-hosted-apicast-apikey.yml index 9a8cff1..594b9fc 100644 --- a/tests/3scale-saas-with-hosted-apicast-apikey.yml +++ b/tests/3scale-saas-with-hosted-apicast-apikey.yml @@ -8,10 +8,14 @@ threescale_cicd_openapi_file_format: 'JSON' threescale_cicd_api_backend_hostname: echo-api.3scale.net threescale_cicd_openapi_smoketest_operation: GET_beer - roles: - # Test first deployment - - { role: 'nmasse-itix.threescale-cicd', vars: { 'round': 1 } } + tasks: + # Test a first deployment + - import_role: + name: 'nmasse-itix.threescale-cicd' # Verify idempotence - - { role: 'nmasse-itix.threescale-cicd', vars: { 'round': 2 } } - post_tasks: - - import_tasks: 'cleanup.yaml' + - import_role: + name: 'nmasse-itix.threescale-cicd' + # Delete the service + - import_role: + name: 'nmasse-itix.threescale-cicd' + tasks_from: 'cleanup' diff --git a/tests/3scale-saas-with-hosted-apicast-multi-environment.yml b/tests/3scale-saas-with-hosted-apicast-multi-environment.yml new file mode 100644 index 0000000..da34296 --- /dev/null +++ b/tests/3scale-saas-with-hosted-apicast-multi-environment.yml @@ -0,0 +1,45 @@ +--- + +- name: Deploy the Beer Catalog API to a 3scale SaaS instance in multi environment + hosts: threescale + gather_facts: no + vars: + threescale_cicd_openapi_file: '{{ playbook_dir }}/api/beer-catalog-api.json' + threescale_cicd_openapi_file_format: 'JSON' + threescale_cicd_api_backend_hostname: echo-api.3scale.net + threescale_cicd_openapi_smoketest_operation: GET_beer + threescale_cicd_api_base_system_name: beer_catalog + tasks: + # Deploy in DEV + - import_role: + name: 'nmasse-itix.threescale-cicd' + vars: + threescale_cicd_api_environment_name: dev + # Deploy in TEST + - import_role: + name: 'nmasse-itix.threescale-cicd' + vars: + threescale_cicd_api_environment_name: test + # Deploy in PROD + - import_role: + name: 'nmasse-itix.threescale-cicd' + vars: + threescale_cicd_api_environment_name: prod + # Cleanup the DEV + - import_role: + name: 'nmasse-itix.threescale-cicd' + tasks_from: 'cleanup' + vars: + threescale_cicd_api_environment_name: dev + # Cleanup the TEST + - import_role: + name: 'nmasse-itix.threescale-cicd' + tasks_from: 'cleanup' + vars: + threescale_cicd_api_environment_name: test + # Cleanup the PROD + - import_role: + name: 'nmasse-itix.threescale-cicd' + tasks_from: 'cleanup' + vars: + threescale_cicd_api_environment_name: prod diff --git a/tests/3scale-saas-with-hosted-apicast-oidc.yml b/tests/3scale-saas-with-hosted-apicast-oidc.yml index 3922ab9..ba2633e 100644 --- a/tests/3scale-saas-with-hosted-apicast-oidc.yml +++ b/tests/3scale-saas-with-hosted-apicast-oidc.yml @@ -5,10 +5,14 @@ gather_facts: no vars: threescale_cicd_openapi_file: '{{ playbook_dir }}/api/echo-api-oidc.yaml' - roles: - # Test first deployment - - { role: 'nmasse-itix.threescale-cicd', vars: { 'round': 1 } } + tasks: + # Test a first deployment + - import_role: + name: 'nmasse-itix.threescale-cicd' # Verify idempotence - - { role: 'nmasse-itix.threescale-cicd', vars: { 'round': 2 } } - post_tasks: - - import_tasks: 'cleanup.yaml' + - import_role: + name: 'nmasse-itix.threescale-cicd' + # Delete the service + - import_role: + name: 'nmasse-itix.threescale-cicd' + tasks_from: 'cleanup' diff --git a/tests/3scale-saas-with-hosted-apicast-with-basePath.yml b/tests/3scale-saas-with-hosted-apicast-with-basePath.yml index 90be8ec..dfb7ff4 100644 --- a/tests/3scale-saas-with-hosted-apicast-with-basePath.yml +++ b/tests/3scale-saas-with-hosted-apicast-with-basePath.yml @@ -5,10 +5,14 @@ gather_facts: no vars: threescale_cicd_openapi_file: '{{ playbook_dir }}/api/echo-api-with-basePath.yaml' - roles: - # Test first deployment - - { role: 'nmasse-itix.threescale-cicd', vars: { 'round': 1 } } + tasks: + # Test a first deployment + - import_role: + name: 'nmasse-itix.threescale-cicd' # Verify idempotence - - { role: 'nmasse-itix.threescale-cicd', vars: { 'round': 2 } } - post_tasks: - - import_tasks: 'cleanup.yaml' + - import_role: + name: 'nmasse-itix.threescale-cicd' + # Delete the service + - import_role: + name: 'nmasse-itix.threescale-cicd' + tasks_from: 'cleanup' diff --git a/tests/inventory.j2 b/tests/inventory.j2 index 57f98a2..e291b14 100644 --- a/tests/inventory.j2 +++ b/tests/inventory.j2 @@ -6,12 +6,4 @@ ansible_connection=local [threescale:vars] threescale_cicd_access_token={{ threescale_inventory.threescale_hosted.access_token }} - -[sso] -{{ threescale_inventory.sso.host }} - -[sso:vars] -realm={{ threescale_inventory.sso.realm }} -client_id={{ threescale_inventory.sso.client_id }} -client_secret={{ threescale_inventory.sso.client_secret }} -scheme=https +threescale_cicd_sso_issuer_endpoint=https://{{ threescale_inventory.sso.client_id }}:{{ threescale_inventory.sso.client_secret }}@{{ threescale_inventory.sso.host }}/auth/realms/{{ threescale_inventory.sso.realm }} diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..b116f88 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,92 @@ +--- +# Credentials are expected to be passed in HTTP headers unless stated otherwise +# and only for API Keys +threescale_cicd_api_credentials_location: '{{ ''headers'' if threescale_cicd_api_security_scheme.in|default(''header'') == ''header'' or threescale_cicd_api_security_scheme.type == ''oauth2'' else ''query'' }}' + +# A list of unused metrics to delete +threescale_cicd_metrics_to_delete: '{{ lookup(''template'', ''metrics_to_delete.j2'') }}' + +# The OpenAPI file to be pushed to 3scale as an ActiveDocs +threescale_cicd_openapi_rewritten: '{{ lookup(''template'', ''rewritten_openapi.j2'') }}' + +# Compute the Keycloak Realm endpoint from the threescale_cicd_sso_issuer_endpoint +threescale_cicd_sso_realm_endpoint: '{{ (threescale_cicd_sso_issuer_endpoint|urlsplit(''scheme'')) ~ ''://'' ~ (threescale_cicd_sso_issuer_endpoint|urlsplit(''hostname'')) ~ (threescale_cicd_sso_issuer_endpoint|urlsplit(''path'')) }}' + +# Compute the Keycloak REST Admin Endpoint from the threescale_cicd_sso_realm_endpoint +threescale_cicd_sso_admin_endpoint: '{{ threescale_cicd_sso_realm_endpoint|replace(''/auth/realms/'', ''/auth/admin/realms/'') }}' + +## +## OpenAPI Specification File parsing +## +threescale_cicd_openapi_file_content: '{{ lookup(''file'', threescale_cicd_openapi_file)|from_json if threescale_cicd_openapi_file_format|upper == ''JSON'' else lookup(''file'', threescale_cicd_openapi_file)|from_yaml }}' +threescale_cicd_openapi_file_version: '{{ threescale_cicd_openapi_file_content.swagger }}' +threescale_cicd_api_default_name: '{{ threescale_cicd_openapi_file_content.info.title|default("API") }}' +threescale_cicd_api_name: '{{ lookup(''template'', ''openapi/service_name.j2'') }}' +threescale_cicd_api_description: '{{ threescale_cicd_openapi_file_content.info.description|default("") }}' +threescale_cicd_api_version: '{{ threescale_cicd_openapi_file_content.info.version|default("0.0.1") }}' +threescale_cicd_api_basepath: '{{ threescale_cicd_openapi_file_content.basePath|default("") }}' +threescale_cicd_api_operations: '{{ lookup(''template'', ''openapi/openapi_operations.j2'') }}' +threescale_cicd_api_version_components: '{{ threescale_cicd_api_version.split(".") }}' +threescale_cicd_api_version_major: '{{ threescale_cicd_api_version_components|first }}' +threescale_cicd_api_security_requirements: '{{ threescale_cicd_openapi_file_content.security|default([]) }}' +threescale_cicd_api_security_definitions: '{{ threescale_cicd_openapi_file_content.securityDefinitions|default({}) }}' +threescale_cicd_api_security_scheme_name: '{{ threescale_cicd_api_security_requirements[0].keys()[0]|default(''none'') }}' +threescale_cicd_api_security_scheme: '{{ threescale_cicd_api_security_definitions[threescale_cicd_api_security_scheme_name] if threescale_cicd_api_security_scheme_name in threescale_cicd_api_security_definitions else {} }}' +threescale_cicd_api_backend_version: '{{ threescale_cicd_backend_version_mapping[threescale_cicd_api_security_scheme.type] }}' +threescale_cicd_backend_version_mapping: + apiKey: '1' + oauth2: 'oidc' +threescale_cicd_openapi_smoketest_path: '{{ threescale_cicd_api_basepath }}{{ threescale_cicd_api_operations[threescale_cicd_openapi_smoketest_operation].path }}' + +## +## ID Lookup Variables +## + +# The id of the current service is fetched from the threescale_cicd_existing_services_details fact +threescale_cicd_api_service_id: '{{ (threescale_cicd_existing_services_details|selectattr(''system_name'', ''equalto'', threescale_cicd_api_system_name)|first)[''id''] }}' + +# The id of the 'hits' metric is fetched from the threescale_cicd_existing_metrics_details fact +threescale_cicd_metric_id: '{{ (threescale_cicd_existing_metrics_details|selectattr(''system_name'', ''equalto'', ''hits'')|first).id }}' + +# Find the default application plan id from its system name +threescale_cicd_default_application_plan_id: '{{ (threescale_cicd_existing_application_plans_details|selectattr("system_name", "equalto", threescale_cicd_default_application_plan)|first).id }}' + +# Find the id of the existing activedocs from the threescale_cicd_existing_activedocs_details fact +threescale_cicd_api_activedocs_id: '{{ (threescale_cicd_existing_activedocs_details|selectattr(''system_name'', ''equalto'', threescale_cicd_api_system_name)|first).id }}' + +## +## Mapping Rules computation +## +# what we want +threescale_cicd_wanted_mapping_rules: '{{ lookup(''template'', ''wanted_mapping_rules.j2'') }}' +# what we have +threescale_cicd_existing_mapping_rules: '{{ lookup(''template'', ''existing_mapping_rules.j2'') }}' +# create the items that we want but don't have yet +threescale_cicd_mapping_rules_to_create: '{{ threescale_cicd_wanted_mapping_rules.keys()|difference(threescale_cicd_existing_mapping_rules.keys()) }}' +# delete the items that we don't want but we have +threescale_cicd_mapping_rules_to_delete: '{{ threescale_cicd_existing_mapping_rules.keys()|difference(threescale_cicd_wanted_mapping_rules.keys()) }}' +# update the items that we want and we have +threescale_cicd_mapping_rules_to_update: '{{ threescale_cicd_existing_mapping_rules.keys()|intersect(threescale_cicd_wanted_mapping_rules.keys()) }}' + +## +## 3scale API Payload definition +## +threescale_cicd_update_proxy_payload: '{{ lookup(''template'', ''api-calls/update_proxy.j2'') }}' +threescale_cicd_update_service_payload: '{{ lookup(''template'', ''api-calls/update_service.j2'') }}' +threescale_cicd_create_service_payload: '{{ lookup(''template'', ''api-calls/create_service.j2'') }}' +threescale_cicd_update_method_payload: '{{ lookup(''template'', ''api-calls/update_method.j2'') }}' +threescale_cicd_create_method_payload: '{{ lookup(''template'', ''api-calls/create_method.j2'') }}' +threescale_cicd_update_mapping_rule_payload: '{{ lookup(''template'', ''api-calls/update_mapping_rule.j2'') }}' +threescale_cicd_create_mapping_rule_payload: '{{ lookup(''template'', ''api-calls/create_mapping_rule.j2'') }}' +threescale_cicd_update_application_plan_payload: '{{ lookup(''template'', ''api-calls/update_application_plan.j2'') }}' +threescale_cicd_create_application_plan_payload: '{{ lookup(''template'', ''api-calls/create_application_plan.j2'') }}' +threescale_cicd_find_application_payload: '{{ lookup(''template'', ''api-calls/find_application.j2'') }}' +threescale_cicd_update_application_payload: '{{ lookup(''template'', ''api-calls/update_application.j2'') }}' +threescale_cicd_create_application_payload: '{{ lookup(''template'', ''api-calls/create_application.j2'') }}' +threescale_cicd_authenticate_to_keycloak_payload: '{{ lookup(''template'', ''api-calls/keycloak/authenticate.j2'') }}' +threescale_cicd_patch_keycloak_client_payload: '{{ lookup(''template'', ''api-calls/keycloak/patch_client.j2'') }}' +threescale_cicd_smoke_test_headers: '{{ lookup(''template'', ''api-calls/smoke-test/headers.j2'') }}' +threescale_cicd_smoke_test_url: '{{ lookup(''template'', ''api-calls/smoke-test/url.j2'') }}' +threescale_cicd_promote_proxy_payload: '{{ lookup(''template'', ''api-calls/promote_proxy.j2'') }}' +threescale_cicd_update_activedoc_payload: '{{ lookup(''template'', ''api-calls/update_activedoc.j2'') }}' +threescale_cicd_create_activedoc_payload: '{{ lookup(''template'', ''api-calls/create_activedoc.j2'') }}'