From 9b00abd786f7e134fd9bc65b39dc4c77b64f2aed Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Fri, 17 May 2019 12:44:13 -0400 Subject: [PATCH] Add support for specifying playbook labels as Ansible variables By declaring the "ara_playbook_labels" list Ansible variable, the callback picks them up and sends them to the API. The API then ensures the label is created and associates it with the playbook. Change-Id: Ibe5c36ce005f0a7ce99b38c57439a6dc2277228a --- ara/api/fields.py | 13 +++++++++++++ ara/api/serializers.py | 15 +++------------ ara/api/tests/tests_playbook.py | 21 +++++++++++++++++++++ ara/plugins/callback/ara_default.py | 6 ++++++ tests/integration/smoke.yaml | 11 +++++++++++ 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/ara/api/fields.py b/ara/api/fields.py index df080f21..20bf43d5 100644 --- a/ara/api/fields.py +++ b/ara/api/fields.py @@ -73,3 +73,16 @@ class FileContentField(serializers.CharField): sha1=sha1, defaults={"sha1": sha1, "contents": zlib.compress(contents)} ) return content_file + + +class CreatableSlugRelatedField(serializers.SlugRelatedField): + """ + A SlugRelatedField that supports get_or_create. + Used for creating or retrieving labels by name. + """ + + def to_internal_value(self, data): + try: + return self.get_queryset().get_or_create(**{self.slug_field: data})[0] + except (TypeError, ValueError): + self.fail("invalid") diff --git a/ara/api/serializers.py b/ara/api/serializers.py index 974fe0d0..13d474d9 100644 --- a/ara/api/serializers.py +++ b/ara/api/serializers.py @@ -351,18 +351,9 @@ class PlaybookSerializer(DurationSerializer): fields = "__all__" arguments = ara_fields.CompressedObjectField(default=ara_fields.EMPTY_DICT) - labels = LabelSerializer(many=True, default=[]) - - def create(self, validated_data): - # First create the playbook without the labels - labels = validated_data.pop("labels") - playbook = models.Playbook.objects.create(**validated_data) - - # Now associate the labels to the playbook - for label in labels: - playbook.labels.add(models.Label.objects.create(**label)) - - return playbook + labels = ara_fields.CreatableSlugRelatedField( + many=True, slug_field="name", queryset=models.Label.objects.all(), required=False + ) class PlaySerializer(DurationSerializer): diff --git a/ara/api/tests/tests_playbook.py b/ara/api/tests/tests_playbook.py index bdb55c14..5038da3e 100644 --- a/ara/api/tests/tests_playbook.py +++ b/ara/api/tests/tests_playbook.py @@ -82,6 +82,18 @@ class PlaybookTestCase(APITestCase): self.assertEqual(1, models.Playbook.objects.count()) self.assertEqual(request.data["status"], "running") + def test_create_playbook_with_labels(self): + self.assertEqual(0, models.Playbook.objects.count()) + labels = ["test-label", "another-test-label"] + request = self.client.post( + "/api/v1/playbooks", + {"ansible_version": "2.4.0", "status": "running", "path": "/path/playbook.yml", "labels": labels}, + ) + self.assertEqual(201, request.status_code) + self.assertEqual(1, models.Playbook.objects.count()) + self.assertEqual(request.data["status"], "running") + self.assertEqual(request.data["labels"], labels) + def test_partial_update_playbook(self): playbook = factories.PlaybookFactory() self.assertNotEqual("completed", playbook.status) @@ -133,4 +145,13 @@ class PlaybookTestCase(APITestCase): request = self.client.get("/api/v1/playbooks/%s" % playbook.id) self.assertEqual(request.data["duration"], datetime.timedelta(0, 3600)) + def test_patch_playbook_labels(self): + playbook = factories.PlaybookFactory() + labels = ["test-label", "another-test-label"] + self.assertNotEqual(playbook.labels, labels) + request = self.client.patch("/api/v1/playbooks/%s" % playbook.id, {"labels": labels}) + self.assertEqual(200, request.status_code) + playbook_updated = models.Playbook.objects.get(id=playbook.id) + self.assertEqual([label.name for label in playbook_updated.labels.all()], labels) + # TODO: Add tests for incrementally updating files diff --git a/ara/plugins/callback/ara_default.py b/ara/plugins/callback/ara_default.py index df56a1dd..25fa8762 100644 --- a/ara/plugins/callback/ara_default.py +++ b/ara/plugins/callback/ara_default.py @@ -183,6 +183,8 @@ class CallbackModule(CallbackBase): play_vars = play._variable_manager.get_vars(play=play)["vars"] if "ara_playbook_name" in play_vars: self._set_playbook_name(name=play_vars["ara_playbook_name"]) + if "ara_playbook_labels" in play_vars: + self._set_playbook_labels(labels=play_vars["ara_playbook_labels"]) # Record all the files involved in the play for path in play._loader._FILE_CACHE.keys(): @@ -281,6 +283,10 @@ class CallbackModule(CallbackBase): if self.playbook["name"] != name: self.playbook = self.client.patch("/api/v1/playbooks/%s" % self.playbook["id"], name=name) + def _set_playbook_labels(self, labels): + if self.playbook["labels"] != labels: + self.playbook = self.client.patch("/api/v1/playbooks/%s" % self.playbook["id"], labels=labels) + def _get_or_create_file(self, path): self.log.debug("Getting or creating file: %s" % path) # Note: The get_or_create is handled through the serializer of the API server. diff --git a/tests/integration/smoke.yaml b/tests/integration/smoke.yaml index 1c278cbb..5fb0fbf0 100644 --- a/tests/integration/smoke.yaml +++ b/tests/integration/smoke.yaml @@ -21,6 +21,9 @@ gather_facts: no vars: ara_playbook_name: Smoke tests + ara_playbook_labels: + - integration tests + - smoke tasks: - name: ARA Integration test debug: @@ -33,6 +36,10 @@ - name: Add a host with non-ascii characters hosts: localhost gather_facts: no + vars: + ara_playbook_labels: + - integration tests + - smoke tasks: - name: Add a host with non-ascii character add_host: @@ -46,6 +53,10 @@ - name: Play with non-ascii characters - ä, ö, ü hosts: höstñämë gather_facts: yes + vars: + ara_playbook_labels: + - integration tests + - smoke tasks: - name: Task with non-ascii characters - ä, ö, ü debug: