Skip to content

Commit

Permalink
feat: add dokku_resource_limit and dokku_resource_reserve modules (#104)
Browse files Browse the repository at this point in the history
The `dokku_resource_limit` and `dokku_resource_reserve` modules allow limiting and reserving
resources (cpu, ram, ...) for individual apps.

Co-authored-by: Leopold Talirz <leopold.talirz@gmail.com>
  • Loading branch information
apavanello and ltalirz authored May 8, 2021
1 parent 1825952 commit 4427ea8
Show file tree
Hide file tree
Showing 3 changed files with 599 additions and 0 deletions.
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,100 @@ Manage the registry configuration for a given dokku application
state: absent
```

### dokku_resource_limit

Manage resource limits for a given dokku application

#### Parameters

|Parameter|Choices/Defaults|Comments|
|---------|----------------|--------|
|app<br /><sup>*required*</sup>||The name of the app|
|clear_before|*Choices:* <ul><li>True</li><li>**False** (default)</li></ul>|Clear all resource limits before applying|
|process_type||The process type selector|
|resources||The Resource type and quantity (required when state=present)|
|state|*Choices:* <ul><li>**present** (default)</li><li>absent</li></ul>|The state of the resource limits|

#### Example

```yaml
- name: Limit CPU and memory of a dokku app
dokku_resource_limit:
app: hello-world
resources:
cpu: 100
memory: 100
- name: name: Limit resources per process type of a dokku app
dokku_resource_limit:
app: hello-world
process_type: web
resources:
cpu: 100
memory: 100
- name: Clear limits before applying new limits
dokku_resource_limit:
app: hello-world
state: present
clear_before: True
resources:
cpu: 100
memory: 100
- name: Remove all resource limits
dokku_resource_limit:
app: hello-world
state: absent
```

### dokku_resource_reserve

Manage resource reservations for a given dokku application

#### Parameters

|Parameter|Choices/Defaults|Comments|
|---------|----------------|--------|
|app<br /><sup>*required*</sup>||The name of the app|
|clear_before|*Choices:* <ul><li>True</li><li>**False** (default)</li></ul>|Clear all reserves before apply|
|process_type||The process type selector|
|resources||The Resource type and quantity (required when state=present)|
|state|*Choices:* <ul><li>**present** (default)</li><li>absent</li></ul>|The state of the resource reservations|

#### Example

```yaml
- name: Reserve CPU and memory for a dokku app
dokku_resource_reserve:
app: hello-world
resources:
cpu: 100
memory: 100
- name: Create a reservation per process type of a dokku app
dokku_resource_reserve:
app: hello-world
process_type: web
resources:
cpu: 100
memory: 100
- name: Clear all reservations before applying
dokku_resource_reserve:
app: hello-world
state: present
clear_before: True
resources:
cpu: 100
memory: 100
- name: Remove all resource reservations
dokku_resource_reserve:
app: hello-world
state: absent
```

### dokku_service_create

Creates a given service
Expand Down
253 changes: 253 additions & 0 deletions library/dokku_resource_limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# from ansible.module_utils.basic import *
from ansible.module_utils.basic import AnsibleModule
import subprocess
import re

DOCUMENTATION = """
---
module: dokku_resource_limit
short_description: Manage resource limits for a given dokku application
options:
app:
description:
- The name of the app
required: True
default: null
aliases: []
resources:
description:
- The Resource type and quantity (required when state=present)
required: False
default: null
aliases: []
process_type:
description:
- The process type selector
required: False
default: null
alias: []
clear_before:
description:
- Clear all resource limits before applying
required: False
default: "False"
choices: [ "True", "False" ]
aliases: []
state:
description:
- The state of the resource limits
required: False
default: present
choices: [ "present", "absent" ]
aliases: []
author: Alexandre Pavanello e Silva
requirements: [ ]
"""

EXAMPLES = """
- name: Limit CPU and memory of a dokku app
dokku_resource_limit:
app: hello-world
resources:
cpu: 100
memory: 100
- name: name: Limit resources per process type of a dokku app
dokku_resource_limit:
app: hello-world
process_type: web
resources:
cpu: 100
memory: 100
- name: Clear limits before applying new limits
dokku_resource_limit:
app: hello-world
state: present
clear_before: True
resources:
cpu: 100
memory: 100
- name: Remove all resource limits
dokku_resource_limit:
app: hello-world
state: absent
"""


def force_list(var):
if isinstance(var, list):
return var
return list(var)


def subprocess_check_output(command, split="\n"):
error = None
output = []
try:
output = subprocess.check_output(command, shell=True)
if isinstance(output, bytes):
output = output.decode("utf-8")
output = str(output).rstrip("\n")
if split is None:
return output, error

output = output.split(split)
output = force_list(filter(None, output))
output = [o.strip() for o in output]
except subprocess.CalledProcessError as e:
error = str(e)
return output, error


def dokku_resource_clear(data):
error = None
process_type = ""
if data["process_type"]:
process_type = "--process-type {0}".format(data["process_type"])
command = "dokku resource:limit-clear {0} {1}".format(process_type, data["app"])
try:
subprocess.check_call(command, shell=True)
except subprocess.CalledProcessError as e:
error = str(e)
return error


def dokku_resource_limit_report(data):

process_type = ""
if data["process_type"]:
process_type = "--process-type {0}".format(data["process_type"])
command = "dokku --quiet resource:limit {0} {1}".format(process_type, data["app"])

output, error = subprocess_check_output(command)
if error is not None:
return output, error
output = [re.sub(r"\s+", "", line) for line in output]

report = {}

for line in output:
if ":" not in line:
continue
key, value = line.split(":", 1)
report[key] = value

return report, error


def dokku_resource_limit_present(data):
is_error = True
has_changed = False
meta = {"present": False}

if "resources" not in data:
meta["error"] = "missing required arguments: resources"
return (is_error, has_changed, meta)

report, error = dokku_resource_limit_report(data)
meta["debug"] = report.keys()
if error:
meta["error"] = error
return (is_error, has_changed, meta)

for k, v in data["resources"].items():
if k not in report.keys():
is_error = True
has_changed = False
meta["error"] = "Unknown resource {0}, choose one of: {1}".format(
k, list(report.keys())
)
return (is_error, has_changed, meta)
if report[k] != str(v):
has_changed = True

if data["clear_before"] is True:

error = dokku_resource_clear(data)
if error:
meta["error"] = error
is_error = True
has_changed = False
return (is_error, has_changed, meta)
has_changed = True

if not has_changed:
meta["present"] = True
is_error = False
return (is_error, has_changed, meta)

values = []
for key, value in data["resources"].items():
values.append("--{0} {1}".format(key, value))

process_type = ""
if data["process_type"]:
process_type = "--process-type {0}".format(data["process_type"])

command = "dokku resource:limit {0} {1} {2}".format(
" ".join(values), process_type, data["app"]
)
try:
subprocess.check_call(command, shell=True)
is_error = False
has_changed = True
meta["present"] = True
except subprocess.CalledProcessError as e:
meta["error"] = str(e)
return (is_error, has_changed, meta)


def dokku_resource_limit_absent(data):
is_error = True
has_changed = False
meta = {"present": True}

error = dokku_resource_clear(data)
if error:
meta["error"] = error
is_error = True
has_changed = False
return (is_error, has_changed, meta)

is_error = False
has_changed = True
meta = {"present": False}

return (is_error, has_changed, meta)


def main():
fields = {
"app": {"required": True, "type": "str"},
"process_type": {"required": False, "type": "str"},
"resources": {"required": False, "type": "dict"},
"clear_before": {"required": False, "type": "bool"},
"state": {
"required": False,
"default": "present",
"choices": ["present", "absent"],
"type": "str",
},
}
choice_map = {
"present": dokku_resource_limit_present,
"absent": dokku_resource_limit_absent,
}

module = AnsibleModule(argument_spec=fields, supports_check_mode=False)
is_error, has_changed, result = choice_map.get(module.params["state"])(
module.params
)

if is_error:
module.fail_json(msg=result["error"], meta=result)
module.exit_json(changed=has_changed, meta=result)


if __name__ == "__main__":
main()
Loading

0 comments on commit 4427ea8

Please sign in to comment.