Browse Source

initial commit

pull/25/head
Nicolas Massé 8 years ago
commit
ebc499acbf
  1. 1
      .gitignore
  2. 21
      LICENSE
  3. 8
      defaults/main.yml
  4. 14
      meta/main.yml
  5. 34
      tasks/create_application_plans.yml
  6. 80
      tasks/create_default_application.yml
  7. 24
      tasks/create_mapping_rule.yml
  8. 51
      tasks/create_service.yml
  9. 21
      tasks/delete_unused_metrics.yml
  10. 126
      tasks/main.yml
  11. 32
      tasks/promote.yml
  12. 143
      tasks/read_openapi_file.yml
  13. 11
      tasks/retrieve_existing_services.yml
  14. 57
      tasks/smoke_tests.yml
  15. 9
      tasks/smoke_tests_apikey.yml
  16. 4
      tasks/smoke_tests_oauth.yml
  17. 26
      tasks/update_mapping_rule.yml
  18. 55
      tasks/update_mapping_rules.yml
  19. 43
      tasks/update_method.yml
  20. 20
      tasks/update_metrics.yml
  21. 19
      tasks/update_proxy.yml

1
.gitignore

@ -0,0 +1 @@
*.retry

21
LICENSE

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Nicolas MASSE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

8
defaults/main.yml

@ -0,0 +1,8 @@
---
threescale_cicd_openapi_file_format: YAML
threescale_cicd_delay: 10
threescale_cicd_retries: 50
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

14
meta/main.yml

@ -0,0 +1,14 @@
galaxy_info:
author: Nicolas Massé
description: Enables CI/CD with 3scale API Management Platform
company: Red Hat
license: MIT
min_ansible_version: 2.4
galaxy_tags:
- 3scale
- threescale
- 3scale-amp
- api-management
- continuous-deployment
dependencies: []

34
tasks/create_application_plans.yml

@ -0,0 +1,34 @@
---
- 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'

80
tasks/create_default_application.yml

@ -0,0 +1,80 @@
---
- 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'
when: 'threescale_cicd_api_security_scheme.type == ''oauth2'''
- set_fact:
threescale_cicd_tmp_search_criteria: '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_search_criteria ~ "=" ~ (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'

24
tasks/create_mapping_rule.yml

@ -0,0 +1,24 @@
---
- 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'

51
tasks/create_service.yml

@ -0,0 +1,51 @@
---
- 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
- 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,422
register: threescale_cicd_tmpresponse
changed_when: 'threescale_cicd_tmpresponse.status == 201'
- set_fact:
threescale_cicd_api_service_id: '{{ threescale_cicd_tmpresponse.json.service.id }}'
when: 'threescale_cicd_tmpresponse.status == 201'
- 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
when: 'threescale_cicd_tmpresponse.status == 422'
- 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}'') }}'
when: 'threescale_cicd_tmpresponse.status == 422'
- 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_tmpresponse.status == 422'
- 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_tmpresponse.status == 422'

21
tasks/delete_unused_metrics.yml

@ -0,0 +1,21 @@
---
- 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

126
tasks/main.yml

@ -0,0 +1,126 @@
---
- 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_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'
# Load the API definition from the provided OpenAPI file
- import_tasks: read_openapi_file.yml
# Retrieve existing services from the 3scale Admin Portal
- import_tasks: retrieve_existing_services.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'
- 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 }}'
# Create the service definition
- import_tasks: create_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'''
- 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_api_backend_scheme ~ ''://'' ~ threescale_cicd_api_backend_hostname }}'
- 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({ ''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 proxy
- import_tasks: update_proxy.yml
# Update the metrics
- import_tasks: update_metrics.yml
# Update the mapping rules
- import_tasks: update_mapping_rules.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
- 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
# Run smoke tests on the staging gateway
- include_tasks: smoke_tests.yml
vars:
threescale_cicd_env: staging
when: 'threescale_cicd_openapi_smoketest_path is defined and threescale_cicd_application_plans is defined'
# Promote to production
- import_tasks: promote.yml
# Run smoke tests on the production gateway
- include_tasks: smoke_tests.yml
vars:
threescale_cicd_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

32
tasks/promote.yml

@ -0,0 +1,32 @@
---
- 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
register: threescale_cicd_tmpresponse
- set_fact:
threescale_cicd_tmp_production_proxy_version: '{{ threescale_cicd_tmpresponse.json.proxy_config.version }}'
- 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'

143
tasks/read_openapi_file.yml

@ -0,0 +1,143 @@
---
- 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_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 the wanted system_name from OpenAPI
set_fact:
threescale_cicd_api_system_name: '{{ threescale_cicd_openapi_file_content.info[''x-threescale-system-name''] }}'
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_]+'', ''_'') }}'
when: 'threescale_cicd_api_system_name is not 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: '{{ threescale_cicd_api_security_requirements.keys()[0] }}'
- name: Make sure the requested security definition exists
assert:
that:
- 'threescale_cicd_api_security_scheme 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: 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: 'oauth'
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'
- assert:
that:
- 'threescale_cicd_api_backend_scheme is defined'
- 'threescale_cicd_api_backend_hostname is defined'
msg: 'The backend hostname and scheme must either be in the swagger or declared as extra variables (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_operations[threescale_cicd_openapi_smoketest_operation].path }}'
when: 'threescale_cicd_openapi_smoketest_operation is defined and threescale_cicd_openapi_smoketest_operation|length > 0'

11
tasks/retrieve_existing_services.yml

@ -0,0 +1,11 @@
---
- 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}'') }}'

57
tasks/smoke_tests.yml

@ -0,0 +1,57 @@
---
- 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.sandbox_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 }}'

9
tasks/smoke_tests_apikey.yml

@ -0,0 +1,9 @@
---
- 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"'

4
tasks/smoke_tests_oauth.yml

@ -0,0 +1,4 @@
---
- name: TODO
fail:

26
tasks/update_mapping_rule.yml

@ -0,0 +1,26 @@
---
- 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'

55
tasks/update_mapping_rules.yml

@ -0,0 +1,55 @@
---
- 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_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

43
tasks/update_method.yml

@ -0,0 +1,43 @@
---
- 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'

20
tasks/update_metrics.yml

@ -0,0 +1,20 @@
---
- 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

19
tasks/update_proxy.yml

@ -0,0 +1,19 @@
---
- 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'
Loading…
Cancel
Save