From b6b8f43badcf94f107660a2e3e704aae9425745f Mon Sep 17 00:00:00 2001
From: Benjamin Bertrand <benjamin.bertrand@esss.se>
Date: Fri, 27 Jul 2018 19:12:18 +0200
Subject: [PATCH] Add Ansible groups parent - child

---
 app/models.py                                 | 29 +++++++++++++
 ...78283a288a05_add_ansible_group_children.py | 43 +++++++++++++++++++
 tests/functional/test_api.py                  | 13 ++++--
 tests/functional/test_models.py               | 13 ++++++
 4 files changed, 95 insertions(+), 3 deletions(-)
 create mode 100644 migrations/versions/78283a288a05_add_ansible_group_children.py

diff --git a/app/models.py b/app/models.py
index d3745fe..c234a4b 100644
--- a/app/models.py
+++ b/app/models.py
@@ -744,6 +744,24 @@ class DeviceType(db.Model):
         }
 
 
+# Table required for Many-to-Many relationships between Ansible parent and child groups
+ansible_groups_parent_child_table = db.Table(
+    "ansible_groups_parent_child",
+    db.Column(
+        "parent_group_id",
+        db.Integer,
+        db.ForeignKey("ansible_group.id"),
+        primary_key=True,
+    ),
+    db.Column(
+        "child_group_id",
+        db.Integer,
+        db.ForeignKey("ansible_group.id"),
+        primary_key=True,
+    ),
+)
+
+
 # Table required for Many-to-Many relationships between Ansible groups and hosts
 ansible_groups_hosts_table = db.Table(
     "ansible_groups_hosts",
@@ -759,9 +777,19 @@ ansible_groups_hosts_table = db.Table(
 
 class AnsibleGroup(CreatedMixin, db.Model):
     __tablename__ = "ansible_group"
+    # Define id here so that it can be used in the primary and secondary join
+    id = db.Column(db.Integer, primary_key=True)
     name = db.Column(CIText, nullable=False, unique=True)
     vars = db.Column(postgresql.JSONB)
 
+    children = db.relationship(
+        "AnsibleGroup",
+        secondary=ansible_groups_parent_child_table,
+        primaryjoin=id == ansible_groups_parent_child_table.c.parent_group_id,
+        secondaryjoin=id == ansible_groups_parent_child_table.c.child_group_id,
+        backref=db.backref("parents"),
+    )
+
     def __str__(self):
         return str(self.name)
 
@@ -772,6 +800,7 @@ class AnsibleGroup(CreatedMixin, db.Model):
                 "name": self.name,
                 "vars": self.vars,
                 "hosts": [str(host) for host in self.hosts],
+                "children": [str(child) for child in self.children],
             }
         )
         return d
diff --git a/migrations/versions/78283a288a05_add_ansible_group_children.py b/migrations/versions/78283a288a05_add_ansible_group_children.py
new file mode 100644
index 0000000..d83e8d5
--- /dev/null
+++ b/migrations/versions/78283a288a05_add_ansible_group_children.py
@@ -0,0 +1,43 @@
+"""Add Ansible group children
+
+Revision ID: 78283a288a05
+Revises: 924d15deb321
+Create Date: 2018-07-27 17:05:57.105899
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "78283a288a05"
+down_revision = "924d15deb321"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    op.create_table(
+        "ansible_groups_parent_child",
+        sa.Column("parent_group_id", sa.Integer(), nullable=False),
+        sa.Column("child_group_id", sa.Integer(), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["child_group_id"],
+            ["ansible_group.id"],
+            name=op.f("fk_ansible_groups_parent_child_child_group_id_ansible_group"),
+        ),
+        sa.ForeignKeyConstraint(
+            ["parent_group_id"],
+            ["ansible_group.id"],
+            name=op.f("fk_ansible_groups_parent_child_parent_group_id_ansible_group"),
+        ),
+        sa.PrimaryKeyConstraint(
+            "parent_group_id",
+            "child_group_id",
+            name=op.f("pk_ansible_groups_parent_child"),
+        ),
+    )
+
+
+def downgrade():
+    op.drop_table("ansible_groups_parent_child")
diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py
index 9efc9fc..677fc35 100644
--- a/tests/functional/test_api.py
+++ b/tests/functional/test_api.py
@@ -1159,9 +1159,16 @@ def test_create_ansible_group(client, admin_token):
     data = {"name": "mygroup"}
     response = post(client, f"{API_URL}/network/groups", data=data, token=admin_token)
     assert response.status_code == 201
-    assert {"id", "name", "vars", "hosts", "created_at", "updated_at", "user"} == set(
-        response.json.keys()
-    )
+    assert {
+        "id",
+        "name",
+        "vars",
+        "hosts",
+        "children",
+        "created_at",
+        "updated_at",
+        "user",
+    } == set(response.json.keys())
     assert response.json["name"] == data["name"]
 
     # Check that name shall be unique
diff --git a/tests/functional/test_models.py b/tests/functional/test_models.py
index 2c6233b..2a40e1f 100644
--- a/tests/functional/test_models.py
+++ b/tests/functional/test_models.py
@@ -179,6 +179,19 @@ def test_ansible_groups(ansible_group_factory, host_factory):
     assert group2.hosts == [host1]
 
 
+def test_ansible_groups_children(ansible_group_factory, host_factory):
+    group1 = ansible_group_factory()
+    group2 = ansible_group_factory()
+    group3 = ansible_group_factory()
+    group1.children.append(group2)
+    group1.children.append(group3)
+    assert group1.children == [group2, group3]
+    assert group2.parents == [group1]
+    assert group3.parents == [group1]
+    group4 = ansible_group_factory(parents=[group1])
+    assert group1.children == [group2, group3, group4]
+
+
 def test_host_model(model_factory, item_factory, host_factory):
     host1 = host_factory()
     model1 = model_factory(name="EX3400")
-- 
GitLab