Merge "Fix use of --ask-vault-pass argument"
This commit is contained in:
commit
ca92fb4a91
@ -79,6 +79,7 @@ def _get_inventory_path(parsed_args):
|
|||||||
|
|
||||||
def _validate_args(parsed_args, playbooks):
|
def _validate_args(parsed_args, playbooks):
|
||||||
"""Validate Kayobe Ansible arguments."""
|
"""Validate Kayobe Ansible arguments."""
|
||||||
|
vault.validate_args(parsed_args)
|
||||||
result = utils.is_readable_dir(parsed_args.config_path)
|
result = utils.is_readable_dir(parsed_args.config_path)
|
||||||
if not result["result"]:
|
if not result["result"]:
|
||||||
LOG.error("Kayobe configuration path %s is invalid: %s",
|
LOG.error("Kayobe configuration path %s is invalid: %s",
|
||||||
@ -124,7 +125,7 @@ def build_args(parsed_args, playbooks,
|
|||||||
cmd += ["-" + "v" * verbose_level]
|
cmd += ["-" + "v" * verbose_level]
|
||||||
if parsed_args.list_tasks:
|
if parsed_args.list_tasks:
|
||||||
cmd += ["--list-tasks"]
|
cmd += ["--list-tasks"]
|
||||||
cmd += vault.build_args(parsed_args)
|
cmd += vault.build_args(parsed_args, "--vault-password-file")
|
||||||
inventory = _get_inventory_path(parsed_args)
|
inventory = _get_inventory_path(parsed_args)
|
||||||
cmd += ["--inventory", inventory]
|
cmd += ["--inventory", inventory]
|
||||||
vars_files = _get_vars_files(parsed_args.config_path)
|
vars_files = _get_vars_files(parsed_args.config_path)
|
||||||
@ -152,13 +153,6 @@ def build_args(parsed_args, playbooks,
|
|||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
def _read_vault_password_file(vault_password_file):
|
|
||||||
"""Return the password from a vault password file."""
|
|
||||||
vault_password = utils.read_file(vault_password_file)
|
|
||||||
vault_password = vault_password.strip()
|
|
||||||
return vault_password
|
|
||||||
|
|
||||||
|
|
||||||
def run_playbooks(parsed_args, playbooks,
|
def run_playbooks(parsed_args, playbooks,
|
||||||
extra_vars=None, limit=None, tags=None, quiet=False,
|
extra_vars=None, limit=None, tags=None, quiet=False,
|
||||||
verbose_level=None, check=None):
|
verbose_level=None, check=None):
|
||||||
@ -168,13 +162,7 @@ def run_playbooks(parsed_args, playbooks,
|
|||||||
extra_vars=extra_vars, limit=limit, tags=tags,
|
extra_vars=extra_vars, limit=limit, tags=tags,
|
||||||
verbose_level=verbose_level, check=check)
|
verbose_level=verbose_level, check=check)
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
# If the Vault password has been specified via --vault-password-file,
|
vault.update_environment(parsed_args, env)
|
||||||
# ensure the environment variable is set, so that it can be referenced by
|
|
||||||
# playbooks to generate the kolla-ansible passwords.yml file.
|
|
||||||
if vault.VAULT_PASSWORD_ENV not in env and parsed_args.vault_password_file:
|
|
||||||
vault_password = _read_vault_password_file(
|
|
||||||
parsed_args.vault_password_file)
|
|
||||||
env[vault.VAULT_PASSWORD_ENV] = vault_password
|
|
||||||
# If the configuration path has been specified via --config-path, ensure
|
# If the configuration path has been specified via --config-path, ensure
|
||||||
# the environment variable is set, so that it can be referenced by
|
# the environment variable is set, so that it can be referenced by
|
||||||
# playbooks.
|
# playbooks.
|
||||||
|
@ -19,6 +19,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from kayobe import utils
|
from kayobe import utils
|
||||||
|
from kayobe import vault
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONFIG_PATH = "/etc/kolla"
|
DEFAULT_CONFIG_PATH = "/etc/kolla"
|
||||||
@ -78,6 +79,7 @@ def _get_inventory_path(parsed_args, inventory_filename):
|
|||||||
|
|
||||||
def _validate_args(parsed_args, inventory_filename):
|
def _validate_args(parsed_args, inventory_filename):
|
||||||
"""Validate Kayobe Ansible arguments."""
|
"""Validate Kayobe Ansible arguments."""
|
||||||
|
vault.validate_args(parsed_args)
|
||||||
result = utils.is_readable_dir(parsed_args.kolla_config_path)
|
result = utils.is_readable_dir(parsed_args.kolla_config_path)
|
||||||
if not result["result"]:
|
if not result["result"]:
|
||||||
LOG.error("Kolla configuration path %s is invalid: %s",
|
LOG.error("Kolla configuration path %s is invalid: %s",
|
||||||
@ -106,8 +108,7 @@ def build_args(parsed_args, command, inventory_filename, extra_vars=None,
|
|||||||
cmd += ["kolla-ansible", command]
|
cmd += ["kolla-ansible", command]
|
||||||
if verbose_level:
|
if verbose_level:
|
||||||
cmd += ["-" + "v" * verbose_level]
|
cmd += ["-" + "v" * verbose_level]
|
||||||
if parsed_args.vault_password_file:
|
cmd += vault.build_args(parsed_args, "--key")
|
||||||
cmd += ["--key", parsed_args.vault_password_file]
|
|
||||||
inventory = _get_inventory_path(parsed_args, inventory_filename)
|
inventory = _get_inventory_path(parsed_args, inventory_filename)
|
||||||
cmd += ["--inventory", inventory]
|
cmd += ["--inventory", inventory]
|
||||||
if parsed_args.kolla_config_path != DEFAULT_CONFIG_PATH:
|
if parsed_args.kolla_config_path != DEFAULT_CONFIG_PATH:
|
||||||
@ -144,8 +145,10 @@ def run(parsed_args, command, inventory_filename, extra_vars=None,
|
|||||||
verbose_level=verbose_level,
|
verbose_level=verbose_level,
|
||||||
extra_args=extra_args,
|
extra_args=extra_args,
|
||||||
limit=limit)
|
limit=limit)
|
||||||
|
env = os.environ.copy()
|
||||||
|
vault.update_environment(parsed_args, env)
|
||||||
try:
|
try:
|
||||||
utils.run_command(" ".join(cmd), quiet=quiet, shell=True)
|
utils.run_command(" ".join(cmd), quiet=quiet, shell=True, env=env)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
LOG.error("kolla-ansible %s exited %d", command, e.returncode)
|
LOG.error("kolla-ansible %s exited %d", command, e.returncode)
|
||||||
sys.exit(e.returncode)
|
sys.exit(e.returncode)
|
||||||
|
@ -28,12 +28,12 @@ from kayobe import utils
|
|||||||
from kayobe import vault
|
from kayobe import vault
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.dict(os.environ, clear=True)
|
||||||
class TestCase(unittest.TestCase):
|
class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
|
||||||
def test_run_playbooks(self, mock_validate, mock_vars, mock_run):
|
def test_run_playbooks(self, mock_validate, mock_vars, mock_run):
|
||||||
mock_vars.return_value = ["/etc/kayobe/vars-file1.yml",
|
mock_vars.return_value = ["/etc/kayobe/vars-file1.yml",
|
||||||
"/etc/kayobe/vars-file2.yaml"]
|
"/etc/kayobe/vars-file2.yaml"]
|
||||||
@ -58,7 +58,6 @@ class TestCase(unittest.TestCase):
|
|||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
|
||||||
def test_run_playbooks_all_the_args(self, mock_validate, mock_vars,
|
def test_run_playbooks_all_the_args(self, mock_validate, mock_vars,
|
||||||
mock_run):
|
mock_run):
|
||||||
mock_vars.return_value = ["/path/to/config/vars-file1.yml",
|
mock_vars.return_value = ["/path/to/config/vars-file1.yml",
|
||||||
@ -102,14 +101,15 @@ class TestCase(unittest.TestCase):
|
|||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
@mock.patch.object(vault, "_ask_vault_pass")
|
||||||
def test_run_playbooks_all_the_long_args(self, mock_validate, mock_vars,
|
def test_run_playbooks_all_the_long_args(self, mock_ask, mock_validate,
|
||||||
mock_run):
|
mock_vars, mock_run):
|
||||||
mock_vars.return_value = ["/path/to/config/vars-file1.yml",
|
mock_vars.return_value = ["/path/to/config/vars-file1.yml",
|
||||||
"/path/to/config/vars-file2.yaml"]
|
"/path/to/config/vars-file2.yaml"]
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
ansible.add_args(parser)
|
ansible.add_args(parser)
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
|
mock_ask.return_value = "test-pass"
|
||||||
args = [
|
args = [
|
||||||
"--ask-vault-pass",
|
"--ask-vault-pass",
|
||||||
"--become",
|
"--become",
|
||||||
@ -123,11 +123,12 @@ class TestCase(unittest.TestCase):
|
|||||||
"--list-tasks",
|
"--list-tasks",
|
||||||
]
|
]
|
||||||
parsed_args = parser.parse_args(args)
|
parsed_args = parser.parse_args(args)
|
||||||
|
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||||
ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"])
|
ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"])
|
||||||
expected_cmd = [
|
expected_cmd = [
|
||||||
"ansible-playbook",
|
"ansible-playbook",
|
||||||
"--list-tasks",
|
"--list-tasks",
|
||||||
"--ask-vault-pass",
|
"--vault-password-file", "/path/to/kayobe-vault-password-helper",
|
||||||
"--inventory", "/path/to/inventory",
|
"--inventory", "/path/to/inventory",
|
||||||
"-e", "@/path/to/config/vars-file1.yml",
|
"-e", "@/path/to/config/vars-file1.yml",
|
||||||
"-e", "@/path/to/config/vars-file2.yaml",
|
"-e", "@/path/to/config/vars-file2.yaml",
|
||||||
@ -140,20 +141,24 @@ class TestCase(unittest.TestCase):
|
|||||||
"playbook1.yml",
|
"playbook1.yml",
|
||||||
"playbook2.yml",
|
"playbook2.yml",
|
||||||
]
|
]
|
||||||
expected_env = {"KAYOBE_CONFIG_PATH": "/path/to/config"}
|
expected_env = {"KAYOBE_CONFIG_PATH": "/path/to/config",
|
||||||
mock_run.assert_called_once_with(expected_cmd, quiet=False,
|
"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||||
env=expected_env)
|
expected_calls = [
|
||||||
|
mock.call(["which", "kayobe-vault-password-helper"],
|
||||||
|
check_output=True),
|
||||||
|
mock.call(expected_cmd, quiet=False, env=expected_env)
|
||||||
|
]
|
||||||
|
self.assertEqual(expected_calls, mock_run.mock_calls)
|
||||||
mock_vars.assert_called_once_with("/path/to/config")
|
mock_vars.assert_called_once_with("/path/to/config")
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.object(ansible, "_read_vault_password_file")
|
@mock.patch.object(vault, "update_environment")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
def test_run_playbooks_vault_password_file(self, mock_update,
|
||||||
def test_run_playbooks_vault_password_file(self, mock_read, mock_validate,
|
mock_validate,
|
||||||
mock_vars, mock_run):
|
mock_vars, mock_run):
|
||||||
mock_vars.return_value = []
|
mock_vars.return_value = []
|
||||||
mock_read.return_value = "test-pass"
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
ansible.add_args(parser)
|
ansible.add_args(parser)
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
@ -168,10 +173,10 @@ class TestCase(unittest.TestCase):
|
|||||||
"--inventory", "/etc/kayobe/inventory",
|
"--inventory", "/etc/kayobe/inventory",
|
||||||
"playbook1.yml",
|
"playbook1.yml",
|
||||||
]
|
]
|
||||||
expected_env = {"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
expected_env = {"KAYOBE_CONFIG_PATH": "/etc/kayobe"}
|
||||||
"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
|
||||||
mock_run.assert_called_once_with(expected_cmd, quiet=False,
|
mock_run.assert_called_once_with(expected_cmd, quiet=False,
|
||||||
env=expected_env)
|
env=expected_env)
|
||||||
|
mock_update.assert_called_once_with(mock.ANY, expected_env)
|
||||||
|
|
||||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"},
|
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"},
|
||||||
clear=True)
|
clear=True)
|
||||||
@ -204,7 +209,6 @@ class TestCase(unittest.TestCase):
|
|||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
|
||||||
def test_run_playbooks_vault_ask_and_file(self, mock_validate, mock_vars,
|
def test_run_playbooks_vault_ask_and_file(self, mock_validate, mock_vars,
|
||||||
mock_run):
|
mock_run):
|
||||||
mock_vars.return_value = []
|
mock_vars.return_value = []
|
||||||
@ -220,7 +224,6 @@ class TestCase(unittest.TestCase):
|
|||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
|
||||||
def test_run_playbooks_func_args(self, mock_validate, mock_vars, mock_run):
|
def test_run_playbooks_func_args(self, mock_validate, mock_vars, mock_run):
|
||||||
mock_vars.return_value = ["/etc/kayobe/vars-file1.yml",
|
mock_vars.return_value = ["/etc/kayobe/vars-file1.yml",
|
||||||
"/etc/kayobe/vars-file2.yaml"]
|
"/etc/kayobe/vars-file2.yaml"]
|
||||||
@ -263,7 +266,6 @@ class TestCase(unittest.TestCase):
|
|||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(ansible, "_get_vars_files")
|
@mock.patch.object(ansible, "_get_vars_files")
|
||||||
@mock.patch.object(ansible, "_validate_args")
|
@mock.patch.object(ansible, "_validate_args")
|
||||||
@mock.patch.dict(os.environ, clear=True)
|
|
||||||
def test_run_playbooks_failure(self, mock_validate, mock_vars, mock_run):
|
def test_run_playbooks_failure(self, mock_validate, mock_vars, mock_run):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
ansible.add_args(parser)
|
ansible.add_args(parser)
|
||||||
@ -387,10 +389,3 @@ class TestCase(unittest.TestCase):
|
|||||||
mock_is_readable.assert_called_once_with(
|
mock_is_readable.assert_called_once_with(
|
||||||
"/etc/kayobe/ansible/requirements.yml")
|
"/etc/kayobe/ansible/requirements.yml")
|
||||||
mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/roles")
|
mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/roles")
|
||||||
|
|
||||||
@mock.patch.object(utils, 'read_file')
|
|
||||||
def test__read_vault_password_file(self, mock_read):
|
|
||||||
mock_read.return_value = "test-pass\n"
|
|
||||||
result = ansible._read_vault_password_file("/path/to/file")
|
|
||||||
self.assertEqual("test-pass", result)
|
|
||||||
mock_read.assert_called_once_with("/path/to/file")
|
|
||||||
|
@ -25,6 +25,7 @@ from kayobe import vault
|
|||||||
|
|
||||||
|
|
||||||
@mock.patch.object(os, "getcwd", new=lambda: "/path/to/cwd")
|
@mock.patch.object(os, "getcwd", new=lambda: "/path/to/cwd")
|
||||||
|
@mock.patch.dict(os.environ, clear=True)
|
||||||
class TestCase(unittest.TestCase):
|
class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@ -41,7 +42,8 @@ class TestCase(unittest.TestCase):
|
|||||||
"--inventory", "/etc/kolla/inventory/overcloud",
|
"--inventory", "/etc/kolla/inventory/overcloud",
|
||||||
]
|
]
|
||||||
expected_cmd = " ".join(expected_cmd)
|
expected_cmd = " ".join(expected_cmd)
|
||||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||||
|
env={})
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||||
@ -69,14 +71,17 @@ class TestCase(unittest.TestCase):
|
|||||||
"--tags", "tag1,tag2",
|
"--tags", "tag1,tag2",
|
||||||
]
|
]
|
||||||
expected_cmd = " ".join(expected_cmd)
|
expected_cmd = " ".join(expected_cmd)
|
||||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||||
|
env={})
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||||
def test_run_all_the_long_args(self, mock_validate, mock_run):
|
@mock.patch.object(vault, "_ask_vault_pass")
|
||||||
|
def test_run_all_the_long_args(self, mock_ask, mock_validate, mock_run):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
kolla_ansible.add_args(parser)
|
kolla_ansible.add_args(parser)
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
|
mock_ask.return_value = "test-pass"
|
||||||
args = [
|
args = [
|
||||||
"--ask-vault-pass",
|
"--ask-vault-pass",
|
||||||
"--kolla-config-path", "/path/to/config",
|
"--kolla-config-path", "/path/to/config",
|
||||||
@ -87,10 +92,12 @@ class TestCase(unittest.TestCase):
|
|||||||
"--kolla-tags", "tag1,tag2",
|
"--kolla-tags", "tag1,tag2",
|
||||||
]
|
]
|
||||||
parsed_args = parser.parse_args(args)
|
parsed_args = parser.parse_args(args)
|
||||||
|
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||||
kolla_ansible.run(parsed_args, "command", "overcloud")
|
kolla_ansible.run(parsed_args, "command", "overcloud")
|
||||||
expected_cmd = [
|
expected_cmd = [
|
||||||
".", "/path/to/cwd/venvs/kolla-ansible/bin/activate", "&&",
|
".", "/path/to/cwd/venvs/kolla-ansible/bin/activate", "&&",
|
||||||
"kolla-ansible", "command",
|
"kolla-ansible", "command",
|
||||||
|
"--key", "/path/to/kayobe-vault-password-helper",
|
||||||
"--inventory", "/path/to/inventory",
|
"--inventory", "/path/to/inventory",
|
||||||
"--configdir", "/path/to/config",
|
"--configdir", "/path/to/config",
|
||||||
"--passwords", "/path/to/config/passwords.yml",
|
"--passwords", "/path/to/config/passwords.yml",
|
||||||
@ -100,11 +107,19 @@ class TestCase(unittest.TestCase):
|
|||||||
"--tags", "tag1,tag2",
|
"--tags", "tag1,tag2",
|
||||||
]
|
]
|
||||||
expected_cmd = " ".join(expected_cmd)
|
expected_cmd = " ".join(expected_cmd)
|
||||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
expected_env = {"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||||
|
expected_calls = [
|
||||||
|
mock.call(["which", "kayobe-vault-password-helper"],
|
||||||
|
check_output=True),
|
||||||
|
mock.call(expected_cmd, shell=True, quiet=False, env=expected_env)
|
||||||
|
]
|
||||||
|
self.assertEqual(expected_calls, mock_run.mock_calls)
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||||
def test_run_vault_password_file(self, mock_validate, mock_run):
|
@mock.patch.object(vault, "update_environment")
|
||||||
|
def test_run_vault_password_file(self, mock_update, mock_validate,
|
||||||
|
mock_run):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
kolla_ansible.add_args(parser)
|
kolla_ansible.add_args(parser)
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
@ -120,12 +135,15 @@ class TestCase(unittest.TestCase):
|
|||||||
"--inventory", "/etc/kolla/inventory/overcloud",
|
"--inventory", "/etc/kolla/inventory/overcloud",
|
||||||
]
|
]
|
||||||
expected_cmd = " ".join(expected_cmd)
|
expected_cmd = " ".join(expected_cmd)
|
||||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||||
|
env={})
|
||||||
|
mock_update.assert_called_once_with(mock.ANY, {})
|
||||||
|
|
||||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||||
def test_run_vault_password_helper(self, mock_vars, mock_run):
|
@mock.patch.object(vault, "update_environment")
|
||||||
|
def test_run_vault_password_helper(self, mock_update, mock_vars, mock_run):
|
||||||
mock_vars.return_value = []
|
mock_vars.return_value = []
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||||
@ -143,7 +161,10 @@ class TestCase(unittest.TestCase):
|
|||||||
"--inventory", "/etc/kolla/inventory/overcloud",
|
"--inventory", "/etc/kolla/inventory/overcloud",
|
||||||
]
|
]
|
||||||
expected_cmd = " ".join(expected_cmd)
|
expected_cmd = " ".join(expected_cmd)
|
||||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
expected_env = {"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||||
|
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||||
|
env=expected_env)
|
||||||
|
mock_update.assert_called_once_with(mock.ANY, expected_env)
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||||
@ -174,7 +195,8 @@ class TestCase(unittest.TestCase):
|
|||||||
"--arg1", "--arg2",
|
"--arg1", "--arg2",
|
||||||
]
|
]
|
||||||
expected_cmd = " ".join(expected_cmd)
|
expected_cmd = " ".join(expected_cmd)
|
||||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||||
|
env={})
|
||||||
|
|
||||||
@mock.patch.object(utils, "run_command")
|
@mock.patch.object(utils, "run_command")
|
||||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||||
|
104
kayobe/tests/unit/test_vault.py
Normal file
104
kayobe/tests/unit/test_vault.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# Copyright (c) 2018 StackHPC Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from kayobe import utils
|
||||||
|
from kayobe import vault
|
||||||
|
|
||||||
|
|
||||||
|
class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_validate_args_ok(self):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
parsed_args = parser.parse_args([])
|
||||||
|
vault.validate_args(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||||
|
def test_validate_args_env(self):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
parsed_args = parser.parse_args([])
|
||||||
|
vault.validate_args(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||||
|
def test_validate_args_ask_vault_pass(self):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
parsed_args = parser.parse_args(["--ask-vault-pass"])
|
||||||
|
self.assertRaises(SystemExit, vault.validate_args, parsed_args)
|
||||||
|
|
||||||
|
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||||
|
def test_validate_args_vault_password_file(self):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
parsed_args = parser.parse_args(["--vault-password-file",
|
||||||
|
"/path/to/file"])
|
||||||
|
self.assertRaises(SystemExit, vault.validate_args, parsed_args)
|
||||||
|
|
||||||
|
@mock.patch.object(vault.getpass, 'getpass')
|
||||||
|
def test__ask_vault_pass(self, mock_getpass):
|
||||||
|
mock_getpass.return_value = 'test-pass'
|
||||||
|
|
||||||
|
# Call twice to verify that the user is only prompted once.
|
||||||
|
result = vault._ask_vault_pass()
|
||||||
|
self.assertEqual('test-pass', result)
|
||||||
|
mock_getpass.assert_called_once_with("Vault password: ")
|
||||||
|
|
||||||
|
result = vault._ask_vault_pass()
|
||||||
|
self.assertEqual('test-pass', result)
|
||||||
|
mock_getpass.assert_called_once_with("Vault password: ")
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'read_file')
|
||||||
|
def test__read_vault_password_file(self, mock_read):
|
||||||
|
mock_read.return_value = "test-pass\n"
|
||||||
|
result = vault._read_vault_password_file("/path/to/file")
|
||||||
|
self.assertEqual("test-pass", result)
|
||||||
|
mock_read.assert_called_once_with("/path/to/file")
|
||||||
|
|
||||||
|
def test_update_environment_no_vault(self):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
parsed_args = parser.parse_args([])
|
||||||
|
env = {}
|
||||||
|
vault.update_environment(parsed_args, env)
|
||||||
|
self.assertEqual({}, env)
|
||||||
|
|
||||||
|
@mock.patch.object(vault, '_ask_vault_pass')
|
||||||
|
def test_update_environment_prompt(self, mock_ask):
|
||||||
|
mock_ask.return_value = "test-pass"
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
parsed_args = parser.parse_args(["--ask-vault-pass"])
|
||||||
|
env = {}
|
||||||
|
vault.update_environment(parsed_args, env)
|
||||||
|
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
||||||
|
mock_ask.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch.object(vault, '_read_vault_password_file')
|
||||||
|
def test_update_environment_file(self, mock_read):
|
||||||
|
mock_read.return_value = "test-pass"
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
vault.add_args(parser)
|
||||||
|
args = ["--vault-password-file", "/path/to/file"]
|
||||||
|
parsed_args = parser.parse_args(args)
|
||||||
|
env = {}
|
||||||
|
vault.update_environment(parsed_args, env)
|
||||||
|
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
||||||
|
mock_read.assert_called_once_with("/path/to/file")
|
101
kayobe/vault.py
101
kayobe/vault.py
@ -12,15 +12,30 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from kayobe import utils
|
from kayobe import utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_vault_password_helper():
|
||||||
|
"""Return the path to the kayobe-vault-password-helper executable."""
|
||||||
|
cmd = ["which", "kayobe-vault-password-helper"]
|
||||||
|
try:
|
||||||
|
output = utils.run_command(cmd, check_output=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
return output.strip()
|
||||||
|
|
||||||
|
|
||||||
def _get_default_vault_password_file():
|
def _get_default_vault_password_file():
|
||||||
"""Return the default value for the vault password file argument.
|
"""Return the default value for the vault password file argument.
|
||||||
|
|
||||||
@ -29,12 +44,7 @@ def _get_default_vault_password_file():
|
|||||||
"""
|
"""
|
||||||
if not os.getenv(VAULT_PASSWORD_ENV):
|
if not os.getenv(VAULT_PASSWORD_ENV):
|
||||||
return None
|
return None
|
||||||
cmd = ["which", "kayobe-vault-password-helper"]
|
return _get_vault_password_helper()
|
||||||
try:
|
|
||||||
output = utils.run_command(cmd, check_output=True)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return None
|
|
||||||
return output.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def add_args(parser):
|
def add_args(parser):
|
||||||
@ -48,11 +58,82 @@ def add_args(parser):
|
|||||||
help="vault password file")
|
help="vault password file")
|
||||||
|
|
||||||
|
|
||||||
def build_args(parsed_args):
|
def build_args(parsed_args, password_file_arg_name):
|
||||||
"""Build a list of command line arguments for use with ansible-playbook."""
|
"""Build a list of command line arguments for use with ansible-playbook."""
|
||||||
cmd = []
|
vault_password_file = None
|
||||||
if parsed_args.ask_vault_pass:
|
if parsed_args.ask_vault_pass:
|
||||||
cmd += ["--ask-vault-pass"]
|
vault_password_file = _get_vault_password_helper()
|
||||||
elif parsed_args.vault_password_file:
|
elif parsed_args.vault_password_file:
|
||||||
cmd += ["--vault-password-file", parsed_args.vault_password_file]
|
vault_password_file = parsed_args.vault_password_file
|
||||||
|
|
||||||
|
cmd = []
|
||||||
|
if vault_password_file:
|
||||||
|
cmd += [password_file_arg_name, vault_password_file]
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def validate_args(parsed_args):
|
||||||
|
"""Validate command line arguments."""
|
||||||
|
# Ensure that a password prompt or file has not been requested if the
|
||||||
|
# password environment variable is set.
|
||||||
|
if VAULT_PASSWORD_ENV not in os.environ:
|
||||||
|
return
|
||||||
|
|
||||||
|
helper = _get_vault_password_helper()
|
||||||
|
invalid_arg = None
|
||||||
|
if parsed_args.ask_vault_pass:
|
||||||
|
invalid_arg = "--ask-vault-pass"
|
||||||
|
elif parsed_args.vault_password_file != helper:
|
||||||
|
invalid_arg = "--vault-password-file"
|
||||||
|
|
||||||
|
if invalid_arg:
|
||||||
|
LOG.error("Cannot specify %s when $%s is specified" %
|
||||||
|
(invalid_arg, VAULT_PASSWORD_ENV))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _ask_vault_pass():
|
||||||
|
"""Prompt the user for a Vault password.
|
||||||
|
|
||||||
|
The first time this function is called, the user is prompted for a
|
||||||
|
password. To avoid prompting the user multiple times per invocation of
|
||||||
|
kayobe, we cache the password and return it without prompting on subsequent
|
||||||
|
calls.
|
||||||
|
|
||||||
|
:return: The password entered by the user.
|
||||||
|
"""
|
||||||
|
if not hasattr(_ask_vault_pass, "password"):
|
||||||
|
password = getpass.getpass("Vault password: ")
|
||||||
|
setattr(_ask_vault_pass, "password", password)
|
||||||
|
return getattr(_ask_vault_pass, "password")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_vault_password_file(vault_password_file):
|
||||||
|
"""Return the password from a vault password file."""
|
||||||
|
vault_password = utils.read_file(vault_password_file)
|
||||||
|
vault_password = vault_password.strip()
|
||||||
|
return vault_password
|
||||||
|
|
||||||
|
|
||||||
|
def update_environment(parsed_args, env):
|
||||||
|
"""Update environment variables with the vault password if necessary.
|
||||||
|
|
||||||
|
:param parsed_args: Parsed command line arguments.
|
||||||
|
:params env: Dict of environment variables to update.
|
||||||
|
"""
|
||||||
|
# If the Vault password has been specified via --vault-password-file, or a
|
||||||
|
# prompt has been requested via --ask-vault-pass, ensure the environment
|
||||||
|
# variable is set, so that it can be referenced by playbooks to generate
|
||||||
|
# the kolla-ansible passwords.yml file.
|
||||||
|
if VAULT_PASSWORD_ENV in env:
|
||||||
|
return
|
||||||
|
|
||||||
|
vault_password = None
|
||||||
|
if parsed_args.ask_vault_pass:
|
||||||
|
vault_password = _ask_vault_pass()
|
||||||
|
elif parsed_args.vault_password_file:
|
||||||
|
vault_password = _read_vault_password_file(
|
||||||
|
parsed_args.vault_password_file)
|
||||||
|
|
||||||
|
if vault_password is not None:
|
||||||
|
env[VAULT_PASSWORD_ENV] = vault_password
|
||||||
|
7
releasenotes/notes/ask-vault-pass-b6ced0220384dde1.yaml
Normal file
7
releasenotes/notes/ask-vault-pass-b6ced0220384dde1.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixes an issue with the ``--ask-vault-pass`` argument, where Kayobe would
|
||||||
|
fail to generate the Kolla Ansible ``passwords.yml`` file. Also ensures
|
||||||
|
that the user is only prompted for the password once per execution of
|
||||||
|
kayobe.
|
Loading…
Reference in New Issue
Block a user