From 491ed919d69f40f8f195458540174a1c34785017 Mon Sep 17 00:00:00 2001
From: Mark Goddard <mark@stackhpc.com>
Date: Fri, 3 Aug 2018 15:19:48 +0100
Subject: [PATCH] Add commands to update packages on hosts

Just performs a yum update.

Change-Id: I4cdc23a3e491c5a2f92e8beeb7eaebf2df818df3
Story: 2003305
Task: 24257
---
 ansible/host-package-update.yml               |  16 ++
 doc/source/administration.rst                 |  22 +++
 kayobe/cli/commands.py                        |  50 ++++++
 kayobe/tests/unit/cli/test_commands.py        | 150 ++++++++++++++++++
 .../host-package-update-c5bddba791754646.yaml |   7 +
 setup.cfg                                     |   2 +
 6 files changed, 247 insertions(+)
 create mode 100644 ansible/host-package-update.yml
 create mode 100644 releasenotes/notes/host-package-update-c5bddba791754646.yaml

diff --git a/ansible/host-package-update.yml b/ansible/host-package-update.yml
new file mode 100644
index 00000000..8ae7bd8d
--- /dev/null
+++ b/ansible/host-package-update.yml
@@ -0,0 +1,16 @@
+---
+- name: Update host packages
+  hosts: seed:overcloud
+  vars:
+    # Optionally set this to a list of packages to update. Default behaviour is
+    # to update all packages.
+    host_package_update_packages: "*"
+    host_package_update_security: false
+  tasks:
+    - name: Update host packages
+      yum:
+        name: "{{ host_package_update_packages }}"
+        security: "{{ host_package_update_security | bool }}"
+        state: latest
+      when: ansible_os_family == 'RedHat'
+      become: true
diff --git a/doc/source/administration.rst b/doc/source/administration.rst
index cf0821bb..eca2cb10 100644
--- a/doc/source/administration.rst
+++ b/doc/source/administration.rst
@@ -5,6 +5,28 @@ Administration
 This section describes how to use kayobe to simplify post-deployment
 administrative tasks.
 
+Updating Packages
+=================
+
+It is possible to update packages on the seed and overcloud hosts. To update
+one or more packages::
+
+    (kayobe) $ kayobe seed host package update --packages <package1>,<package2>
+    (kayobe) $ kayobe overcloud host package update --packages <package1>,<package2>
+
+To update all eligible packages, use ``*``, escaping if necessary::
+
+    (kayobe) $ kayobe seed host package update --packages *
+    (kayobe) $ kayobe overcloud host package update --packages *
+
+To only install updates that have been marked security related::
+
+    (kayobe) $ kayobe seed host package update --packages <packages> --security
+    (kayobe) $ kayobe overcloud host package update --packages <packages> --security
+
+Note that these commands do not affect packages installed in containers, only
+those installed on the host.
+
 Reconfiguring Containerised Services
 ====================================
 
diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py
index 49c89c07..1c177c03 100644
--- a/kayobe/cli/commands.py
+++ b/kayobe/cli/commands.py
@@ -425,6 +425,31 @@ class SeedHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin,
         self.run_kayobe_playbooks(parsed_args, playbooks, limit="seed")
 
 
+class SeedHostPackageUpdate(KayobeAnsibleMixin, VaultMixin, Command):
+    """Update packages on the seed host."""
+
+    def get_parser(self, prog_name):
+        parser = super(SeedHostPackageUpdate, self).get_parser(prog_name)
+        group = parser.add_argument_group("Host Package Updates")
+        group.add_argument("--packages", required=True,
+                           help="List of packages to update. Use '*' to "
+                                "update all packages.")
+        group.add_argument("--security", action='store_true',
+                           help="Only install updates that have been marked "
+                                "security related.")
+        return parser
+
+    def take_action(self, parsed_args):
+        self.app.LOG.debug("Updating seed host packages")
+        extra_vars = {
+            "host_package_update_packages": parsed_args.packages,
+            "host_package_update_security": parsed_args.security,
+        }
+        playbooks = _build_playbook_list("host-package-update")
+        self.run_kayobe_playbooks(parsed_args, playbooks, limit="seed",
+                                  extra_vars=extra_vars)
+
+
 class SeedHostUpgrade(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin,
                       Command):
     """Upgrade the seed host services.
@@ -732,6 +757,31 @@ class OvercloudHostConfigure(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin,
         self.run_kayobe_playbooks(parsed_args, playbooks, limit="overcloud")
 
 
+class OvercloudHostPackageUpdate(KayobeAnsibleMixin, VaultMixin, Command):
+    """Update packages on the overcloud hosts."""
+
+    def get_parser(self, prog_name):
+        parser = super(OvercloudHostPackageUpdate, self).get_parser(prog_name)
+        group = parser.add_argument_group("Host Package Updates")
+        group.add_argument("--packages", required=True,
+                           help="List of packages to update. Use '*' to "
+                                "update all packages.")
+        group.add_argument("--security", action='store_true',
+                           help="Only install updates that have been marked "
+                                "security related.")
+        return parser
+
+    def take_action(self, parsed_args):
+        self.app.LOG.debug("Updating overcloud host packages")
+        extra_vars = {
+            "host_package_update_packages": parsed_args.packages,
+            "host_package_update_security": parsed_args.security,
+        }
+        playbooks = _build_playbook_list("host-package-update")
+        self.run_kayobe_playbooks(parsed_args, playbooks, limit="overcloud",
+                                  extra_vars=extra_vars)
+
+
 class OvercloudHostUpgrade(KayobeAnsibleMixin, VaultMixin, Command):
     """Upgrade the overcloud host services.
 
diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py
index 6961fdf4..37595d4a 100644
--- a/kayobe/tests/unit/cli/test_commands.py
+++ b/kayobe/tests/unit/cli/test_commands.py
@@ -314,6 +314,81 @@ class TestCase(unittest.TestCase):
         ]
         self.assertEqual(expected_calls, mock_kolla_run.call_args_list)
 
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_seed_host_package_update_all(self, mock_run):
+        command = commands.SeedHostPackageUpdate(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--packages", "*"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    "ansible/host-package-update.yml",
+                ],
+                limit="seed",
+                extra_vars={
+                    "host_package_update_packages": "*",
+                    "host_package_update_security": False,
+                },
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_seed_host_package_update_list(self, mock_run):
+        command = commands.SeedHostPackageUpdate(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--packages", "p1,p2"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    "ansible/host-package-update.yml",
+                ],
+                limit="seed",
+                extra_vars={
+                    "host_package_update_packages": "p1,p2",
+                    "host_package_update_security": False,
+                },
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_seed_host_package_update_security(self, mock_run):
+        command = commands.SeedHostPackageUpdate(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--packages", "*", "--security"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    "ansible/host-package-update.yml",
+                ],
+                limit="seed",
+                extra_vars={
+                    "host_package_update_packages": "*",
+                    "host_package_update_security": True,
+                },
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
     @mock.patch.object(commands.KayobeAnsibleMixin,
                        "run_kayobe_playbooks")
     def test_seed_host_upgrade(self, mock_run):
@@ -642,6 +717,81 @@ class TestCase(unittest.TestCase):
         ]
         self.assertEqual(expected_calls, mock_kolla_run.call_args_list)
 
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_overcloud_host_package_update_all(self, mock_run):
+        command = commands.OvercloudHostPackageUpdate(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--packages", "*"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    "ansible/host-package-update.yml",
+                ],
+                limit="overcloud",
+                extra_vars={
+                    "host_package_update_packages": "*",
+                    "host_package_update_security": False,
+                },
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_overcloud_host_package_update_list(self, mock_run):
+        command = commands.OvercloudHostPackageUpdate(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--packages", "p1,p2"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    "ansible/host-package-update.yml",
+                ],
+                limit="overcloud",
+                extra_vars={
+                    "host_package_update_packages": "p1,p2",
+                    "host_package_update_security": False,
+                },
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
+    @mock.patch.object(commands.KayobeAnsibleMixin,
+                       "run_kayobe_playbooks")
+    def test_overcloud_host_package_update_security(self, mock_run):
+        command = commands.OvercloudHostPackageUpdate(TestApp(), [])
+        parser = command.get_parser("test")
+        parsed_args = parser.parse_args(["--packages", "*", "--security"])
+
+        result = command.run(parsed_args)
+        self.assertEqual(0, result)
+
+        expected_calls = [
+            mock.call(
+                mock.ANY,
+                [
+                    "ansible/host-package-update.yml",
+                ],
+                limit="overcloud",
+                extra_vars={
+                    "host_package_update_packages": "*",
+                    "host_package_update_security": True,
+                },
+            ),
+        ]
+        self.assertEqual(expected_calls, mock_run.call_args_list)
+
     @mock.patch.object(commands.KayobeAnsibleMixin,
                        "run_kayobe_playbooks")
     def test_overcloud_host_upgrade(self, mock_run):
diff --git a/releasenotes/notes/host-package-update-c5bddba791754646.yaml b/releasenotes/notes/host-package-update-c5bddba791754646.yaml
new file mode 100644
index 00000000..fc1510ea
--- /dev/null
+++ b/releasenotes/notes/host-package-update-c5bddba791754646.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add commands to update packages on seed and overcloud hosts:
+
+    ``kayobe seed host package update --packages <packages>``
+    ``kayobe overcloud host package update --packages <packages>``
diff --git a/setup.cfg b/setup.cfg
index 1cea1e06..6e31ad19 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -45,6 +45,7 @@ kayobe.cli=
     overcloud_deprovision = kayobe.cli.commands:OvercloudDeprovision
     overcloud_hardware_inspect = kayobe.cli.commands:OvercloudHardwareInspect
     overcloud_host_configure = kayobe.cli.commands:OvercloudHostConfigure
+    overcloud_host_package_update = kayobe.cli.commands:OvercloudHostPackageUpdate
     overcloud_host_upgrade = kayobe.cli.commands:OvercloudHostUpgrade
     overcloud_introspection_data_save = kayobe.cli.commands:OvercloudIntrospectionDataSave
     overcloud_inventory_discover = kayobe.cli.commands:OvercloudInventoryDiscover
@@ -61,6 +62,7 @@ kayobe.cli=
     seed_container_image_build = kayobe.cli.commands:SeedContainerImageBuild
     seed_deployment_image_build = kayobe.cli.commands:SeedDeploymentImageBuild
     seed_host_configure = kayobe.cli.commands:SeedHostConfigure
+    seed_host_package_update = kayobe.cli.commands:SeedHostPackageUpdate
     seed_host_upgrade = kayobe.cli.commands:SeedHostUpgrade
     seed_hypervisor_host_configure = kayobe.cli.commands:SeedHypervisorHostConfigure
     seed_hypervisor_host_upgrade = kayobe.cli.commands:SeedHypervisorHostUpgrade
-- 
GitLab