Add support for recording controller fqdn
The controller fqdn that ran the playbook is now recorded and can be searched for both in the UI and the CLI. Fixes: https://github.com/ansible-community/ara/issues/193 Change-Id: I53e8d158fc3b6ba7a16582234aaa2542eab5fcdc
This commit is contained in:
parent
a4f21d6e3f
commit
52b201dae2
@ -56,6 +56,7 @@ class LabelFilter(BaseFilter):
|
||||
|
||||
|
||||
class PlaybookFilter(DateFilter):
|
||||
controller = django_filters.CharFilter(field_name="controller", lookup_expr="icontains")
|
||||
name = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
|
||||
path = django_filters.CharFilter(field_name="path", lookup_expr="icontains")
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
|
18
ara/api/migrations/0008_playbook_controller.py
Normal file
18
ara/api/migrations/0008_playbook_controller.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.17 on 2020-12-04 04:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0007_add_expired_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='playbook',
|
||||
name='controller',
|
||||
field=models.CharField(default='localhost', max_length=255),
|
||||
),
|
||||
]
|
@ -102,6 +102,7 @@ class Playbook(Duration):
|
||||
arguments = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
path = models.CharField(max_length=255)
|
||||
labels = models.ManyToManyField(Label)
|
||||
controller = models.CharField(max_length=255, default="localhost")
|
||||
|
||||
def __str__(self):
|
||||
return "<Playbook %s>" % self.id
|
||||
|
@ -43,6 +43,7 @@ class PlaybookFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = models.Playbook
|
||||
|
||||
controller = "localhost"
|
||||
name = "test-playbook"
|
||||
ansible_version = "2.4.0"
|
||||
status = "running"
|
||||
|
@ -32,11 +32,17 @@ class PlaybookTestCase(APITestCase):
|
||||
|
||||
def test_playbook_serializer(self):
|
||||
serializer = serializers.PlaybookSerializer(
|
||||
data={"name": "serializer-playbook", "ansible_version": "2.4.0", "path": "/path/playbook.yml"}
|
||||
data={
|
||||
"controller": "serializer",
|
||||
"name": "serializer-playbook",
|
||||
"ansible_version": "2.4.0",
|
||||
"path": "/path/playbook.yml",
|
||||
}
|
||||
)
|
||||
serializer.is_valid()
|
||||
playbook = serializer.save()
|
||||
playbook.refresh_from_db()
|
||||
self.assertEqual(playbook.controller, "serializer")
|
||||
self.assertEqual(playbook.name, "serializer-playbook")
|
||||
self.assertEqual(playbook.ansible_version, "2.4.0")
|
||||
self.assertEqual(playbook.status, "unknown")
|
||||
@ -125,6 +131,20 @@ class PlaybookTestCase(APITestCase):
|
||||
request = self.client.get("/api/v1/playbooks/%s" % playbook.id)
|
||||
self.assertEqual(playbook.ansible_version, request.data["ansible_version"])
|
||||
|
||||
def test_get_playbook_by_controller(self):
|
||||
playbook = factories.PlaybookFactory(name="playbook1", controller="controller-one")
|
||||
factories.PlaybookFactory(name="playbook2", controller="controller-two")
|
||||
|
||||
# Test exact match
|
||||
request = self.client.get("/api/v1/playbooks?controller=controller-one")
|
||||
self.assertEqual(1, len(request.data["results"]))
|
||||
self.assertEqual(playbook.name, request.data["results"][0]["name"])
|
||||
self.assertEqual(playbook.controller, request.data["results"][0]["controller"])
|
||||
|
||||
# Test partial match
|
||||
request = self.client.get("/api/v1/playbooks?controller=controller")
|
||||
self.assertEqual(len(request.data["results"]), 2)
|
||||
|
||||
def test_get_playbook_by_name(self):
|
||||
playbook = factories.PlaybookFactory(name="playbook1")
|
||||
factories.PlaybookFactory(name="playbook2")
|
||||
|
@ -31,6 +31,12 @@ class PlaybookList(Lister):
|
||||
default=None,
|
||||
help=("List playbooks matching the provided label"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--controller",
|
||||
metavar="<controller>",
|
||||
default=None,
|
||||
help=("List playbooks that ran from the provided controller (full or partial)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--name",
|
||||
metavar="<name>",
|
||||
@ -88,6 +94,9 @@ class PlaybookList(Lister):
|
||||
if args.label is not None:
|
||||
query["label"] = args.label
|
||||
|
||||
if args.controller is not None:
|
||||
query["controller"] = args.controller
|
||||
|
||||
if args.name is not None:
|
||||
query["name"] = args.name
|
||||
|
||||
@ -118,6 +127,7 @@ class PlaybookList(Lister):
|
||||
columns = (
|
||||
"id",
|
||||
"status",
|
||||
"controller",
|
||||
"name",
|
||||
"path",
|
||||
"plays",
|
||||
@ -133,6 +143,7 @@ class PlaybookList(Lister):
|
||||
columns = (
|
||||
"id",
|
||||
"status",
|
||||
"controller",
|
||||
"path",
|
||||
"tasks",
|
||||
"results",
|
||||
@ -191,6 +202,7 @@ class PlaybookShow(ShowOne):
|
||||
columns = (
|
||||
"id",
|
||||
"report",
|
||||
"controller",
|
||||
"status",
|
||||
"path",
|
||||
"started",
|
||||
@ -262,6 +274,12 @@ class PlaybookPrune(Command):
|
||||
default=None,
|
||||
help=("Only delete playbooks matching the provided name (full or partial)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--controller",
|
||||
metavar="<controller>",
|
||||
default=None,
|
||||
help=("Only delete playbooks that ran from the provided controller (full or partial)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--path",
|
||||
metavar="<path>",
|
||||
@ -316,6 +334,9 @@ class PlaybookPrune(Command):
|
||||
if args.label is not None:
|
||||
query["label"] = args.label
|
||||
|
||||
if args.controller is not None:
|
||||
query["controller"] = args.controller
|
||||
|
||||
if args.name is not None:
|
||||
query["name"] = args.name
|
||||
|
||||
|
@ -21,6 +21,7 @@ import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from ansible import __version__ as ansible_version
|
||||
@ -292,6 +293,7 @@ class CallbackModule(CallbackBase):
|
||||
arguments=cli_options,
|
||||
status="running",
|
||||
path=path,
|
||||
controller=socket.getfqdn(),
|
||||
started=datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
)
|
||||
|
||||
|
@ -21,6 +21,7 @@ from ara.api import models
|
||||
|
||||
|
||||
class PlaybookSearchForm(forms.Form):
|
||||
controller = forms.CharField(label="Playbook controller", max_length=255, required=False)
|
||||
name = forms.CharField(label="Playbook name", max_length=255, required=False)
|
||||
path = forms.CharField(label="Playbook path", max_length=255, required=False)
|
||||
status = forms.MultipleChoiceField(
|
||||
|
@ -7,6 +7,14 @@
|
||||
<div class="pf-l-flex">
|
||||
<form novalidate action="/" method="get" class="pf-c-form">
|
||||
<div class="pf-c-form__group pf-m-inline">
|
||||
<div class="pf-l-flex__item pf-m-flex-1">
|
||||
<label class="pf-c-form__label" for="controller">
|
||||
<span class="pf-c-form__label-text">Controller</span>
|
||||
</label>
|
||||
<div class="pf-c-form__horizontal-group">
|
||||
<input class="pf-c-form-control" type="text" id="controller" name="controller" value="{% if search_form.controller.value is not null %}{{ search_form.controller.value }}{% endif %}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-l-flex__item pf-m-flex-1">
|
||||
<label class="pf-c-form__label" for="name">
|
||||
<span class="pf-c-form__label-text">Name</span>
|
||||
@ -102,6 +110,7 @@
|
||||
{% include "partials/sort_by_duration.html" %}
|
||||
</th>
|
||||
<th role="columnheader" scope="col" class="pf-m-fit-content">Ansible version</th>
|
||||
<th role="columnheader" scope="col">Controller</th>
|
||||
<th role="columnheader" scope="col">Name (or path)</th>
|
||||
<th role="columnheader" scope="col">Labels</th>
|
||||
<th role="columnheader" scope="col" class="pf-m-fit-content">Hosts</th>
|
||||
@ -142,6 +151,9 @@
|
||||
<td role="cell" data-label="Ansible version" class="pf-m-fit-content">
|
||||
{{ playbook.ansible_version }}
|
||||
</td>
|
||||
<td role="cell" data-label="Controller" class="pf-m-fit-content">
|
||||
{{ playbook.controller }}
|
||||
</td>
|
||||
<td role="cell" data-label="Name (or path)" class="pf-m-fit-content">
|
||||
{% if static_generation %}
|
||||
<a href="{% if page != "index" %}../{% endif %}playbooks/{{ playbook.id }}.html" title="{{ playbook.path }}">
|
||||
|
@ -22,7 +22,7 @@ class Index(generics.ListAPIView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# TODO: Can we retrieve those fields automatically ?
|
||||
fields = ["order", "name", "started_after", "status", "label"]
|
||||
fields = ["order", "controller", "name", "started_after", "status", "label"]
|
||||
search_query = False
|
||||
for field in fields:
|
||||
if field in request.GET:
|
||||
|
@ -47,6 +47,7 @@
|
||||
ansible_version: "9.0.0.1"
|
||||
started: "{{ ansible_date_time.iso8601_micro }}"
|
||||
status: running
|
||||
controller: localhost
|
||||
labels:
|
||||
- "{{ _get_root.json['version'] }}"
|
||||
- "{{ item.name }}:{{ item.tag }}"
|
||||
|
Loading…
x
Reference in New Issue
Block a user