From d59c2f85704e0cc5b2f6ba6eeb18f7b0fdfe4add Mon Sep 17 00:00:00 2001 From: Dermot Bradley Date: Sat, 22 Jul 2023 03:24:11 +0100 Subject: [PATCH] cc_ssh_import_id: add Alpine support and add doas support Add Alpine to distros supported by this module. If sudo is not available then try and use doas as an alternative if it is available. --- cloudinit/config/cc_ssh_import_id.py | 27 +++++++--- .../unittests/config/test_cc_ssh_import_id.py | 49 +++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/cloudinit/config/cc_ssh_import_id.py b/cloudinit/config/cc_ssh_import_id.py index 7b6272ccaac..a4ca1b981f7 100644 --- a/cloudinit/config/cc_ssh_import_id.py +++ b/cloudinit/config/cc_ssh_import_id.py @@ -19,7 +19,7 @@ from cloudinit.settings import PER_INSTANCE # https://launchpad.net/ssh-import-id -distros = ["ubuntu", "debian", "cos"] +distros = ["alpine", "cos", "debian", "ubuntu"] SSH_IMPORT_ID_BINARY = "ssh-import-id" MODULE_DESCRIPTION = """\ @@ -152,13 +152,24 @@ def import_ssh_ids(ids, user): # I'm including the `--preserve-env` here as a one-off, but we should # have a better way of setting env earlier in boot and using it later. # Perhaps a 'set_env' module? - cmd = [ - "sudo", - "--preserve-env=https_proxy", - "-Hu", - user, - SSH_IMPORT_ID_BINARY, - ] + ids + if subp.which("sudo"): + cmd = [ + "sudo", + "--preserve-env=https_proxy", + "-Hu", + user, + SSH_IMPORT_ID_BINARY, + ] + ids + elif subp.which("doas"): + cmd = [ + "doas", + "-u", + user, + SSH_IMPORT_ID_BINARY, + ] + ids + else: + LOG.error("Neither sudo nor doas available! Unable to import SSH ids.") + return LOG.debug("Importing SSH ids for user %s.", user) try: diff --git a/tests/unittests/config/test_cc_ssh_import_id.py b/tests/unittests/config/test_cc_ssh_import_id.py index 572094a43e2..2d5608a6464 100644 --- a/tests/unittests/config/test_cc_ssh_import_id.py +++ b/tests/unittests/config/test_cc_ssh_import_id.py @@ -76,3 +76,52 @@ def test_skip_inapplicable_configs(self, m_which, cfg, log, caplog): cloud = get_cloud("ubuntu") cc_ssh_import_id.handle("name", cfg, cloud, []) assert log in caplog.text + + @mock.patch("cloudinit.ssh_util.pwd.getpwnam") + @mock.patch("cloudinit.config.cc_ssh_import_id.subp.subp") + @mock.patch("cloudinit.subp.which") + def test_use_sudo(self, m_which, m_subp, m_getpwnam): + """Check that sudo is available and use that""" + m_which.return_value = "/usr/bin/ssh-import-id" + ids = ["waffle"] + user = "bob" + cc_ssh_import_id.import_ssh_ids(ids, user) + m_subp.assert_called_once_with( + [ + "sudo", + "--preserve-env=https_proxy", + "-Hu", + user, + "ssh-import-id", + ] + + ids, + capture=False, + ) + + @mock.patch("cloudinit.ssh_util.pwd.getpwnam") + @mock.patch("cloudinit.config.cc_ssh_import_id.subp.subp") + @mock.patch("cloudinit.subp.which") + def test_use_doas(self, m_which, m_subp, m_getpwnam): + """Check that doas is available and use that""" + m_which.side_effect = [None, "/usr/bin/doas"] + ids = ["waffle"] + user = "bob" + cc_ssh_import_id.import_ssh_ids(ids, user) + m_subp.assert_called_once_with( + ["doas", "-u", user, "ssh-import-id"] + ids, capture=False + ) + + @mock.patch("cloudinit.ssh_util.pwd.getpwnam") + @mock.patch("cloudinit.config.cc_ssh_import_id.subp.subp") + @mock.patch("cloudinit.subp.which") + def test_use_neither_sudo_nor_doas( + self, m_which, m_subp, m_getpwnam, caplog + ): + """Test when neither sudo nor doas is available""" + m_which.return_value = None + ids = ["waffle"] + user = "bob" + cc_ssh_import_id.import_ssh_ids(ids, user) + assert ( + "Neither sudo nor doas available! Unable to import SSH ids" + ) in caplog.text