Implement acceptance test job
Implement acceptance tests. Those jobs will run in the post-review pipeline requiring access to secrets containing credentials of friendly public clouds to test sdk with them. Base job is generating a token from the given credentials and writes clouds.yaml file with the token inside instead of password. As a post step the token is physically revoked. This is done to prevent potential leakage of real credentials from the test jobs/logs. Since devstack is not a real cloud we do not use zuul secrets. Change-Id: I95af9b81e6abd51af2a7dd91cae14b56926a869c
This commit is contained in:
parent
74f8869fd9
commit
43ab59d8b3
38
.zuul.yaml
38
.zuul.yaml
@ -436,6 +436,41 @@
|
||||
required-projects:
|
||||
- openstack/openstacksdk
|
||||
|
||||
- job:
|
||||
name: openstacksdk-acceptance-base
|
||||
parent: openstack-tox
|
||||
description: Acceptance test of the OpenStackSDK on real clouds
|
||||
pre-run:
|
||||
- playbooks/acceptance/pre.yaml
|
||||
post-run:
|
||||
- playbooks/acceptance/post.yaml
|
||||
|
||||
- job:
|
||||
name: openstacksdk-acceptance-devstack
|
||||
parent: openstacksdk-functional-devstack
|
||||
description: Acceptance test of the OpenStackSDK on real clouds
|
||||
run:
|
||||
- playbooks/acceptance/run-with-devstack.yaml
|
||||
post-run:
|
||||
- playbooks/acceptance/post.yaml
|
||||
vars:
|
||||
tox_envlist: acceptance-regular-user
|
||||
tox_environment:
|
||||
OPENSTACKSDK_DEMO_CLOUD: acceptance
|
||||
OS_CLOUD: acceptance
|
||||
OS_TEST_CLOUD: acceptance
|
||||
openstack_credentials:
|
||||
auth:
|
||||
auth_url: "https://{{ hostvars['controller']['nodepool']['private_ipv4'] }}/identity"
|
||||
username: demo
|
||||
password: secretadmin
|
||||
project_domain_id: default
|
||||
project_name: demo
|
||||
user_domain_id: default
|
||||
identity_api_version: '3'
|
||||
region_name: RegionOne
|
||||
volume_api_version: '3'
|
||||
|
||||
- project-template:
|
||||
name: openstacksdk-functional-tips
|
||||
check:
|
||||
@ -486,6 +521,9 @@
|
||||
voting: false
|
||||
- ansible-collections-openstack-functional-devstack:
|
||||
voting: false
|
||||
post-review:
|
||||
jobs:
|
||||
- openstacksdk-acceptance-devstack
|
||||
gate:
|
||||
jobs:
|
||||
- opendev-buildset-registry
|
||||
|
1
playbooks/acceptance/library
Symbolic link
1
playbooks/acceptance/library
Symbolic link
@ -0,0 +1 @@
|
||||
../library
|
18
playbooks/acceptance/post.yaml
Normal file
18
playbooks/acceptance/post.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
# TODO:
|
||||
# - clean the resources, which might have been created
|
||||
# - revoke the temp token explicitly
|
||||
- name: read token
|
||||
command: "cat {{ zuul.executor.work_root }}/.{{ zuul.build }}"
|
||||
register: token_data
|
||||
no_log: true
|
||||
|
||||
- name: delete data file
|
||||
command: "shred {{ zuul.executor.work_root }}/.{{ zuul.build }}"
|
||||
|
||||
- include_role:
|
||||
name: revoke_token
|
||||
vars:
|
||||
cloud: "{{ openstack_credentials }}"
|
||||
token: "{{ token_data.stdout }}"
|
60
playbooks/acceptance/pre.yaml
Normal file
60
playbooks/acceptance/pre.yaml
Normal file
@ -0,0 +1,60 @@
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Get temporary token for the cloud
|
||||
# nolog is important to keep job-output.json clean
|
||||
no_log: true
|
||||
os_auth:
|
||||
cloud:
|
||||
profile: "{{ openstack_credentials.profile | default(omit) }}"
|
||||
auth:
|
||||
auth_url: "{{ openstack_credentials.auth.auth_url }}"
|
||||
username: "{{ openstack_credentials.auth.username }}"
|
||||
password: "{{ openstack_credentials.auth.password }}"
|
||||
user_domain_name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}"
|
||||
user_domain_id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}"
|
||||
domain_name: "{{ openstack_credentials.auth.domain_name | default(omit) }}"
|
||||
domain_id: "{{ openstack_credentials.auth.domain_id | default(omit) }}"
|
||||
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
|
||||
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
|
||||
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
|
||||
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
|
||||
register: os_auth
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Verify token
|
||||
no_log: true
|
||||
os_auth:
|
||||
cloud:
|
||||
profile: "{{ openstack_credentials.profile | default(omit) }}"
|
||||
auth_type: token
|
||||
auth:
|
||||
auth_url: "{{ openstack_credentials.auth.auth_url }}"
|
||||
token: "{{ os_auth.auth_token }}"
|
||||
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
|
||||
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
|
||||
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
|
||||
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Include deploy-clouds-config role
|
||||
include_role:
|
||||
name: deploy-clouds-config
|
||||
vars:
|
||||
cloud_config:
|
||||
clouds:
|
||||
acceptance:
|
||||
profile: "{{ openstack_credentials.profile | default(omit) }}"
|
||||
auth_type: "token"
|
||||
auth:
|
||||
auth_url: "{{ openstack_credentials.auth.auth_url | default(omit) }}"
|
||||
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
|
||||
token: "{{ os_auth.auth_token }}"
|
||||
|
||||
# Intruders might want to corrupt clouds.yaml to avoid revoking token in the post phase
|
||||
# To prevent this we save token on the executor for later use.
|
||||
- name: Save token
|
||||
delegate_to: localhost
|
||||
copy:
|
||||
dest: "{{ zuul.executor.work_root }}/.{{ zuul.build }}"
|
||||
content: "{{ os_auth.auth_token }}"
|
||||
mode: "0440"
|
83
playbooks/acceptance/run-with-devstack.yaml
Normal file
83
playbooks/acceptance/run-with-devstack.yaml
Normal file
@ -0,0 +1,83 @@
|
||||
# Need to actually start devstack first
|
||||
- hosts: all
|
||||
roles:
|
||||
- run-devstack
|
||||
|
||||
# Prepare local clouds.yaml
|
||||
# We can't rely on pre.yaml, since it is specifically delegates to
|
||||
# localhost, while on devstack it will not work unless APIs are available
|
||||
# over the net.
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Get temporary token for the cloud
|
||||
# nolog is important to keep job-output.json clean
|
||||
no_log: true
|
||||
os_auth:
|
||||
cloud:
|
||||
profile: "{{ openstack_credentials.profile | default(omit) }}"
|
||||
auth:
|
||||
auth_url: "{{ openstack_credentials.auth.auth_url }}"
|
||||
username: "{{ openstack_credentials.auth.username }}"
|
||||
password: "{{ openstack_credentials.auth.password }}"
|
||||
user_domain_name: "{{ openstack_credentials.auth.user_domain_name | default(omit) }}"
|
||||
user_domain_id: "{{ openstack_credentials.auth.user_domain_id | default(omit) }}"
|
||||
domain_name: "{{ openstack_credentials.auth.domain_name | default(omit) }}"
|
||||
domain_id: "{{ openstack_credentials.auth.domain_id | default(omit) }}"
|
||||
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
|
||||
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
|
||||
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
|
||||
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
|
||||
register: os_auth
|
||||
|
||||
- name: Verify token
|
||||
# nolog is important to keep job-output.json clean
|
||||
no_log: true
|
||||
os_auth:
|
||||
cloud:
|
||||
profile: "{{ openstack_credentials.profile | default(omit) }}"
|
||||
auth_type: token
|
||||
auth:
|
||||
auth_url: "{{ openstack_credentials.auth.auth_url }}"
|
||||
token: "{{ os_auth.auth_token }}"
|
||||
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
|
||||
project_id: "{{ openstack_credentials.auth.project_id | default(omit) }}"
|
||||
project_domain_name: "{{ openstack_credentials.auth.project_domain_name | default(omit) }}"
|
||||
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
|
||||
|
||||
- name: Include deploy-clouds-config role
|
||||
include_role:
|
||||
name: deploy-clouds-config
|
||||
vars:
|
||||
cloud_config:
|
||||
clouds:
|
||||
acceptance:
|
||||
profile: "{{ openstack_credentials.profile | default(omit) }}"
|
||||
auth_type: "token"
|
||||
auth:
|
||||
|
||||
auth_url: "{{ openstack_credentials.auth.auth_url }}"
|
||||
project_name: "{{ openstack_credentials.auth.project_name | default(omit) }}"
|
||||
project_domain_id: "{{ openstack_credentials.auth.project_domain_id | default(omit) }}"
|
||||
token: "{{ os_auth.auth_token }}"
|
||||
verify: false
|
||||
|
||||
# Intruders might want to corrupt clouds.yaml to avoid revoking token in
|
||||
# the post phase. To prevent this we save token on the executor for later
|
||||
# use.
|
||||
- name: Save token
|
||||
delegate_to: localhost
|
||||
copy:
|
||||
dest: "{{ zuul.executor.work_root }}/.{{ zuul.build }}"
|
||||
content: "{{ os_auth.auth_token }}"
|
||||
mode: "0640"
|
||||
|
||||
# Run the rest
|
||||
- hosts: all
|
||||
roles:
|
||||
- role: bindep
|
||||
bindep_profile: test
|
||||
bindep_dir: "{{ zuul_work_dir }}"
|
||||
- test-setup
|
||||
- ensure-tox
|
||||
- get-devstack-os-environment
|
||||
- tox
|
45
playbooks/library/os_auth.py
Normal file
45
playbooks/library/os_auth.py
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility to get Keystone token
|
||||
"""
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
import openstack
|
||||
|
||||
|
||||
def get_cloud(cloud):
|
||||
if isinstance(cloud, dict):
|
||||
config = openstack.config.loader.OpenStackConfig().get_one(**cloud)
|
||||
return openstack.connection.Connection(config=config)
|
||||
else:
|
||||
return openstack.connect(cloud=cloud)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
cloud=dict(required=True, type='raw', no_log=True),
|
||||
)
|
||||
)
|
||||
cloud = get_cloud(module.params.get('cloud'))
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
auth_token=cloud.auth_token
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
roles/deploy-clouds-config/README.rst
Normal file
0
roles/deploy-clouds-config/README.rst
Normal file
1
roles/deploy-clouds-config/defaults/main.yaml
Normal file
1
roles/deploy-clouds-config/defaults/main.yaml
Normal file
@ -0,0 +1 @@
|
||||
zuul_work_dir: "{{ zuul.project.src_dir }}"
|
11
roles/deploy-clouds-config/tasks/main.yaml
Normal file
11
roles/deploy-clouds-config/tasks/main.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
- name: Create OpenStack config dir
|
||||
ansible.builtin.file:
|
||||
dest: ~/.config/openstack
|
||||
state: directory
|
||||
recurse: true
|
||||
|
||||
- name: Deploy clouds.yaml
|
||||
ansible.builtin.template:
|
||||
src: clouds.yaml.j2
|
||||
dest: ~/.config/openstack/clouds.yaml
|
||||
mode: 0440
|
2
roles/deploy-clouds-config/templates/clouds.yaml.j2
Normal file
2
roles/deploy-clouds-config/templates/clouds.yaml.j2
Normal file
@ -0,0 +1,2 @@
|
||||
---
|
||||
{{ cloud_config | to_nice_yaml }}
|
0
roles/revoke_token/README.rst
Normal file
0
roles/revoke_token/README.rst
Normal file
72
roles/revoke_token/library/os_auth_revoke.py
Normal file
72
roles/revoke_token/library/os_auth_revoke.py
Normal file
@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2014 Rackspace Australia
|
||||
# Copyright 2018 Red Hat, Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility to revoke Keystone token
|
||||
"""
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import keystoneauth1.exceptions
|
||||
import requests
|
||||
import requests.exceptions
|
||||
|
||||
import openstack
|
||||
|
||||
|
||||
def get_cloud(cloud):
|
||||
if isinstance(cloud, dict):
|
||||
config = openstack.config.loader.OpenStackConfig().get_one(**cloud)
|
||||
return openstack.connection.Connection(config=config)
|
||||
else:
|
||||
return openstack.connect(cloud=cloud)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
cloud=dict(required=True, type='raw', no_log=True),
|
||||
revoke_token=dict(required=True, type='str', no_log=True)
|
||||
)
|
||||
)
|
||||
|
||||
p = module.params
|
||||
cloud = get_cloud(p.get('cloud'))
|
||||
try:
|
||||
cloud.identity.delete(
|
||||
'/auth/tokens',
|
||||
headers={
|
||||
'X-Subject-Token': p.get('revoke_token')
|
||||
}
|
||||
)
|
||||
except (keystoneauth1.exceptions.http.HttpError,
|
||||
requests.exceptions.RequestException):
|
||||
s = "Error performing token revoke"
|
||||
logging.exception(s)
|
||||
s += "\n" + traceback.format_exc()
|
||||
module.fail_json(
|
||||
changed=False,
|
||||
msg=s,
|
||||
cloud=cloud.name,
|
||||
region_name=cloud.config.region_name)
|
||||
module.exit_json(changed=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
7
roles/revoke_token/tasks/main.yaml
Normal file
7
roles/revoke_token/tasks/main.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
- name: Revoke token
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
os_auth_revoke:
|
||||
cloud: "{{ cloud }}"
|
||||
revoke_token: "{{ token }}"
|
||||
failed_when: false
|
Loading…
Reference in New Issue
Block a user