From 38ee0e0c8fc80ffe0695b69e5e5c612811175473 Mon Sep 17 00:00:00 2001 From: Cian Hatton Date: Sat, 13 Aug 2022 15:09:13 +0100 Subject: [PATCH] wip: working on portainer ansible module --- ansible/library/portainer.py | 106 ++++++++++++++++++-- docker-compose/bitwarden/docker-compose.yml | 2 +- 2 files changed, 97 insertions(+), 11 deletions(-) mode change 100644 => 100755 ansible/library/portainer.py diff --git a/ansible/library/portainer.py b/ansible/library/portainer.py old mode 100644 new mode 100755 index 16aac89..650955f --- a/ansible/library/portainer.py +++ b/ansible/library/portainer.py @@ -3,8 +3,11 @@ # Copyright: (c) 2018, Terry Jones # 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 +import requests + DOCUMENTATION = r''' --- module: my_test @@ -72,11 +75,66 @@ message: from ansible.module_utils.basic import AnsibleModule +def _extract_creds(module): + return { + "username": module.params["username"], + "password": module.params["password"], + "base_url": module.params["base_url"], + } + + +def _get_jwt_token(creds): + payload = { + "Username": creds["username"], + "Password": creds["password"], + } + + base_url = creds["base_url"] + auth_url = f"{base_url}/api/auth" + resp = requests.post(auth_url, json=payload) + resp.raise_for_status() + return resp.json()["jwt"] + + +COMPOSE_STACK = 2 +STRING_METHOD = "string" + + +class PortainerClient: + def __init__(self, creds): + self.base_url = creds["base_url"] + self.token = _get_jwt_token(creds) + self.headers = { + "Authorization": f"Bearer {self.token}" + } + + def get(self, endpoint): + url = f"{self.base_url}/api/{endpoint}" + res = requests.get(url, headers=self.headers) + res.raise_for_status() + return res.json() + + def post(self, endpoint, body, query_params=None): + url = f"{self.base_url}/api/{endpoint}" + if query_params: + url += "?" + for k, v in query_params.items(): + url += f"&{k}={v}" + + res = requests.post(url, json=body, headers=self.headers) + res.raise_for_status() + return res.json() + + def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( - name=dict(type='str', required=True), - new=dict(type='bool', required=False, default=False) + stack_name=dict(type='str', required=True), + docker_compose_file_path=dict(type='str', required=True), + env_file_path=dict(type='str', required=False), + username=dict(type='str', default='admin'), + password=dict(type='str', required=True), + base_url=dict(type='str', default="http://localhost:9000") ) # seed the result dict in the object @@ -86,8 +144,6 @@ def run_module(): # for consumption, for example, in a subsequent task result = dict( changed=False, - original_message='', - message='' ) # the AnsibleModule object will be our abstraction working with Ansible @@ -99,6 +155,36 @@ def run_module(): supports_check_mode=True ) + client = PortainerClient(creds=_extract_creds(module)) + + stacks = client.get("stacks") + + result["token"] = client.token + # result["stacks"] = stacks + + file_contents = "" + with open(module.params["docker_compose_file_path"]) as f: + file_contents = f.read() + + result["stacks"] = stacks + result["stack_name"] = module.params["stack_name"] + + body = { + "body_compose_string": { + "name": module.params["stack_name"], + "stackFileContent": file_contents, + }, + } + + query_params = { + "type": COMPOSE_STACK, + "method": STRING_METHOD, + "endpointId": 2, + } + + res = client.post("stacks", body=body, query_params=query_params) + result["res"] = res + # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current # state with no modifications @@ -107,19 +193,19 @@ def run_module(): # manipulate or modify the state as needed (this is going to be the # part where your module will do what it needs to do) - result['original_message'] = module.params['name'] - result['message'] = 'goodbye' + # result['original_message'] = module.params['name'] + # result['message'] = 'goodbye' # use whatever logic you need to determine whether or not this module # made any modifications to your target - if module.params['new']: - result['changed'] = True + # if module.params['new']: + # result['changed'] = True # during the execution of the module, if there is an exception or a # conditional state that effectively causes a failure, run # AnsibleModule.fail_json() to pass in the message and the result - if module.params['name'] == 'fail me': - module.fail_json(msg='You requested this to fail', **result) + # if module.params['name'] == 'fail me': + # module.fail_json(msg='You requested this to fail', **result) # in the event of a successful module execution, you will want to # simple AnsibleModule.exit_json(), passing the key/value results diff --git a/docker-compose/bitwarden/docker-compose.yml b/docker-compose/bitwarden/docker-compose.yml index e109081..2bca280 100644 --- a/docker-compose/bitwarden/docker-compose.yml +++ b/docker-compose/bitwarden/docker-compose.yml @@ -4,7 +4,7 @@ services: image: bitwardenrs/server restart: always ports: - - ${HOST_PORT}:80 + - 80:80 volumes: - data:/data environment: