Update model and serializers to 1.0 schema
The proof of concept/prototype was built using a database schema that was closer to what the stable release of ARA used instead of the one that had been worked on already for 1.0. This updates the models and API serializers to reflect that and be more accurate. Change-Id: I963823e84e2d2ef9f6262cd04de8fd31ffc7d5b4
This commit is contained in:
parent
4e6df8ba44
commit
e6a39c9663
|
@ -1,10 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.8 on 2018-02-27 12:25
|
||||
from __future__ import unicode_literals
|
||||
# Generated by Django 2.0.3 on 2018-03-10 16:49
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -18,22 +16,37 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='File',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('content', models.FileField(upload_to='files/%Y/%m/%d/')),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('path', models.CharField(max_length=255)),
|
||||
('is_playbook', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'files',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FileContent',
|
||||
fields=[
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('sha1', models.CharField(max_length=40, unique=True)),
|
||||
('contents', models.BinaryField(max_length=4294967295)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'file_contents',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Host',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField(auto_now_add=True, verbose_name='started')),
|
||||
('ended', models.DateTimeField(auto_now=True, verbose_name='ended')),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('facts', models.TextField(blank=True, null=True)),
|
||||
('facts', models.BinaryField(max_length=4294967295)),
|
||||
('changed', models.IntegerField(default=0)),
|
||||
('failed', models.IntegerField(default=0)),
|
||||
('ok', models.IntegerField(default=0)),
|
||||
|
@ -41,100 +54,127 @@ class Migration(migrations.Migration):
|
|||
('unreachable', models.IntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'hosts',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Play',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField(auto_now_add=True, verbose_name='started')),
|
||||
('ended', models.DateTimeField(auto_now=True, verbose_name='ended')),
|
||||
('name', models.TextField(blank=True, null=True)),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('started', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('ended', models.DateTimeField(blank=True, null=True)),
|
||||
('name', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'plays',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Playbook',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField(auto_now_add=True, verbose_name='started')),
|
||||
('ended', models.DateTimeField(auto_now=True, verbose_name='ended')),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('started', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('ended', models.DateTimeField(blank=True, null=True)),
|
||||
('path', models.CharField(max_length=255)),
|
||||
('ansible_version', models.CharField(max_length=255)),
|
||||
('parameters', models.TextField(blank=True, null=True)),
|
||||
('parameters', models.BinaryField(max_length=4294967295)),
|
||||
('completed', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'playbooks',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Record',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField(auto_now_add=True, verbose_name='started')),
|
||||
('ended', models.DateTimeField(auto_now=True, verbose_name='ended')),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('key', models.CharField(max_length=255)),
|
||||
('value', models.TextField(blank=True, null=True)),
|
||||
('type', models.CharField(max_length=255)),
|
||||
('playbook_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='api.Playbook')),
|
||||
('playbook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='api.Playbook')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'records',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Result',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField(auto_now_add=True, verbose_name='started')),
|
||||
('ended', models.DateTimeField(auto_now=True, verbose_name='ended')),
|
||||
('status', models.CharField(blank=True, choices=[('ok', 'ok'), ('failed', 'failed'), ('skipped', 'skipped'), ('unreachable', 'unreachable')], max_length=11, null=True)),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('started', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('ended', models.DateTimeField(blank=True, null=True)),
|
||||
('status', models.CharField(choices=[('ok', 'ok'), ('failed', 'failed'), ('skipped', 'skipped'), ('unreachable', 'unreachable'), ('unknown', 'unknown')], default='unknown', max_length=25)),
|
||||
('changed', models.BooleanField(default=False)),
|
||||
('failed', models.BooleanField(default=False)),
|
||||
('skipped', models.BooleanField(default=False)),
|
||||
('unreachable', models.BooleanField(default=False)),
|
||||
('ignore_errors', models.BooleanField(default=False)),
|
||||
('result', models.TextField(blank=True, null=True)),
|
||||
('playbook_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='api.Playbook')),
|
||||
('result', models.BinaryField(max_length=4294967295)),
|
||||
('playbook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='api.Playbook')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'results',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Task',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('start', models.DateTimeField(auto_now_add=True, verbose_name='started')),
|
||||
('ended', models.DateTimeField(auto_now=True, verbose_name='ended')),
|
||||
('name', models.TextField(blank=True, null=True)),
|
||||
('action', models.TextField(blank=True, null=True)),
|
||||
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('started', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('ended', models.DateTimeField(blank=True, null=True)),
|
||||
('name', models.TextField()),
|
||||
('action', models.TextField()),
|
||||
('lineno', models.IntegerField()),
|
||||
('tags', models.TextField(blank=True, null=True)),
|
||||
('tags', models.BinaryField(max_length=4294967295)),
|
||||
('handler', models.BooleanField()),
|
||||
('playbook_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='api.Playbook')),
|
||||
('file', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='tasks', to='api.File')),
|
||||
('play', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='tasks', to='api.Play')),
|
||||
('playbook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='api.Playbook')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'db_table': 'tasks',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='play',
|
||||
name='playbook_id',
|
||||
name='playbook',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plays', to='api.Playbook'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='playbook_id',
|
||||
name='playbook',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hosts', to='api.Playbook'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='file',
|
||||
name='playbook_id',
|
||||
name='content',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='api.FileContent'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='file',
|
||||
name='playbook',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='api.Playbook'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='record',
|
||||
unique_together={('key', 'playbook')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='host',
|
||||
unique_together={('name', 'playbook')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='file',
|
||||
unique_together={('path', 'playbook')},
|
||||
),
|
||||
]
|
||||
|
|
229
api/models.py
229
api/models.py
|
@ -1,104 +1,251 @@
|
|||
import uuid
|
||||
# Copyright (c) 2018 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of ARA Records Ansible.
|
||||
#
|
||||
# ARA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ARA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
logger = logging.getLogger('ara_backend.models')
|
||||
|
||||
|
||||
class Base(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
id = models.BigAutoField(primary_key=True, editable=False)
|
||||
created = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
updated = models.DateTimeField(auto_now=True, editable=False)
|
||||
|
||||
@property
|
||||
def age(self):
|
||||
"""
|
||||
Calculates duration between created and updated.
|
||||
"""
|
||||
return self.updated - self.created
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class DateMixin(Base):
|
||||
start = models.DateTimeField(auto_now_add=True, verbose_name='started')
|
||||
ended = models.DateTimeField(auto_now=True, verbose_name='ended')
|
||||
class DurationMixin(models.Model):
|
||||
started = models.DateTimeField(default=timezone.now)
|
||||
ended = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""
|
||||
Calculates duration between started and ended or between started and
|
||||
updated if we do not yet have an end.
|
||||
"""
|
||||
if self.ended is None:
|
||||
if self.started is None:
|
||||
return timezone.timedelta(seconds=0)
|
||||
else:
|
||||
return self.updated - self.started
|
||||
return self.ended - self.started
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Playbook(DateMixin):
|
||||
class Playbook(Base, DurationMixin):
|
||||
"""
|
||||
The 'playbook' table represents a single execution of the ansible or
|
||||
ansible-playbook commands. All the data for that execution is tied back
|
||||
to this one playbook.
|
||||
"""
|
||||
class Meta:
|
||||
db_table = 'playbooks'
|
||||
|
||||
path = models.CharField(max_length=255)
|
||||
ansible_version = models.CharField(max_length=255)
|
||||
parameters = models.TextField(null=True, blank=True)
|
||||
parameters = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
completed = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return '<Playbook %s>' % self.id
|
||||
return '<Playbook %s:%s>' % (self.id, self.path)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class FileContent(Base):
|
||||
class Meta:
|
||||
db_table = 'file_contents'
|
||||
|
||||
sha1 = models.CharField(max_length=40, unique=True)
|
||||
contents = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
|
||||
def __str__(self):
|
||||
return '<FileContent %s:%s>' % (self.id, self.sha1)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class File(Base):
|
||||
content = models.FileField(upload_to='files/%Y/%m/%d/')
|
||||
class Meta:
|
||||
db_table = 'files'
|
||||
unique_together = ('path', 'playbook',)
|
||||
|
||||
path = models.CharField(max_length=255)
|
||||
is_playbook = models.BooleanField(default=False)
|
||||
playbook_id = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='files')
|
||||
content = models.ForeignKey(FileContent,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='files')
|
||||
playbook = models.ForeignKey(Playbook,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='files')
|
||||
|
||||
def __str__(self):
|
||||
return '<File %s:%s>' % (self.id, self.path)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class Record(DateMixin):
|
||||
class Record(Base):
|
||||
class Meta:
|
||||
db_table = 'records'
|
||||
unique_together = ('key', 'playbook',)
|
||||
|
||||
key = models.CharField(max_length=255)
|
||||
value = models.TextField(null=True, blank=True)
|
||||
type = models.CharField(max_length=255)
|
||||
playbook_id = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='records')
|
||||
playbook = models.ForeignKey(Playbook,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='records')
|
||||
|
||||
def __str__(self):
|
||||
return '<Record %s>' % self.id
|
||||
return '<Record %s:%s>' % (self.id, self.key)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class Host(DateMixin):
|
||||
class Host(Base):
|
||||
class Meta:
|
||||
db_table = 'hosts'
|
||||
unique_together = ('name', 'playbook',)
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
facts = models.TextField(null=True, blank=True)
|
||||
facts = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
changed = models.IntegerField(default=0)
|
||||
failed = models.IntegerField(default=0)
|
||||
ok = models.IntegerField(default=0)
|
||||
skipped = models.IntegerField(default=0)
|
||||
unreachable = models.IntegerField(default=0)
|
||||
playbook_id = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='hosts')
|
||||
playbook = models.ForeignKey(Playbook,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='hosts')
|
||||
|
||||
def __str__(self):
|
||||
return '<Host %s:%s>' % (self.name, self.id)
|
||||
return '<Host %s:%s>' % (self.id, self.name)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
OK = "ok"
|
||||
FAILED = "failed"
|
||||
SKIPPED = "skipped"
|
||||
UNREACHABLE = "unreachable"
|
||||
class Play(Base, DurationMixin):
|
||||
class Meta:
|
||||
db_table = 'plays'
|
||||
|
||||
RESULT_STATUS = (
|
||||
(OK, "ok"),
|
||||
(FAILED, "failed"),
|
||||
(SKIPPED, "skipped"),
|
||||
(UNREACHABLE, "unreachable")
|
||||
)
|
||||
name = models.TextField()
|
||||
playbook = models.ForeignKey(Playbook,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='plays')
|
||||
|
||||
|
||||
class Play(DateMixin):
|
||||
name = models.TextField(null=True, blank=True)
|
||||
playbook_id = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='plays')
|
||||
@property
|
||||
def offset_from_playbook(self):
|
||||
return self.started - self.playbook.started
|
||||
|
||||
def __str__(self):
|
||||
return '<Play %s:%s>' % (self.name, self.id)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class Task(DateMixin):
|
||||
name = models.TextField(null=True, blank=True)
|
||||
action = models.TextField(null=True, blank=True)
|
||||
class Task(Base, DurationMixin):
|
||||
class Meta:
|
||||
db_table = 'tasks'
|
||||
|
||||
name = models.TextField()
|
||||
action = models.TextField()
|
||||
lineno = models.IntegerField()
|
||||
tags = models.TextField(null=True, blank=True)
|
||||
tags = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
handler = models.BooleanField()
|
||||
playbook_id = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='tasks')
|
||||
|
||||
playbook = models.ForeignKey(Playbook,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='tasks')
|
||||
file = models.ForeignKey(File,
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name='tasks')
|
||||
play = models.ForeignKey(Play,
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name='tasks')
|
||||
|
||||
@property
|
||||
def offset_from_playbook(self):
|
||||
return self.started - self.playbook.started
|
||||
|
||||
@property
|
||||
def offset_from_play(self):
|
||||
return self.started - self.play.started
|
||||
|
||||
def __str__(self):
|
||||
return '<Task %s:%s>' % (self.name, self.id)
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class Result(DateMixin):
|
||||
status = models.CharField(max_length=11, choices=RESULT_STATUS, null=True, blank=True)
|
||||
class Result(Base, DurationMixin):
|
||||
class Meta:
|
||||
db_table = 'results'
|
||||
|
||||
# Ansible statuses
|
||||
OK = 'ok'
|
||||
FAILED = 'failed'
|
||||
SKIPPED = 'skipped'
|
||||
UNREACHABLE = 'unreachable'
|
||||
# ARA specific statuses (derived or assumed)
|
||||
CHANGED = 'changed'
|
||||
IGNORED = 'ignored'
|
||||
UNKNOWN = 'unknown'
|
||||
|
||||
RESULT_STATUS = (
|
||||
(OK, 'ok'),
|
||||
(FAILED, 'failed'),
|
||||
(SKIPPED, 'skipped'),
|
||||
(UNREACHABLE, 'unreachable'),
|
||||
(UNKNOWN, 'unknown')
|
||||
)
|
||||
|
||||
status = models.CharField(max_length=25,
|
||||
choices=RESULT_STATUS,
|
||||
default=UNKNOWN)
|
||||
changed = models.BooleanField(default=False)
|
||||
failed = models.BooleanField(default=False)
|
||||
skipped = models.BooleanField(default=False)
|
||||
unreachable = models.BooleanField(default=False)
|
||||
ignore_errors = models.BooleanField(default=False)
|
||||
result = models.TextField(null=True, blank=True)
|
||||
playbook_id = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='results')
|
||||
result = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
playbook = models.ForeignKey(Playbook,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='results')
|
||||
|
||||
@property
|
||||
def derived_status(self):
|
||||
if self.status == self.OK and self.changed:
|
||||
return self.CHANGED
|
||||
elif self.status == self.FAILED and self.ignore_errors:
|
||||
return self.IGNORED
|
||||
elif self.status not in [
|
||||
self.OK, self.FAILED, self.SKIPPED, self.UNREACHABLE
|
||||
]:
|
||||
return self.UNKNOWN
|
||||
else:
|
||||
return self.status
|
||||
|
||||
def __str__(self):
|
||||
return '<Result %s>' % self.id
|
||||
return '<Result %s:%s>' % (self.id, self.derived_status)
|
||||
__repr__ = __str__
|
||||
|
|
|
@ -1,24 +1,207 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import zlib
|
||||
from api import models
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
DATE_FORMAT = "(iso-8601: 2016-05-06T17:20:25.749489-04:00)"
|
||||
DURATION_FORMAT = "([DD] [HH:[MM:]]ss[.uuuuuu])"
|
||||
logger = logging.getLogger('api.serializers')
|
||||
|
||||
|
||||
class PlaybookSerializer(serializers.ModelSerializer):
|
||||
class CompressedTextField(serializers.CharField):
|
||||
def to_representation(self, obj):
|
||||
return zlib.decompress(obj).decode('utf8')
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return zlib.compress(data.encode('utf8'))
|
||||
|
||||
|
||||
class CompressedObjectField(serializers.JSONField):
|
||||
def to_representation(self, obj):
|
||||
return json.loads(zlib.decompress(obj).decode('utf8'))
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return zlib.compress(data.encode('utf8'))
|
||||
|
||||
|
||||
class SHA1Field(serializers.CharField):
|
||||
def to_representation(self, obj):
|
||||
return json.loads(lzma.decompress(obj).decode('utf8'))
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return lzma.compress(data.encode('utf8'))
|
||||
|
||||
|
||||
class BaseSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for the data in the model base
|
||||
"""
|
||||
created = serializers.DateTimeField(
|
||||
read_only=True, help_text='Date of creation %s' % DATE_FORMAT
|
||||
)
|
||||
updated = serializers.DateTimeField(
|
||||
read_only=True, help_text='Date of last update %s' % DATE_FORMAT
|
||||
)
|
||||
age = serializers.DurationField(
|
||||
read_only=True,
|
||||
help_text='Duration since the creation %s' % DURATION_FORMAT
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class DurationSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for duration-based fields
|
||||
"""
|
||||
started = serializers.DateTimeField(
|
||||
initial=timezone.now().isoformat(),
|
||||
help_text='Date this item started %s' % DATE_FORMAT
|
||||
)
|
||||
ended = serializers.DateTimeField(
|
||||
required=False,
|
||||
help_text='Date this item ended %s' % DATE_FORMAT
|
||||
)
|
||||
duration = serializers.DurationField(
|
||||
read_only=True,
|
||||
help_text="Duration between 'started' and 'ended' %s" % DURATION_FORMAT
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Check that the start is before the end.
|
||||
"""
|
||||
if 'ended' in data and (data['started'] > data['ended']):
|
||||
raise serializers.ValidationError(
|
||||
"'Ended' must be before 'started'"
|
||||
)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class PlaybookSerializer(BaseSerializer, DurationSerializer):
|
||||
class Meta:
|
||||
model = models.Playbook
|
||||
fields = '__all__'
|
||||
|
||||
plays = serializers.HyperlinkedRelatedField(
|
||||
many=True,
|
||||
read_only=True,
|
||||
view_name='plays',
|
||||
help_text='Plays associated to this playbook'
|
||||
)
|
||||
# tasks = serializers.HyperlinkedRelatedField(
|
||||
# many=True,
|
||||
# read_only=True,
|
||||
# view_name='tasks',
|
||||
# help_text='Tasks associated to this playbook'
|
||||
# )
|
||||
# hosts = serializers.HyperlinkedRelatedField(
|
||||
# many=True,
|
||||
# read_only=True,
|
||||
# view_name='hosts',
|
||||
# help_text='Hosts associated to this playbook'
|
||||
# )
|
||||
# results = serializers.HyperlinkedRelatedField(
|
||||
# many=True,
|
||||
# read_only=True,
|
||||
# view_name='results',
|
||||
# help_text='Results associated to this playbook'
|
||||
# )
|
||||
# records = serializers.HyperlinkedRelatedField(
|
||||
# many=True,
|
||||
# read_only=True,
|
||||
# view_name='records',
|
||||
# help_text='Records associated to this playbook'
|
||||
# )
|
||||
# files = serializers.HyperlinkedRelatedField(
|
||||
# many=True,
|
||||
# read_only=True,
|
||||
# view_name='files',
|
||||
# help_text='Records associated to this playbook'
|
||||
# )
|
||||
|
||||
class FileSerializer(serializers.ModelSerializer):
|
||||
playbook = PlaybookSerializer(source='playbook_id', read_only=True)
|
||||
|
||||
parameters = CompressedObjectField(
|
||||
initial={},
|
||||
help_text='A JSON dictionary containing Ansible command parameters'
|
||||
)
|
||||
path = serializers.CharField(help_text='Path to the playbook file')
|
||||
ansible_version = serializers.CharField(
|
||||
help_text='Version of Ansible used to run this playbook'
|
||||
)
|
||||
completed = serializers.BooleanField(
|
||||
help_text='If the completion of the execution has been acknowledged'
|
||||
)
|
||||
|
||||
|
||||
class PlaySerializer(BaseSerializer, DurationSerializer):
|
||||
class Meta:
|
||||
model = models.File
|
||||
model = models.Play
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RecordSerializer(serializers.ModelSerializer):
|
||||
playbook = PlaybookSerializer(source='playbook_id', read_only=True)
|
||||
class TaskSerializer(BaseSerializer, DurationSerializer):
|
||||
class Meta:
|
||||
model = models.Task
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class HostSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = models.Host
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ResultSerializer(BaseSerializer, DurationSerializer):
|
||||
class Meta:
|
||||
model = models.Result
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RecordSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = models.Record
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FileContentSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = models.FileContent
|
||||
fields = ('contents', 'sha1')
|
||||
|
||||
contents = CompressedTextField(help_text='Contents of the file')
|
||||
sha1 = serializers.CharField(read_only=True, help_text='sha1 of the file')
|
||||
|
||||
def create(self, validated_data):
|
||||
sha1 = hashlib.sha1(validated_data['contents']).hexdigest()
|
||||
validated_data['sha1'] = sha1
|
||||
obj, created = models.FileContent.objects.get_or_create(
|
||||
**validated_data
|
||||
)
|
||||
return obj
|
||||
|
||||
|
||||
class FileSerializer(BaseSerializer):
|
||||
path = serializers.CharField(help_text='Path to the file')
|
||||
content = FileContentSerializer()
|
||||
|
||||
def create(self, validated_data):
|
||||
contents = validated_data.pop('content')['contents']
|
||||
obj, created = models.FileContent.objects.get_or_create(
|
||||
contents=contents,
|
||||
sha1=hashlib.sha1(contents).hexdigest()
|
||||
)
|
||||
validated_data['content'] = obj
|
||||
return models.File.objects.create(**validated_data)
|
||||
|
||||
class Meta:
|
||||
model = models.File
|
||||
fields = ('id', 'path', 'content', 'playbook')
|
||||
|
|
24
api/urls.py
24
api/urls.py
|
@ -20,15 +20,23 @@ from rest_framework.routers import DefaultRouter
|
|||
|
||||
from api import views
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
# Use URL-based versioning
|
||||
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
|
||||
'DEFAULT_VERSION': 'v1',
|
||||
'ALLOWED_VERSIONS': {'v1'},
|
||||
}
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'playbooks', views.PlaybookViewSet, base_name='playbooks')
|
||||
router.register(r'plays', views.PlayViewSet, base_name='plays')
|
||||
router.register(r'tasks', views.TaskViewSet, base_name='tasks')
|
||||
router.register(r'hosts', views.HostViewSet, base_name='hosts')
|
||||
router.register(r'results', views.ResultViewSet, base_name='results')
|
||||
router.register(r'records', views.RecordViewSet, base_name='records')
|
||||
router.register(r'files', views.FileViewSet, base_name='files')
|
||||
router.register(r'playbooks', views.PlaybookViewSet)
|
||||
router.register(r'plays', views.PlayViewSet)
|
||||
router.register(r'tasks', views.TaskViewSet)
|
||||
router.register(r'hosts', views.HostViewSet)
|
||||
router.register(r'results', views.ResultViewSet)
|
||||
router.register(r'records', views.RecordViewSet)
|
||||
router.register(r'files', views.FileViewSet)
|
||||
# router.register(r'filecontent', views.FileContentViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^(?P<version>[v1]+)/', include(router.urls)),
|
||||
]
|
||||
|
|
|
@ -50,6 +50,11 @@ class RecordViewSet(viewsets.ModelViewSet):
|
|||
serializer_class = serializers.RecordSerializer
|
||||
|
||||
|
||||
# class FileContentViewSet(viewsets.ModelViewSet):
|
||||
# queryset = models.FileContent.objects.all()
|
||||
# serializer_class = serializers.FileContentSerializer
|
||||
|
||||
|
||||
class FileViewSet(viewsets.ModelViewSet):
|
||||
queryset = models.File.objects.all()
|
||||
serializer_class = serializers.FileSerializer
|
||||
|
|
|
@ -7,7 +7,7 @@ admin.site.site_header = 'Administration'
|
|||
admin.site.index_title = 'Administration Ara'
|
||||
|
||||
routes = [
|
||||
url(r'^api/v1/', include('api.urls')),
|
||||
url(r'^api/', include('api.urls')),
|
||||
url(r'^admin/', admin.site.urls),
|
||||
]
|
||||
urlpatterns = routes + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
|
Loading…
Reference in New Issue