From 9a52dca199b5864d270440ecc67e6309b711b169 Mon Sep 17 00:00:00 2001 From: Scott Hussey Date: Wed, 29 Aug 2018 08:09:46 -0500 Subject: [PATCH] Bootaction cleanup - Clean up the docs and code around bootactions to support the baclient work - Update the Jinja2 environment in the bootaction rendering - Refactor authentication of bootaction signal API calls Change-Id: Ic64f0c7ee09a487be750188953013f1ed3cd99cb --- docs/source/API.rst | 3 ++ docs/source/bootaction.rst | 4 ++- images/drydock/Dockerfile | 2 ++ .../drydock_provisioner/control/bootaction.py | 2 ++ .../drydock_provisioner/objects/bootaction.py | 21 +++++++++----- python/tests/postgres/start_postgres.sh | 2 +- .../unit/test_bootaction_asset_render.py | 28 +++++++++++++++++-- .../tests/unit/test_bootaction_tarbuilder.py | 4 ++- .../tests/yaml_samples/deckhand_fullsite.yaml | 6 ++-- 9 files changed, 58 insertions(+), 14 deletions(-) diff --git a/docs/source/API.rst b/docs/source/API.rst index a244a37f..8687164c 100644 --- a/docs/source/API.rst +++ b/docs/source/API.rst @@ -112,6 +112,9 @@ object in details can be extended with additional fields as needed. Once a POST containing the ``status`` field is made to a bootaction-id, that bootaction-id can no longer be updated with status changes nor additional detailed status messages. +Each request made must contain the ``X-Bootaction-Key`` header with the correct hex +key for ``bootaction-id``. + validatedesign API ------------------ diff --git a/docs/source/bootaction.rst b/docs/source/bootaction.rst index d7b211ce..97c168fe 100644 --- a/docs/source/bootaction.rst +++ b/docs/source/bootaction.rst @@ -111,9 +111,11 @@ template - node.network.[network_name].cidr - CIDR of [network_name] - node.network.[network_name].dns_suffix - DNS suffix of [network_name] - node.hostname - Hostname of the node + - node.domain - DNS Domain of the primary network on the node - node.tags - Sequence of tags assigned to this node - node.labels - Key, value pairs of both explicit and dynamic labels for this node - - action.key - A key that uniquely identifies this boot action on this node. Can be used for signaling boot action result. + - action.action_id - A ULID that uniquely identifies this boot action on this node. Can be used for signaling boot action result. + - action.action_key - A random key in hex that authenticates API calls for signaling boot action result. - action.report_url - The URL that can be POSTed to for reporting boot action result. - action.design_ref - The design reference for the deployment that initiated the bootaction diff --git a/images/drydock/Dockerfile b/images/drydock/Dockerfile index eebefba2..326c840f 100644 --- a/images/drydock/Dockerfile +++ b/images/drydock/Dockerfile @@ -43,6 +43,8 @@ RUN pip3 install \ --no-cache-dir \ -r /tmp/drydock/requirements-lock.txt +COPY ./alembic /tmp/drydock/alembic +COPY ./alembic.ini /tmp/drydock/alembic.ini COPY ./python /tmp/drydock/python COPY ${BUILD_DIR}/baclient /tmp/drydock/python/drydock_provisioner/assets/baclient diff --git a/python/drydock_provisioner/control/bootaction.py b/python/drydock_provisioner/control/bootaction.py index ee6faa8c..d4ee7339 100644 --- a/python/drydock_provisioner/control/bootaction.py +++ b/python/drydock_provisioner/control/bootaction.py @@ -176,11 +176,13 @@ class BootactionAssetsResource(StatefulResource): if hostname in ba.target_nodes: ba_status = ba_status_list.get(ba.name, None) action_id = ba_status.get('action_id') + action_key = ba_status.get('identity_key') assets.extend( ba.render_assets( hostname, site_design, action_id, + action_key, task.design_ref, type_filter=asset_type_filter)) diff --git a/python/drydock_provisioner/objects/bootaction.py b/python/drydock_provisioner/objects/bootaction.py index f21192d4..b55a962e 100644 --- a/python/drydock_provisioner/objects/bootaction.py +++ b/python/drydock_provisioner/objects/bootaction.py @@ -64,6 +64,7 @@ class BootAction(base.DrydockPersistentObject, base.DrydockObject): nodename, site_design, action_id, + action_key, design_ref, type_filter=None): """Render all of the assets in this bootaction. @@ -77,6 +78,8 @@ class BootAction(base.DrydockPersistentObject, base.DrydockObject): :param site_design: a objects.SiteDesign instance holding the design sets :param action_id: a 128-bit ULID action_id of the boot action the assets are part of + :param action_key: a 256-bit random key to authenticate API calls + for updating the status of this bootaction :param design_ref: the design ref this boot action was initiated under :param type_filter: optional filter of the types of assets to render """ @@ -84,7 +87,7 @@ class BootAction(base.DrydockPersistentObject, base.DrydockObject): for a in self.asset_list: if type_filter is None or (type_filter is not None and a.type == type_filter): - a.render(nodename, site_design, action_id, design_ref) + a.render(nodename, site_design, action_id, action_key, design_ref) assets.append(a) return assets @@ -140,7 +143,7 @@ class BootActionAsset(base.DrydockObject): super().__init__(package_list=package_list, permissions=mode, **kwargs) self.rendered_bytes = None - def render(self, nodename, site_design, action_id, design_ref): + def render(self, nodename, site_design, action_id, action_key, design_ref): """Render this asset into a base64 encoded string. The ``nodename`` and ``action_id`` will be used to construct @@ -149,10 +152,11 @@ class BootActionAsset(base.DrydockObject): :param nodename: the name of the node where the asset will be deployed :param site_design: instance of objects.SiteDesign :param action_id: a 128-bit ULID boot action id + :param action_key: a 256-bit random key for API auth :param design_ref: The design ref this bootaction was initiated under """ tpl_ctx = self._get_template_context(nodename, site_design, action_id, - design_ref) + action_key, design_ref) if self.location is not None: rendered_location = self.execute_pipeline( @@ -207,27 +211,30 @@ class BootActionAsset(base.DrydockObject): return package_list def _get_template_context(self, nodename, site_design, action_id, - design_ref): + action_key, design_ref): """Create a context to be used for template rendering. :param nodename: The name of the node for the bootaction :param site_design: The full site design :param action_id: the ULID assigned to the boot action using this context + :param action_key: a 256 bit random key for API auth :param design_ref: The design reference representing ``site_design`` """ return dict( node=self._get_node_context(nodename, site_design), - action=self._get_action_context(action_id, design_ref)) + action=self._get_action_context(action_id, action_key, design_ref)) - def _get_action_context(self, action_id, design_ref): + def _get_action_context(self, action_id, action_key, design_ref): """Create the action-specific context items for template rendering. :param action_id: ULID of this boot action + :param action_key: random key of this boot action :param design_ref: Design reference representing the site design """ return dict( - key=ulid2.ulid_to_base32(action_id), + action_id=ulid2.ulid_to_base32(action_id), + action_key=action_key.hex(), report_url=config.config_mgr.conf.bootactions.report_url, design_ref=design_ref) diff --git a/python/tests/postgres/start_postgres.sh b/python/tests/postgres/start_postgres.sh index 67298f42..80839686 100755 --- a/python/tests/postgres/start_postgres.sh +++ b/python/tests/postgres/start_postgres.sh @@ -22,4 +22,4 @@ docker run --rm --net host postgres:9.5 psql -h localhost -c "create database dr export DRYDOCK_DB_URL="postgresql+psycopg2://drydock:drydock@localhost:5432/drydock" -sudo docker run --rm -t --net=host -e DRYDOCK_DB_URL="$DRYDOCK_DB_URL" --entrypoint /usr/local/bin/alembic $IMAGE upgrade head +sudo docker run --rm -t --net=host -e DRYDOCK_DB_URL="$DRYDOCK_DB_URL" -w /tmp/drydock --entrypoint /usr/local/bin/alembic $IMAGE upgrade head diff --git a/python/tests/unit/test_bootaction_asset_render.py b/python/tests/unit/test_bootaction_asset_render.py index 02de7f6c..eb378f5a 100644 --- a/python/tests/unit/test_bootaction_asset_render.py +++ b/python/tests/unit/test_bootaction_asset_render.py @@ -14,6 +14,7 @@ """Test that boot action assets are rendered correctly.""" import ulid2 +import os from drydock_provisioner.statemgmt.state import DrydockState @@ -32,8 +33,9 @@ class TestBootactionRenderAction(object): ba = design_data.get_bootaction('helloworld') action_id = ulid2.generate_binary_ulid() + action_key = os.urandom(32) assets = ba.render_assets('compute01', design_data, action_id, - design_ref) + action_key, design_ref) assert 'compute01' in assets[0].rendered_bytes.decode('utf-8') @@ -50,12 +52,34 @@ class TestBootactionRenderAction(object): ba = design_data.get_bootaction('helloworld') action_id = ulid2.generate_binary_ulid() + action_key = os.urandom(32) assets = ba.render_assets('compute01', design_data, action_id, - design_ref) + action_key, design_ref) assert 'deckhand_fullsite.yaml' in assets[2].rendered_bytes.decode( 'utf-8') + def test_bootaction_render_key(self, input_files, deckhand_ingester, + setup): + """Test that a bootaction can render the correct action_id and + action_key needed by the signalling API.""" + input_file = input_files.join("deckhand_fullsite.yaml") + + design_state = DrydockState() + design_ref = "file://%s" % str(input_file) + + design_status, design_data = deckhand_ingester.ingest_data( + design_state=design_state, design_ref=design_ref) + + ba = design_data.get_bootaction('helloworld') + action_id = ulid2.generate_binary_ulid() + action_key = os.urandom(32) + assets = ba.render_assets('compute01', design_data, action_id, + action_key, design_ref) + + assert action_key.hex() in assets[2].rendered_bytes.decode('utf-8') + assert ulid2.ulid_to_base32(action_id) in assets[2].rendered_bytes.decode('utf-8') + def test_bootaction_network_context(self, input_files, deckhand_orchestrator, setup): """Test that a boot action creates proper network context.""" diff --git a/python/tests/unit/test_bootaction_tarbuilder.py b/python/tests/unit/test_bootaction_tarbuilder.py index 0649cb4c..819da6ce 100644 --- a/python/tests/unit/test_bootaction_tarbuilder.py +++ b/python/tests/unit/test_bootaction_tarbuilder.py @@ -16,6 +16,7 @@ import ulid2 import tarfile import io +import os import drydock_provisioner.objects as objects from drydock_provisioner.statemgmt.state import DrydockState @@ -39,8 +40,9 @@ class TestClass(object): ba = design_data.get_bootaction('helloworld') action_id = ulid2.generate_binary_ulid() + action_key = os.urandom(32) assets = ba.render_assets(target_host, design_data, action_id, - design_ref) + action_key, design_ref) assert len(assets) > 0 diff --git a/python/tests/yaml_samples/deckhand_fullsite.yaml b/python/tests/yaml_samples/deckhand_fullsite.yaml index 62fb6dad..e259a462 100644 --- a/python/tests/yaml_samples/deckhand_fullsite.yaml +++ b/python/tests/yaml_samples/deckhand_fullsite.yaml @@ -541,9 +541,11 @@ data: - path: /var/tmp/designref.sh type: file permissions: '500' - data: e3sgYWN0aW9uLmRlc2lnbl9yZWYgfX0K + data: | + {{ action.design_ref }} + {{ action.action_id }} + {{ action.action_key }} data_pipeline: - - base64_decode - utf8_decode - template ---