mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
parent
d2c7665be9
commit
1104387164
1 changed files with 444 additions and 0 deletions
444
lib/ansible/modules/source_control/gitlab_runner.py
Normal file
444
lib/ansible/modules/source_control/gitlab_runner.py
Normal file
|
@ -0,0 +1,444 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright: (c) 2018, Samy Coenen <samy.coenen@nubera.be>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {
|
||||||
|
'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: gitlab_runner
|
||||||
|
short_description: Create, modify and delete GitLab Runners.
|
||||||
|
description:
|
||||||
|
- Register, update and delete runners with the GitLab API.
|
||||||
|
- All operations are performed using the GitLab API v4.
|
||||||
|
- For details, consult the full API documentation at U(https://docs.gitlab.com/ee/api/runners.html).
|
||||||
|
- A valid private API token is required for all operations. You can create as many tokens as you like using the GitLab web interface at
|
||||||
|
U(https://$GITLAB_URL/profile/personal_access_tokens).
|
||||||
|
- A valid registration token is required for registering a new runner.
|
||||||
|
To create shared runners, you need to ask your administrator to give you this token.
|
||||||
|
It can be found at U(https://$GITLAB_URL/admin/runners/).
|
||||||
|
notes:
|
||||||
|
- Instead of the private_token parameter, the GITLAB_PRIVATE_TOKEN environment variable can be used.
|
||||||
|
- To create a new runner at least the C(private_token), C(registration_token), C(name) and C(url) options are required.
|
||||||
|
- Runners need to have unique names.
|
||||||
|
version_added: 2.8
|
||||||
|
author: "Samy Coenen (SamyCoenen)"
|
||||||
|
options:
|
||||||
|
private_token:
|
||||||
|
description:
|
||||||
|
- Your private token to interact with the GitLab API.
|
||||||
|
required: True
|
||||||
|
type: str
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The unique name of the runner.
|
||||||
|
required: True
|
||||||
|
type: str
|
||||||
|
api_timeout:
|
||||||
|
description:
|
||||||
|
- The maximum time that a request will be attempted to the GitLab API.
|
||||||
|
required: False
|
||||||
|
default: 30
|
||||||
|
type: int
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Make sure that the runner with the same name exists with the same configuration or delete the runner with the same name.
|
||||||
|
required: False
|
||||||
|
default: "present"
|
||||||
|
choices: ["present", "absent"]
|
||||||
|
type: str
|
||||||
|
registration_token:
|
||||||
|
description:
|
||||||
|
- The registration token is used to register new runners.
|
||||||
|
required: True
|
||||||
|
type: str
|
||||||
|
url:
|
||||||
|
description:
|
||||||
|
- The GitLab URL including the API v4 path and http or https.
|
||||||
|
required: False
|
||||||
|
default: "https://gitlab.com/api/v4/"
|
||||||
|
type: str
|
||||||
|
active:
|
||||||
|
description:
|
||||||
|
- Define if the runners is immediately active after creation.
|
||||||
|
required: False
|
||||||
|
default: True
|
||||||
|
choices: [True, False]
|
||||||
|
type: bool
|
||||||
|
locked:
|
||||||
|
description:
|
||||||
|
- Determines if the runner is locked or not.
|
||||||
|
required: False
|
||||||
|
default: False
|
||||||
|
choices: [true, false]
|
||||||
|
type: bool
|
||||||
|
access_level:
|
||||||
|
description:
|
||||||
|
- Determines if a runner can pick up jobs from protected branches.
|
||||||
|
required: False
|
||||||
|
default: "ref_protected"
|
||||||
|
choices: ["ref_protected", "not_protected"]
|
||||||
|
type: str
|
||||||
|
maximum_timeout:
|
||||||
|
description:
|
||||||
|
- The maximum timeout that a runner has to pick up a specific job.
|
||||||
|
required: False
|
||||||
|
default: 3600
|
||||||
|
type: int
|
||||||
|
run_untagged:
|
||||||
|
description:
|
||||||
|
- Run untagged jobs or not.
|
||||||
|
required: False
|
||||||
|
default: True
|
||||||
|
type: bool
|
||||||
|
tag_list:
|
||||||
|
description: The tags that apply to the runner.
|
||||||
|
required: False
|
||||||
|
default: ["docker"]
|
||||||
|
type: list
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Register a new runner (if it does not exist)
|
||||||
|
|
||||||
|
- name: Register runner
|
||||||
|
gitlab_runner:
|
||||||
|
url: https://gitlab.com/api/v4/
|
||||||
|
private_token: ...5432632464326432632463246...
|
||||||
|
registration_token: ...4gfdsg345...
|
||||||
|
name: Docker Machine t1
|
||||||
|
state: present
|
||||||
|
active: True
|
||||||
|
tag_list: ['docker']
|
||||||
|
run_untagged: False
|
||||||
|
locked: False
|
||||||
|
access_level: ref_protected
|
||||||
|
register: api
|
||||||
|
|
||||||
|
# Delete a runner
|
||||||
|
- name: Delete runner
|
||||||
|
gitlab_runner:
|
||||||
|
url: https://gitlab.com/api/v4/
|
||||||
|
private_token: ...5432632464326432632463246...
|
||||||
|
registration_token: ...4gfdsg345...
|
||||||
|
name: Docker Machine t1
|
||||||
|
state: absent
|
||||||
|
register: api
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
changed:
|
||||||
|
description: Values changed on the API
|
||||||
|
returned: changed
|
||||||
|
type: boolean
|
||||||
|
sample: false
|
||||||
|
msg:
|
||||||
|
description: Information returned from the API when updating a runner, a create only returns the id and token.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"id": 31,
|
||||||
|
"description": "Docker Machine t2",
|
||||||
|
"active": true,
|
||||||
|
"is_shared": true,
|
||||||
|
"name": null,
|
||||||
|
"online": null,
|
||||||
|
"status": "not_connected",
|
||||||
|
"tag_list": [
|
||||||
|
"docker"
|
||||||
|
],
|
||||||
|
"run_untagged": true,
|
||||||
|
"locked": false,
|
||||||
|
"maximum_timeout": null,
|
||||||
|
"access_level": "ref_protected",
|
||||||
|
"version": null,
|
||||||
|
"revision": null,
|
||||||
|
"platform":null,
|
||||||
|
"architecture": null,
|
||||||
|
"contacted_at": null,
|
||||||
|
"token": "ba4be.....e6a3b8",
|
||||||
|
"projects": [],
|
||||||
|
"groups": []
|
||||||
|
}
|
||||||
|
token:
|
||||||
|
description: Runner token of affected runner
|
||||||
|
returned: when registered or updated runner
|
||||||
|
type: str
|
||||||
|
sample: ["2a5aeecc61dc98c4d780b14b330e3282"]
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.urls import fetch_url
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleGitlabAPI(AnsibleModule):
|
||||||
|
def __init__(self, module, url, private_token):
|
||||||
|
self._module = module
|
||||||
|
self._auth_header = {'PRIVATE-TOKEN': private_token}
|
||||||
|
self.url = url
|
||||||
|
self.timeout = module.params['api_timeout']
|
||||||
|
|
||||||
|
def check_response(self, info, response, api_call):
|
||||||
|
"""
|
||||||
|
Checks response code.
|
||||||
|
Returns: response in JSON.
|
||||||
|
"""
|
||||||
|
if info['status'] in (200, 201):
|
||||||
|
return json.loads(response.read())
|
||||||
|
elif info['status'] == 204:
|
||||||
|
return json.loads('{"msg":"Request completed"}')
|
||||||
|
elif info['status'] in (403, 404):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self._module.fail_json(msg='Failure while calling the GitLab API for '
|
||||||
|
'"%s".' % api_call, fetch_url_info=info)
|
||||||
|
|
||||||
|
def _get(self, api_call):
|
||||||
|
resp, info = fetch_url(self._module, self.url + api_call,
|
||||||
|
headers=self._auth_header,
|
||||||
|
timeout=self.timeout)
|
||||||
|
return self.check_response(info, resp, api_call)
|
||||||
|
|
||||||
|
def _post(self, api_call, data=None):
|
||||||
|
"""
|
||||||
|
Sends POST request.
|
||||||
|
Returns: response.
|
||||||
|
"""
|
||||||
|
headers = self._auth_header.copy()
|
||||||
|
if data is not None:
|
||||||
|
data = self._module.jsonify(data)
|
||||||
|
headers['Content-type'] = 'application/json'
|
||||||
|
|
||||||
|
resp, info = fetch_url(self._module,
|
||||||
|
self.url + api_call,
|
||||||
|
headers=headers,
|
||||||
|
method='POST',
|
||||||
|
data=data,
|
||||||
|
timeout=self.timeout)
|
||||||
|
return self.check_response(info, resp, api_call)
|
||||||
|
|
||||||
|
def _put(self, api_call, data=None):
|
||||||
|
"""
|
||||||
|
Sends PUT request.
|
||||||
|
Returns: response.
|
||||||
|
"""
|
||||||
|
headers = self._auth_header.copy()
|
||||||
|
if data is not None:
|
||||||
|
data = self._module.jsonify(data)
|
||||||
|
headers['Content-type'] = 'application/json'
|
||||||
|
|
||||||
|
resp, info = fetch_url(self._module,
|
||||||
|
self.url + api_call,
|
||||||
|
headers=headers,
|
||||||
|
method='PUT',
|
||||||
|
data=data,
|
||||||
|
timeout=self.timeout)
|
||||||
|
return self.check_response(info, resp, api_call)
|
||||||
|
|
||||||
|
def _delete(self, api_call):
|
||||||
|
"""
|
||||||
|
Sends DELETE request.
|
||||||
|
Returns: response.
|
||||||
|
"""
|
||||||
|
resp, info = fetch_url(self._module,
|
||||||
|
self.url + api_call,
|
||||||
|
headers=self._auth_header,
|
||||||
|
method='DELETE',
|
||||||
|
timeout=self.timeout)
|
||||||
|
return self.check_response(info, resp, api_call)
|
||||||
|
|
||||||
|
def get_runner_id(self, description):
|
||||||
|
"""
|
||||||
|
Gets the ID for a given description.
|
||||||
|
Returns: ID as int.
|
||||||
|
"""
|
||||||
|
r = self._get('runners/all')
|
||||||
|
if r is None:
|
||||||
|
return None
|
||||||
|
for runner in r:
|
||||||
|
if runner['description'] == description:
|
||||||
|
return runner.get('id')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete_runner(self, id):
|
||||||
|
"""
|
||||||
|
Sends DELETE request for runner with the same ID.
|
||||||
|
Returns: response.
|
||||||
|
"""
|
||||||
|
return self._delete('runners/' + str(id))
|
||||||
|
|
||||||
|
def update_runner(self, id, runner):
|
||||||
|
"""
|
||||||
|
Sends UPDATE request for runner to change all the fields.
|
||||||
|
Returns: response.
|
||||||
|
"""
|
||||||
|
form_data = runner.get_dict()
|
||||||
|
return self._put('runners/' + str(id), form_data)
|
||||||
|
|
||||||
|
def register_runner(self, runner, registration_token):
|
||||||
|
"""
|
||||||
|
Sends POST request to register runner.
|
||||||
|
Returns: response.
|
||||||
|
"""
|
||||||
|
form_data = runner.get_dict()
|
||||||
|
form_data["token"] = registration_token
|
||||||
|
return self._post('runners/', form_data)
|
||||||
|
|
||||||
|
def get_runner_list_short(self):
|
||||||
|
"""
|
||||||
|
Sends GET request to get a global list of all runners.
|
||||||
|
Returns: JSON list.
|
||||||
|
"""
|
||||||
|
return self._get('runners/all')
|
||||||
|
|
||||||
|
def get_runner_list(self, ids, primary_key):
|
||||||
|
"""
|
||||||
|
Sends GET requests to get details of all runners.
|
||||||
|
Returns: JSON list.
|
||||||
|
"""
|
||||||
|
d = {}
|
||||||
|
for i in ids:
|
||||||
|
runner_details = self.get_runner_details(i)
|
||||||
|
d[runner_details.get(primary_key)] = runner_details
|
||||||
|
return d
|
||||||
|
|
||||||
|
def get_runner_details(self, id):
|
||||||
|
"""
|
||||||
|
Sends GET request to get the details of a specific runner .
|
||||||
|
Returns: JSON list.
|
||||||
|
"""
|
||||||
|
return self._get('runners/' + str(id))
|
||||||
|
|
||||||
|
|
||||||
|
class Runner():
|
||||||
|
def __init__(self, id, description, active, tag_list, run_untagged, locked, access_level, maximum_timeout):
|
||||||
|
self.id = id
|
||||||
|
self.description = description
|
||||||
|
self.active = active
|
||||||
|
self.tag_list = tag_list
|
||||||
|
self.run_untagged = run_untagged
|
||||||
|
self.locked = locked
|
||||||
|
self.access_level = access_level
|
||||||
|
self.maximum_timeout = maximum_timeout
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""
|
||||||
|
Compare every field and its value with the fields and values of another instance of this class.
|
||||||
|
Returns: boolean.
|
||||||
|
"""
|
||||||
|
return self.__dict__ == other.__dict__
|
||||||
|
|
||||||
|
def get_dict(self):
|
||||||
|
"""
|
||||||
|
Returns every field and its value of this class.
|
||||||
|
Returns: dict.
|
||||||
|
"""
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
|
||||||
|
def get_gitlab_argument_spec():
|
||||||
|
"""
|
||||||
|
Returns argument spec with all optional or required Ansible arguments.
|
||||||
|
Returns: dict.
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
private_token=dict(
|
||||||
|
fallback=(env_fallback, ['GITLAB_PRIVATE_TOKEN']),
|
||||||
|
no_log=True,
|
||||||
|
required=True),
|
||||||
|
name=dict(required=True, type='str'),
|
||||||
|
active=dict(required=False, type='bool', default=True, choices=[True, False]),
|
||||||
|
tag_list=dict(required=False, type='list',
|
||||||
|
default=["docker"]),
|
||||||
|
run_untagged=dict(
|
||||||
|
required=False, type='bool', default=True),
|
||||||
|
locked=dict(required=False, type='bool', default=False, choices=[True, False]),
|
||||||
|
access_level=dict(required=False, type='str',
|
||||||
|
default='ref_protected', choices=["ref_protected", "not_protected"]),
|
||||||
|
maximum_timeout=dict(
|
||||||
|
required=False, type='int', default=3600),
|
||||||
|
api_timeout=dict(default=30, type='int'),
|
||||||
|
url=dict(required=False, type='str',
|
||||||
|
default="https://gitlab.com/api/v4/"),
|
||||||
|
registration_token=dict(required=True, type='str'),
|
||||||
|
state=dict(required=False, type='str', default="present", choices=["present", "absent"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = get_gitlab_argument_spec()
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
target_state = module.params['state']
|
||||||
|
description = module.params['name']
|
||||||
|
private_token = module.params['private_token']
|
||||||
|
url = module.params['url']
|
||||||
|
active = module.params['active']
|
||||||
|
tag_list = module.params['tag_list']
|
||||||
|
run_untagged = module.params['run_untagged']
|
||||||
|
locked = module.params['locked']
|
||||||
|
access_level = module.params['access_level']
|
||||||
|
maximum_timeout = module.params['maximum_timeout']
|
||||||
|
|
||||||
|
api = AnsibleGitlabAPI(module, url, private_token)
|
||||||
|
id = api.get_runner_id(description)
|
||||||
|
target_runner = Runner(id, description, active, tag_list,
|
||||||
|
run_untagged, locked, access_level, maximum_timeout)
|
||||||
|
token = None
|
||||||
|
api_runner = None
|
||||||
|
response = None
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
# Check if runner needs to be registered, updated or deleted
|
||||||
|
# Don't actually change anything if module is in check_mode (dry run)
|
||||||
|
if target_state == 'present':
|
||||||
|
if id is None:
|
||||||
|
if not module.check_mode:
|
||||||
|
response = api.register_runner(
|
||||||
|
target_runner, module.params["registration_token"])
|
||||||
|
token = response['token']
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
api_runner_details = api.get_runner_details(id)
|
||||||
|
response = api_runner_details
|
||||||
|
token = api_runner_details['token']
|
||||||
|
id = api_runner_details['id']
|
||||||
|
description = api_runner_details['description']
|
||||||
|
active = api_runner_details['active']
|
||||||
|
tag_list = api_runner_details['tag_list']
|
||||||
|
locked = api_runner_details['locked']
|
||||||
|
access_level = api_runner_details['access_level']
|
||||||
|
maximum_timeout = api_runner_details['maximum_timeout']
|
||||||
|
api_runner = Runner(id, description, active, tag_list,
|
||||||
|
run_untagged, locked, access_level, maximum_timeout)
|
||||||
|
if api_runner == target_runner:
|
||||||
|
changed = False
|
||||||
|
else:
|
||||||
|
if not module.check_mode:
|
||||||
|
response = api.update_runner(id, target_runner)
|
||||||
|
changed = True
|
||||||
|
elif target_state == 'absent':
|
||||||
|
if id is None:
|
||||||
|
changed = False
|
||||||
|
else:
|
||||||
|
if not module.check_mode:
|
||||||
|
response = api.delete_runner(id)
|
||||||
|
changed = True
|
||||||
|
module.exit_json(changed=changed, msg=response, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in a new issue