Skip to content
This repository has been archived by the owner on Apr 21, 2022. It is now read-only.

Commit

Permalink
Deployment: Support for asking for ssh password, sudo password and ss…
Browse files Browse the repository at this point in the history
…h key path
  • Loading branch information
blackandred committed Jul 26, 2020
1 parent 573cca6 commit de524d2
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 11 deletions.
6 changes: 3 additions & 3 deletions src/harbor/deployment/files/ansible.cfg
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[ssh_connection]
ssh_args = -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s
ssh_args = -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -tt
pipelining = True

[defaults]
transport = ssh
sudo_flags = -H -E
sudo_flags = -H -E -S

[sudo_become_plugin]
flags = -H -E
flags = -H -E -S

2 changes: 1 addition & 1 deletion src/harbor/deployment/files/harbor.inventory.cfg.j2
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% for group_name, nodes in nodes.items() -%}
[{{ group_name }}]
{% for node in nodes -%}
{{ node.host }} ansible_ssh_user={{ node.user }} ansible_ssh_port={{ node.port }} {% if node.password is defined and node.password %}ansible_ssh_pass={{ node.password }}{%- endif %} ansible_sudo_pass={{ node.sudo_password|default(node.password) }} {% if node.private_key is defined and node.private_key -%}ansible_ssh_private_key_file={{ node.private_key }}{%- endif %} ansible_python_interpreter={{ node.python_interpreter | default('/usr/bin/python3') }}
{{ node.host }} ansible_ssh_user={{ node.user }} ansible_ssh_port={{ node.port }} {% if "password" in node and node.password %}ansible_ssh_pass={{ node.password }}{%- endif %} {% if ("password" in node and node.password) or "sudo_password" in node %}ansible_become_pass={{ node.sudo_password|default(node.password) }}{% endif %} {% if node.private_key is defined and node.private_key -%}ansible_ssh_private_key_file={{ node.private_key }}{%- endif %} ansible_python_interpreter={{ node.python_interpreter | default('/usr/bin/python3') }}
{% endfor %}
{% endfor %}
46 changes: 40 additions & 6 deletions src/harbor/tasks/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from rkd.contract import ExecutionContext
from rkd.yaml_parser import YamlFileLoader
from rkd.exception import MissingInputException
from rkd.inputoutput import Wizard
from .base import HarborBaseTask
from ..formatting import development_formatting
from ..exception import MissingDeploymentConfigurationError
Expand Down Expand Up @@ -97,7 +98,30 @@ def _write_synced_version(self, abs_ansible_dir: str):
def role_is_installed_and_configured(self) -> bool:
return os.path.isfile(self.ansible_dir + '/.synced')

def install_and_configure_role(self, force_update: bool = False) -> bool:
def _ask_and_set_var(self, ctx: ExecutionContext, arg_name: str, title: str, attribute: str, secret: bool):
"""Ask user an interactive question, then add answer to the deployment.yml loaded in memory
The variable will be appended to any node, where the variable is empty.
Example: We have 5 servers, 3 without a password. So the password will be applied to 3 servers.
"""

self.get_config()

if not ctx.get_arg(arg_name):
return

for group_name, nodes in self._config['nodes'].items():
node_num = 0

for node in nodes:
node_num += 1
if attribute in self._config['nodes'][group_name][node_num - 1]:
continue

wizard = Wizard(self).ask(title, attribute=attribute, secret=secret)
self._config['nodes'][group_name][node_num - 1][attribute] = wizard.answers[attribute]

def install_and_configure_role(self, ctx: ExecutionContext, force_update: bool = False) -> bool:
"""Install an Ansible role from galaxy, and configure playbook, inventory, all the needed things"""

abs_ansible_dir = os.path.realpath(self.ansible_dir)
Expand All @@ -107,6 +131,12 @@ def install_and_configure_role(self, force_update: bool = False) -> bool:
self._silent_mkdir(abs_ansible_dir)
self._verify_synced_version(abs_ansible_dir)

