Add coophost and coopaas experimental commands
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
cd3bd67783
commit
a178d0ed7c
@ -1,11 +1,27 @@
|
||||
Autonomic 0.0.2 (2020-04-11)
|
||||
Autonomic 0.0.4 (2020-04-12)
|
||||
============================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Init command. (#2)
|
||||
- Actions command. (#3)
|
||||
- Add CoopHost / CoopPass (WIP) commands. (#4)
|
||||
- Further sanity checks. (#5)
|
||||
|
||||
|
||||
Autonomic 0.0.3 (2020-04-11)
|
||||
============================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Add init command. (#2)
|
||||
- Add Ansible actions command. (#3)
|
||||
|
||||
|
||||
Autonomic 0.0.2 (2020-04-11)
|
||||
============================
|
||||
|
||||
- Woops, missed a tag!
|
||||
|
||||
|
||||
Autonomic 0.0.1 (2020-04-03)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import click
|
||||
|
||||
from autonomic.command import actions, init
|
||||
from autonomic.command import actions, coophost, cooppaas, init
|
||||
|
||||
|
||||
@click.group()
|
||||
@ -25,3 +25,5 @@ def autonomic(ctx):
|
||||
|
||||
autonomic.add_command(init.init)
|
||||
autonomic.add_command(actions.actions)
|
||||
autonomic.add_command(coophost.coophost)
|
||||
autonomic.add_command(cooppaas.cooppaas)
|
||||
|
@ -3,18 +3,19 @@
|
||||
from os import environ
|
||||
|
||||
import click
|
||||
from PyInquirer import prompt
|
||||
|
||||
from autonomic.config import ACTIONS_DIR, INFRA_DIR, PASS_STORE_DIR
|
||||
from autonomic.infra import get_passwd, run_play
|
||||
from autonomic.settings import get
|
||||
from autonomic.utils import git_status, qlist
|
||||
from autonomic.utils import ensure_config_dir, git_status, question_ask
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def actions(ctx):
|
||||
"""Run an Ansible action."""
|
||||
ensure_config_dir()
|
||||
|
||||
env = environ.copy()
|
||||
|
||||
env.update({"ANSIBLE_USER": get("username")})
|
||||
@ -22,13 +23,11 @@ def actions(ctx):
|
||||
env.update({"PASSWORD_STORE_DIR": PASS_STORE_DIR})
|
||||
|
||||
choices = ["addusers", "newhetzner", "rmhetzner", "newdokku", "pingall"]
|
||||
question = qlist("action", "Which Ansible action?", choices,)
|
||||
action = prompt(question)["action"]
|
||||
action = question_ask("action", "Which Ansible action?", choices)
|
||||
|
||||
if any(action in choice for choice in ["newhetzner", "rmhetzner"]):
|
||||
choices = ["prod", "test", "cicd"]
|
||||
question = qlist("key", "Which Hetzner API key?", choices)
|
||||
key = prompt(question)["key"]
|
||||
key = question_ask("key", "Which Hetzner API key?", choices)
|
||||
|
||||
path = "logins/hetzner/{}/api_key".format(key)
|
||||
secret = get_passwd(path)
|
||||
|
88
autonomic/command/coophost.py
Normal file
88
autonomic/command/coophost.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""CoopHost module."""
|
||||
|
||||
from os import chdir, mkdir
|
||||
from os.path import basename, exists
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from autonomic.config import CONFIG_YAML, INFRA_DIR
|
||||
from autonomic.logger import log
|
||||
from autonomic.settings import add, get
|
||||
from autonomic.utils import (
|
||||
ensure_config_dir,
|
||||
ensure_deploy_d_dir,
|
||||
input_ask,
|
||||
pass_ask,
|
||||
question_ask,
|
||||
run,
|
||||
yaml_dump,
|
||||
yaml_load,
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def coophost(ctx):
|
||||
"""Manage CoopHost resources."""
|
||||
ensure_config_dir()
|
||||
|
||||
choices = ["encrypt"]
|
||||
operation = question_ask("operation", "Which operation?", choices)
|
||||
|
||||
if operation == "encrypt":
|
||||
encrypt()
|
||||
|
||||
|
||||
def encrypt():
|
||||
"""Encrypt a secret for a CoopHost package."""
|
||||
ensure_deploy_d_dir()
|
||||
|
||||
app_dir = Path(".").absolute()
|
||||
|
||||
app = basename(Path(".").absolute())
|
||||
log.info("Auto-detected the {} application".format(app))
|
||||
|
||||
app_settings = get(app)
|
||||
if app_settings is not None and "vault-password" in app_settings:
|
||||
log.info("Using app vault password stored in {}".format(CONFIG_YAML))
|
||||
vault_password = app_settings["vault-password"]
|
||||
else:
|
||||
log.info("No app vault password configured")
|
||||
vault_password = pass_ask("Vault password?")
|
||||
|
||||
log.info("App vault password stored in {}".format(CONFIG_YAML))
|
||||
add({app: {"vault-password": vault_password}})
|
||||
|
||||
name = input_ask("Which variable do you want to encrypt?")
|
||||
value = pass_ask("Variable value to encrypt?")
|
||||
|
||||
cmd = [".venv/bin/ansible-vault", "encrypt_string", "--name", name, value]
|
||||
encrypted = run(
|
||||
cmd,
|
||||
cwd=INFRA_DIR,
|
||||
pexpect=True,
|
||||
pexpected={
|
||||
"(?i)new vault password:": vault_password,
|
||||
"(?i)confirm new vault password:": vault_password,
|
||||
},
|
||||
)
|
||||
encrypted = (
|
||||
encrypted.strip()
|
||||
.replace("\r", "")
|
||||
.replace("\nEncryption successful", "")
|
||||
)
|
||||
|
||||
chdir(app_dir)
|
||||
log.info("Changed directory back to to {}".format(app_dir))
|
||||
|
||||
vault_path = (Path(".") / "deploy.d" / "vault").absolute()
|
||||
if not exists(vault_path):
|
||||
log.info("Creating {}".format(vault_path))
|
||||
mkdir(vault_path)
|
||||
|
||||
var_path = (vault_path / "{}.yml".format(name)).absolute()
|
||||
with open(var_path, "w"):
|
||||
loaded = yaml_load(encrypted, text=True)
|
||||
yaml_dump(var_path, loaded)
|
||||
log.success("Encrypted and saved {} in {}".format(name, var_path))
|
12
autonomic/command/cooppaas.py
Normal file
12
autonomic/command/cooppaas.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""CoopPaas module."""
|
||||
|
||||
import click
|
||||
|
||||
from autonomic.logger import log
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cooppaas(ctx):
|
||||
"""Manage CoopPaas resources."""
|
||||
log.info("Still experimenting...")
|
@ -7,13 +7,12 @@ from subprocess import STDOUT
|
||||
|
||||
import click
|
||||
from emoji import emojize
|
||||
from PyInquirer import prompt
|
||||
|
||||
from autonomic.config import CONFIG_DIR, CONFIG_YAML, INFRA_DIR, INFRA_REPO
|
||||
from autonomic.infra import members
|
||||
from autonomic.logger import log
|
||||
from autonomic.settings import add
|
||||
from autonomic.utils import is_proc, qlist, run
|
||||
from autonomic.utils import is_proc, question_ask, run
|
||||
|
||||
|
||||
@click.command()
|
||||
@ -53,11 +52,12 @@ def clone_infrastructure_repo():
|
||||
def store_username():
|
||||
"""Store Autonomic username in the settings."""
|
||||
usernames = members(flatten="username")
|
||||
question = qlist("username", "What is you Autonomic username?", usernames)
|
||||
answer = prompt(question)
|
||||
add(answer)
|
||||
username = question_ask(
|
||||
"username", "What is you Autonomic username?", usernames,
|
||||
)
|
||||
add({"username": username})
|
||||
|
||||
msg = "Welcome comrade {} :kissing:".format(answer["username"])
|
||||
msg = "Welcome comrade {} :kissing:".format(username)
|
||||
log.success(emojize(msg, use_aliases=True))
|
||||
|
||||
|
||||
|
@ -5,11 +5,13 @@ is useful for storing information for future lookup when dealing with
|
||||
repetitive cooperative tasks.
|
||||
"""
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from autonomic.config import CONFIG_YAML
|
||||
from autonomic.utils import yaml_dump, yaml_load
|
||||
|
||||
|
||||
def add(yaml):
|
||||
def add(yaml: Dict[str, str]):
|
||||
"""Add YAML to the settings file."""
|
||||
from autonomic.command.init import create_configuration
|
||||
|
||||
|
@ -8,16 +8,23 @@ things here.
|
||||
"""
|
||||
|
||||
from os import chdir
|
||||
from os.path import exists
|
||||
from pathlib import Path
|
||||
from subprocess import call, check_output
|
||||
from sys import exit as sys_exit
|
||||
|
||||
from pexpect import spawn
|
||||
from psutil import process_iter
|
||||
from PyInquirer import prompt
|
||||
|
||||
from autonomic.config import INFRA_DIR
|
||||
from autonomic.config import CONFIG_DIR, CONFIG_YAML, INFRA_DIR
|
||||
from autonomic.logger import log
|
||||
from autonomic.yaml import yaml
|
||||
|
||||
|
||||
def run(cmd, cwd=None, interactive=False, **kwargs):
|
||||
def run(
|
||||
cmd, cwd=None, interactive=False, pexpect=False, pexpected=None, **kwargs
|
||||
):
|
||||
"""Run a system command.
|
||||
|
||||
Please note, all **kwargs will be passed into the check_output command so
|
||||
@ -34,6 +41,17 @@ def run(cmd, cwd=None, interactive=False, **kwargs):
|
||||
if interactive:
|
||||
return call(cmd, **kwargs)
|
||||
|
||||
if pexpect:
|
||||
child = spawn(" ".join(cmd))
|
||||
for expected, response in pexpected.items():
|
||||
try:
|
||||
child.expect(expected, timeout=5)
|
||||
child.sendline(response)
|
||||
except Exception as exception:
|
||||
msg = "Pexpecting failed, saw {}".format(str(exception))
|
||||
exit(msg)
|
||||
return child.read().decode("utf-8")
|
||||
|
||||
output = check_output(cmd, **kwargs)
|
||||
return output.decode("utf-8")
|
||||
except Exception as exception:
|
||||
@ -48,12 +66,12 @@ def exit(msg, code=1):
|
||||
else:
|
||||
log.info(msg)
|
||||
|
||||
exit(code)
|
||||
sys_exit(code)
|
||||
|
||||
|
||||
def qlist(name, message, choices):
|
||||
"""A question in list format."""
|
||||
return [
|
||||
def question_ask(name, message, choices):
|
||||
"""Ask a question."""
|
||||
question = [
|
||||
{
|
||||
"type": "list",
|
||||
"name": name,
|
||||
@ -63,11 +81,44 @@ def qlist(name, message, choices):
|
||||
}
|
||||
]
|
||||
|
||||
return answer(question, name)
|
||||
|
||||
|
||||
def pass_ask(message):
|
||||
"""Ask for a password."""
|
||||
question = [{"type": "password", "message": message, "name": "password"}]
|
||||
return answer(question, "password")
|
||||
|
||||
|
||||
def input_ask(message):
|
||||
"""Ask for input."""
|
||||
question = [{"type": "input", "message": message, "name": "input"}]
|
||||
return answer(question, "input")
|
||||
|
||||
|
||||
def answer(question, key):
|
||||
"""Retrieve an answer from a prompt"""
|
||||
result = prompt(question)
|
||||
|
||||
if not result:
|
||||
exit("Prompt cancelled")
|
||||
|
||||
def yaml_load(fpath):
|
||||
"""Load a YAML file."""
|
||||
try:
|
||||
with open(fpath, "r") as handle:
|
||||
return result[key]
|
||||
except KeyError:
|
||||
exit("{} key is missing".format(key))
|
||||
|
||||
|
||||
def yaml_load(target, text=False):
|
||||
"""Load a YAML file or text."""
|
||||
if text is True:
|
||||
try:
|
||||
return yaml.load(target)
|
||||
except Exception as exception:
|
||||
log.error(str(exception))
|
||||
|
||||
try:
|
||||
with open(target, "r") as handle:
|
||||
return yaml.load(handle.read())
|
||||
except Exception as exception:
|
||||
log.error(str(exception))
|
||||
@ -105,3 +156,19 @@ def git_status(fpath):
|
||||
else:
|
||||
msg = "No unstaged changes found in {}".format(INFRA_DIR)
|
||||
log.info(msg)
|
||||
|
||||
|
||||
def ensure_config_dir():
|
||||
"""Ensure configuration directory is in place."""
|
||||
if not exists(CONFIG_DIR):
|
||||
msg = "{} is missing, did you run 'autonomic init'?".format(CONFIG_YAML)
|
||||
exit(msg)
|
||||
|
||||
|
||||
def ensure_deploy_d_dir():
|
||||
"""Ensure deploy.d directory is in place."""
|
||||
deploy_d_dir = (Path(".") / "deploy.d").absolute()
|
||||
|
||||
if not exists(deploy_d_dir):
|
||||
msg = "No deploy.d folder found, are you in the right place?"
|
||||
exit(msg)
|
||||
|
@ -45,6 +45,7 @@ install_requires =
|
||||
click >= 7.1.1, <= 8.0
|
||||
colorama >= 0.4.3, <= 0.5
|
||||
emoji >= 0.5.4, <= 0.6
|
||||
pexpect >= 4.8.0, <= 4.9
|
||||
psutil >= 5.7.0, <= 6.0
|
||||
pyinquirer >= 1.0.3, <= 1.1
|
||||
ruamel.yaml >= 0.16.10, <= 0.17
|
||||
|
@ -1,6 +1,6 @@
|
||||
from subprocess import STDOUT
|
||||
|
||||
from autonomic.utils import is_proc, qlist, run, yaml_dump, yaml_load
|
||||
from autonomic.utils import is_proc, run, yaml_dump, yaml_load
|
||||
|
||||
|
||||
def test_run_kwargs():
|
||||
@ -18,20 +18,6 @@ def test_run_cwd(tmp_path):
|
||||
assert "testfile.txt" in output
|
||||
|
||||
|
||||
def test_make_qlist():
|
||||
output = qlist("foo", "bar", ["bang"])
|
||||
|
||||
expected = {
|
||||
"type": "list",
|
||||
"name": "foo",
|
||||
"message": "bar",
|
||||
"choices": ["bang"],
|
||||
}
|
||||
|
||||
for key, val in expected.items():
|
||||
assert expected[key] == output[0][key]
|
||||
|
||||
|
||||
def test_yaml_load(tmp_path):
|
||||
directory = tmp_path / "test"
|
||||
directory.mkdir()
|
||||
|
Reference in New Issue
Block a user