Compare commits
No commits in common. "master" and "0.0.3" have entirely different histories.
|
@ -1,36 +1,11 @@
|
|||
Autonomic 0.0.5 (2020-04-13)
|
||||
============================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Add CoopHost decrypt command. (#5)
|
||||
|
||||
|
||||
Autonomic 0.0.4 (2020-04-12)
|
||||
============================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- 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!
|
||||
Features
|
||||
--------
|
||||
|
||||
- Init command. (#2)
|
||||
- Actions command. (#3)
|
||||
|
||||
|
||||
Autonomic 0.0.1 (2020-04-03)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import click
|
||||
|
||||
from autonomic.command import actions, coophost, cooppaas, init
|
||||
from autonomic.command import actions, init
|
||||
|
||||
|
||||
@click.group()
|
||||
|
@ -25,5 +25,3 @@ def autonomic(ctx):
|
|||
|
||||
autonomic.add_command(init.init)
|
||||
autonomic.add_command(actions.actions)
|
||||
autonomic.add_command(coophost.coophost)
|
||||
autonomic.add_command(cooppaas.cooppaas)
|
||||
|
|
|
@ -3,19 +3,18 @@
|
|||
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 ensure_config_dir, git_status, question_ask
|
||||
from autonomic.utils import git_status, qlist
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def actions(ctx):
|
||||
"""Run an Ansible action."""
|
||||
ensure_config_dir()
|
||||
|
||||
env = environ.copy()
|
||||
|
||||
env.update({"ANSIBLE_USER": get("username")})
|
||||
|
@ -23,11 +22,13 @@ def actions(ctx):
|
|||
env.update({"PASSWORD_STORE_DIR": PASS_STORE_DIR})
|
||||
|
||||
choices = ["addusers", "newhetzner", "rmhetzner", "newdokku", "pingall"]
|
||||
action = question_ask("action", "Which Ansible action?", choices)
|
||||
question = qlist("action", "Which Ansible action?", choices,)
|
||||
action = prompt(question)["action"]
|
||||
|
||||
if any(action in choice for choice in ["newhetzner", "rmhetzner"]):
|
||||
choices = ["prod", "test", "cicd"]
|
||||
key = question_ask("key", "Which Hetzner API key?", choices)
|
||||
question = qlist("key", "Which Hetzner API key?", choices)
|
||||
key = prompt(question)["key"]
|
||||
|
||||
path = "logins/hetzner/{}/api_key".format(key)
|
||||
secret = get_passwd(path)
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
"""CoopHost module."""
|
||||
|
||||
from os import chdir, mkdir
|
||||
from os.path import basename, exists
|
||||
from pathlib import Path
|
||||
from socket import gethostname
|
||||
|
||||
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,
|
||||
exit,
|
||||
input_ask,
|
||||
pass_ask,
|
||||
question_ask,
|
||||
run,
|
||||
yaml_dump,
|
||||
yaml_load,
|
||||
)
|
||||
|
||||
hostname = gethostname()
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def coophost(ctx):
|
||||
"""Manage CoopHost resources."""
|
||||
ensure_config_dir()
|
||||
ensure_deploy_d_dir()
|
||||
|
||||
app_dir = Path(".").absolute()
|
||||
app = basename(app_dir)
|
||||
log.info("Auto-detected the {} application".format(app))
|
||||
|
||||
choices = ["encrypt", "decrypt"]
|
||||
operation = question_ask("operation", "Which operation?", choices)
|
||||
|
||||
if operation == "encrypt":
|
||||
encrypt(app, app_dir)
|
||||
elif operation == "decrypt":
|
||||
decrypt(app, app_dir)
|
||||
|
||||
|
||||
def get_vault_pass(app):
|
||||
"""Retrieve or set the app vault password."""
|
||||
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))
|
||||
return app_settings["vault-password"]
|
||||
|
||||
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}})
|
||||
|
||||
return vault_password
|
||||
|
||||
|
||||
def decrypt(app, app_dir):
|
||||
"""Decrypt a secret."""
|
||||
vault_password = get_vault_pass(app)
|
||||
name = input_ask("Which variable do you want to decrypt?")
|
||||
|
||||
vault_path = (Path(".") / "deploy.d" / "vault").absolute()
|
||||
var_path = (vault_path / "{}.yml".format(name)).absolute()
|
||||
|
||||
if not exists(var_path):
|
||||
exit("{}.yml is missing?".format(name))
|
||||
|
||||
cmd = [
|
||||
".venv/bin/ansible",
|
||||
hostname,
|
||||
"--inventory",
|
||||
"{},".format(hostname),
|
||||
"-m",
|
||||
"debug",
|
||||
"-a",
|
||||
"var='{}'".format(name),
|
||||
"-e @{}".format(var_path),
|
||||
"--ask-vault-pass",
|
||||
"-e",
|
||||
"ansible_user={}".format(get("username")),
|
||||
]
|
||||
|
||||
decrypted = run(
|
||||
cmd,
|
||||
cwd=INFRA_DIR,
|
||||
output=True,
|
||||
pexpect=True,
|
||||
pexpected={"(?i)vault password:": vault_password},
|
||||
)
|
||||
|
||||
log.info(decrypted)
|
||||
|
||||
|
||||
def encrypt(app, app_dir):
|
||||
"""Encrypt a secret for a CoopHost package."""
|
||||
vault_password = get_vault_pass(app)
|
||||
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 {}".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))
|
|
@ -1,12 +0,0 @@
|
|||
"""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,12 +7,13 @@ 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, question_ask, run
|
||||
from autonomic.utils import is_proc, qlist, run
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@ -52,12 +53,11 @@ def clone_infrastructure_repo():
|
|||
def store_username():
|
||||
"""Store Autonomic username in the settings."""
|
||||
usernames = members(flatten="username")
|
||||
username = question_ask(
|
||||
"username", "What is you Autonomic username?", usernames,
|
||||
)
|
||||
add({"username": username})
|
||||
question = qlist("username", "What is you Autonomic username?", usernames)
|
||||
answer = prompt(question)
|
||||
add(answer)
|
||||
|
||||
msg = "Welcome comrade {} :kissing:".format(username)
|
||||
msg = "Welcome comrade {} :kissing:".format(answer["username"])
|
||||
log.success(emojize(msg, use_aliases=True))
|
||||
|
||||
|
||||
|
|
|
@ -5,13 +5,11 @@ 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: Dict[str, str]):
|
||||
def add(yaml):
|
||||
"""Add YAML to the settings file."""
|
||||
from autonomic.command.init import create_configuration
|
||||
|
||||
|
|
|
@ -8,23 +8,16 @@ 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 CONFIG_DIR, CONFIG_YAML, INFRA_DIR
|
||||
from autonomic.config import INFRA_DIR
|
||||
from autonomic.logger import log
|
||||
from autonomic.yaml import yaml
|
||||
|
||||
|
||||
def run(
|
||||
cmd, cwd=None, interactive=False, pexpect=False, pexpected=None, **kwargs
|
||||
):
|
||||
def run(cmd, cwd=None, interactive=False, **kwargs):
|
||||
"""Run a system command.
|
||||
|
||||
Please note, all **kwargs will be passed into the check_output command so
|
||||
|
@ -41,17 +34,6 @@ def run(
|
|||
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:
|
||||
|
@ -66,12 +48,12 @@ def exit(msg, code=1):
|
|||
else:
|
||||
log.info(msg)
|
||||
|
||||
sys_exit(code)
|
||||
exit(code)
|
||||
|
||||
|
||||
def question_ask(name, message, choices):
|
||||
"""Ask a question."""
|
||||
question = [
|
||||
def qlist(name, message, choices):
|
||||
"""A question in list format."""
|
||||
return [
|
||||
{
|
||||
"type": "list",
|
||||
"name": name,
|
||||
|
@ -81,44 +63,11 @@ def question_ask(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:
|
||||
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:
|
||||
with open(fpath, "r") as handle:
|
||||
return yaml.load(handle.read())
|
||||
except Exception as exception:
|
||||
log.error(str(exception))
|
||||
|
@ -156,19 +105,3 @@ 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,7 +45,6 @@ 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, run, yaml_dump, yaml_load
|
||||
from autonomic.utils import is_proc, qlist, run, yaml_dump, yaml_load
|
||||
|
||||
|
||||
def test_run_kwargs():
|
||||
|
@ -18,6 +18,20 @@ 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