# optionally ask user and set facts such as passwords, key paths, sudo passwords
# ansible-vault password prompt is handed by ansible-vault itself
self._ask_and_set_var(ctx, '--ask-ssh-pass', 'SSH password', 'password', secret=True)
self._ask_and_set_var(ctx, '--ask-ssh-key-path', 'SSH private key path', 'private_key', secret=False)
self._ask_and_set_var(ctx, '--ask-sudo-pass', 'Sudo password for remote machines', 'sudo_pass', secret=True)

if not self._synchronize_structure_from_template(abs_ansible_dir, only_jinja_templates=True):
self.io().error_msg('Cannot synchronize templates')
return False
Expand Down Expand Up @@ -257,7 +287,7 @@ def _clear_old_vault_temporary_files(self):

@classmethod
def _add_vault_arguments_to_argparse(cls, parser: ArgumentParser):
parser.add_argument('--ask-vault-pass', '-v', help='Ask for vault password interactively')
parser.add_argument('--ask-vault-pass', '-v', help='Ask for vault password interactively', action='store_true')
parser.add_argument('--vault-passwords', '-V', help='Vault passwords separated by "||" eg. 123||456')


Expand Down Expand Up @@ -287,7 +317,7 @@ def run(self, context: ExecutionContext) -> bool:
self._preserve_vault_parameters_for_usage_in_inner_tasks(context)

try:
return self.install_and_configure_role(force_update=True)
return self.install_and_configure_role(context, force_update=True)

except MissingDeploymentConfigurationError as e:
self.io().error_msg(str(e))
Expand All @@ -303,7 +333,7 @@ class DeploymentTask(BaseDeploymentTask):
such as custom playbook, custom role or a custom inventory. The environment variables from .env are considered.
Example usage:
# deploy services matching profile "gateway", use password stored in .vault-apssword for Ansible Vault
# deploy services matching profile "gateway", use password stored in .vault-password for Ansible Vault
harbor :deployment:apply -V .vault-password --profile=gateway
# another example with Vault, multiple passwords, and environment variable usage
Expand Down Expand Up @@ -344,6 +374,10 @@ def configure_argparse(self, parser: ArgumentParser):
parser.add_argument('--branch', '-b', help='Git branch to deploy from', default='master')
parser.add_argument('--profile', help='Harbor profile to filter out services that needs to be deployed',
default='')
parser.add_argument('--ask-ssh-pass', help='Ask for a SSH password', action='store_true')
parser.add_argument('--ask-ssh-key-path', help='Ask for a SSH private key path', action='store_true')
parser.add_argument('--ask-sudo-pass', help='Ask for sudo password', action='store_true')

self._add_vault_arguments_to_argparse(parser)

def run(self, context: ExecutionContext) -> bool:
Expand All @@ -358,11 +392,11 @@ def run(self, context: ExecutionContext) -> bool:
self._preserve_vault_parameters_for_usage_in_inner_tasks(context)

if not self.role_is_installed_and_configured():
self.io().error_msg('Deployment not configured. Use `harbor :deployment:role:update` first')
self.io().error_msg('Deployment not configured. Use `harbor :deployment:files:update` first')
return False

try:
self.install_and_configure_role(force_update=False)
self.install_and_configure_role(context, force_update=False)

except MissingDeploymentConfigurationError as e:
self.io().error_msg(str(e))
Expand Down
2 changes: 1 addition & 1 deletion test/test_deployment_applytask.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_functional_validates_if_structure_exists(self):
'--ask-vault-pass': False},
env={})

self.assertIn('Deployment not configured. Use `harbor :deployment:role:update` first', out)
self.assertIn('Deployment not configured. Use `harbor :deployment:files:update` first', out)
self.assertIn('TASK_EXIT_RESULT=False', out)

def test_functional_passes_structure_validation_after_using_update_command(self):
Expand Down

0 comments on commit de524d2

Please sign in to comment.