feat: added new roles to match daily driver desktop; full idempotency; several fixes and tweaks; re-added hosts in inventory

This commit is contained in:
NaeiKinDus 2023-11-19 00:00:00 +00:00
parent 555fde4351
commit 726b7668f9
Signed by: WoodSmellParticle
GPG key ID: 8E52ADFF7CA8AE56
65 changed files with 10012 additions and 377 deletions

View file

@ -17,43 +17,41 @@ description: vaguely similar to a package manager, but for GitHub artifacts.
version_added: "2.15.0"
options:
artifacts:
description: a list of artifacts to retrieve
type: list
asset_name:
description: filename of the asset to retrieve, used only for release type; supports templating
type: str
required: false
default: ""
asset_type:
description: whether the asset is a release or just a tagged asset
type: str
required: true
elements: dict
suboptions:
asset_name:
description: filename of the asset to retrieve, used only for release type; supports templating
type: str
required: false
default: ""
asset_type:
description: whether the asset is a release or just a tagged asset
type: str
required: true
choices:
- release
- tag
cmds:
description: commands to execute in order to install the downloaded asset; supports templating
type: list
elements: str
required: false
default: []
repository:
description: repository to query, formatted like "<owner>/<repo>"
required: true
type: str
version:
description: version of the asset to fetch; defaults to `latest`
required: false
type: str
default: latest
choices:
- release
- tag
cmds:
description: commands to execute in order to install the downloaded asset; supports templating
type: list
elements: str
required: false
default: []
creates:
description: if provided and target file / directory exists, this step will **not** be run
type: str
required: false
github_token:
description: a GitHub app token if you have one; limits impact of rate-limiting errors
type: str
required: false
repository:
description: repository to query, formatted like "<owner>/<repo>"
required: true
type: str
version:
description: version of the asset to fetch; defaults to `latest`
required: false
type: str
default: latest
notes:
- "Strings that allow the use of templating variables support the following:"
@ -72,23 +70,18 @@ author:
EXAMPLES = r'''
- name: Install dependencies from GitHub
become: yes
become: true
tags:
- molecule-idempotence-notest
github_artifact:
artifacts:
- asset_type: tag
repository: smxi/inxi
cmds:
- tar -zxf {asset_dirname}/{asset_filename}
- install --group=root --mode=755 --owner=root smxi-inxi-*/inxi /usr/bin
- install --group=root --mode=644 --owner=root smxi-inxi-*/inxi.1 /usr/share/man/man1
- apt-get install libdata-dump-perl
- asset_name: dive_{version}_linux_amd64.deb
asset_type: release
repository: wagoodman/dive
cmds:
- dpkg -i {asset_dirname}/{asset_filename}
nullified.infrastructure.github_artifact:
asset_type: tag
repository: smxi/inxi
cmds:
- tar -zxf {asset_dirname}/{asset_filename}
- install --group=root --mode=755 --owner=root smxi-inxi-*/inxi /usr/bin
- install --group=root --mode=644 --owner=root smxi-inxi-*/inxi.1 /usr/share/man/man1
- apt-get install libdata-dump-perl
creates: /usr/bin/inxi
'''
RETURN = r'''
@ -122,7 +115,7 @@ try:
from datetime import datetime
from difflib import SequenceMatcher
from json import loads
from os import environ, sep
from os import environ, sep, path
from platform import system, machine
from typing import Any
LIB_IMPORTED = True
@ -260,7 +253,7 @@ def get_tagged_asset(artifact: dict[str, Any]) -> tuple[dict[str, str], dict[str
}, info
def fetch_metadata(artifact: dict[str, str]) -> dict[str, str] | None:
def fetch_metadata(artifact: dict[str, str | list[str]]) -> dict[str, str] | None:
""" retrieve metadata from the specified repository """
if artifact["asset_type"] == "tag":
metadata, info = get_tagged_asset(artifact)
@ -278,37 +271,34 @@ def main():
global ANSIBLE_MODULE
module_args: dict[str, dict[str, Any]] = {
"artifacts": {
"type": "list",
"elements": "dict",
"asset_name": {
"type": "str",
"required": False,
"default": ""
},
"asset_type": {
"type": "str",
"required": True,
"options": {
"asset_name": {
"type": "str",
"required": False,
"default": ""
},
"asset_type": {
"type": "str",
"required": True,
"choices": ["release", "tag"],
},
"cmds": {
"type": "list",
"elements": "str",
"required": False,
"default": []
},
"repository": {
"type": "str",
"required": True
},
"version": {
"type": "str",
"required": False,
"default": "latest"
}
}
"choices": ["release", "tag"],
},
"cmds": {
"type": "list",
"elements": "str",
"required": False,
"default": []
},
"creates": {
"type": "str",
"required": False
},
"repository": {
"type": "str",
"required": True
},
"version": {
"type": "str",
"required": False,
"default": "latest"
},
"github_token": {
"type": "str",
@ -317,9 +307,13 @@ def main():
},
}
result: dict[str, Any] = {
"artifacts": [],
"changed": False,
"msg": ""
"commands": [],
"failed": False,
"filepath": "",
"msg": "",
"state": "",
"version": ""
}
ANSIBLE_MODULE = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
@ -328,6 +322,11 @@ def main():
if ANSIBLE_MODULE.params["github_token"]:
DEFAULT_HEADERS["Authorization"] = "Bearer {}".format(ANSIBLE_MODULE.params["github_token"])
creates_file: str | None = ANSIBLE_MODULE.params.get("creates", None)
if creates_file and path.exists(creates_file):
result["state"] = "ignored"
ANSIBLE_MODULE.exit_json(**result)
if not LIB_IMPORTED:
ANSIBLE_MODULE.fail_json(msg=missing_required_lib(IMPORT_LIB_NAME), exception=IMPORT_LIB_ERROR) # pylint: disable=used-before-assignment
@ -335,66 +334,58 @@ def main():
TEMPLATE_ASSET_NAME_VARS.update({key.lower(): value for key, value in freedesktop_os_release().items()})
TEMPLATE_ASSET_NAME_VARS["system"] = system().lower()
TEMPLATE_ASSET_NAME_VARS["machine"] = machine().lower()
for artifact in ANSIBLE_MODULE.params["artifacts"]:
# fetch artifact metadata
artifact_result: dict[str, Any] = {
"asset_data": fetch_metadata(artifact),
"repository": artifact.get("repository"),
"version": artifact.get("version"),
"cmds": []
}
result["rate_limit_remaining"] = artifact_result["asset_data"].get("rate_limit_remaining", "unknown")
result["rate_limit_max"] = artifact_result["asset_data"].get("rate_limit_max", "unknown")
if "error" in artifact_result["asset_data"]:
result["artifacts"].append(artifact_result)
result["msg"] = artifact_result["asset_data"].get("error")
result["failed"] = True
ANSIBLE_MODULE.fail_json(**result)
artifact: dict[str, str | list[str]] = {}
for param_name in ["asset_name", "asset_type", "cmds", "repository", "version"]:
artifact[param_name] = ANSIBLE_MODULE.params[param_name]
# download artifact
if ANSIBLE_MODULE.check_mode:
artifact_result["download_dir"] = "unknown"
else:
artifact_result["download_dir"] = fetch_file(ANSIBLE_MODULE, artifact_result["asset_data"].get("download_url", "unknown"), decompress=False)
TEMPLATE_ASSET_NAME_VARS["asset_name"] = artifact_result["asset_data"].get("asset_name", "unknown")
TEMPLATE_ASSET_NAME_VARS["asset_version"] = artifact_result["asset_data"].get("version", "unknown")
parts = artifact_result["download_dir"].rsplit(sep, 1)
TEMPLATE_ASSET_NAME_VARS["asset_dirname"] = parts[0] if len(parts) > 1 else ""
TEMPLATE_ASSET_NAME_VARS["asset_filename"] = parts[1] if len(parts) > 1 else parts[0]
asset_data: dict[str, str] = fetch_metadata(artifact)
result["rate_limit_remaining"] = asset_data.get("rate_limit_remaining", "unknown")
result["rate_limit_max"] = asset_data.get("rate_limit_max", "unknown")
result["version"] = asset_data.get("version", artifact.get("version"))
# install artifact
artifact_commands = [line.format(**TEMPLATE_ASSET_NAME_VARS) for line in artifact["cmds"]]
if ANSIBLE_MODULE.check_mode:
artifact_result["stdout"] = artifact_result["stderr"] = ""
artifact_result["ret_code"] = None
artifact_result["cmds"] = artifact_commands
artifact_result["state"] = "should be installed" if len(artifact_commands) else "should be downloaded"
else:
for command_line in artifact_commands:
cmd_rc, cmd_out, cmd_err = ANSIBLE_MODULE.run_command(command_line, use_unsafe_shell=True, cwd=ANSIBLE_MODULE.tmpdir)
result["changed"] = True
artifact_result["cmds"].append({
"command": command_line,
"stdout": cmd_out,
"stderr": cmd_err,
"ret_code": cmd_rc
})
if "error" in asset_data:
result["state"] = "fetch failed"
result["msg"] = asset_data.get("error", "unknown error encountered")
result["failed"] = True
ANSIBLE_MODULE.fail_json(**result)
if cmd_rc:
artifact_result["state"] = "installation failed"
result["failed"] = True
result["artifacts"].append(artifact_result)
ANSIBLE_MODULE.fail_json(**result)
# download artifact
if ANSIBLE_MODULE.check_mode:
result["filepath"] = "unknown"
else:
result["filepath"] = fetch_file(ANSIBLE_MODULE, asset_data.get("download_url", "unknown"), decompress=False)
TEMPLATE_ASSET_NAME_VARS["asset_name"] = asset_data.get("asset_name", "unknown")
TEMPLATE_ASSET_NAME_VARS["asset_version"] = asset_data.get("version", "unknown")
parts = result["filepath"].rsplit(sep, 1)
TEMPLATE_ASSET_NAME_VARS["asset_dirname"] = parts[0] if len(parts) > 1 else ""
TEMPLATE_ASSET_NAME_VARS["asset_filename"] = parts[1] if len(parts) > 1 else parts[0]
try:
del artifact_result["command"], artifact_result["stdout"], artifact_result["stderr"], artifact_result["ret_code"]
except: # pylint: disable=bare-except # noqa: 722
pass
artifact_result["state"] = "installed" if len(artifact_commands) else "downloaded"
# install artifact
artifact_commands = [line.format(**TEMPLATE_ASSET_NAME_VARS) for line in artifact["cmds"]]
if ANSIBLE_MODULE.check_mode:
result["commands"] = artifact_commands
result["state"] = "should be installed" if len(artifact_commands) else "should be downloaded"
else:
for command_line in artifact_commands:
cmd_rc, cmd_out, cmd_err = ANSIBLE_MODULE.run_command(command_line, use_unsafe_shell=True, cwd=ANSIBLE_MODULE.tmpdir)
result["changed"] = True
result["commands"].append({
"command": command_line,
"stdout": cmd_out,
"stderr": cmd_err,
"ret_code": cmd_rc
})
result["artifacts"].append(artifact_result)
result["msg"] = "OK"
if cmd_rc:
result["state"] = "installation failed"
result["msg"] = cmd_err
result["failed"] = True
ANSIBLE_MODULE.fail_json(**result)
result["state"] = "installed" if len(artifact_commands) else "downloaded"
result["msg"] = "Successful"
ANSIBLE_MODULE.exit_json(**result)