Add file foreign key to Task and Playbook
And add some tests, too. Change-Id: Ie44dd60dd7fdee546b3d3a2a5424f8f551f08b4e
This commit is contained in:
parent
17f6c9eca5
commit
61bd81226f
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 2.0.3 on 2018-03-20 13:55
|
||||
# Generated by Django 2.0.3 on 2018-03-20 18:21
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
@ -81,6 +81,7 @@ class Migration(migrations.Migration):
|
|||
('ansible_version', models.CharField(max_length=255)),
|
||||
('completed', models.BooleanField(default=False)),
|
||||
('parameters', models.BinaryField(max_length=4294967295)),
|
||||
('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='playbooks', to='api.File')),
|
||||
('files', models.ManyToManyField(to='api.File')),
|
||||
],
|
||||
options={
|
||||
|
@ -112,6 +113,7 @@ class Migration(migrations.Migration):
|
|||
('ended', models.DateTimeField(blank=True, null=True)),
|
||||
('status', models.CharField(choices=[('ok', 'ok'), ('failed', 'failed'), ('skipped', 'skipped'), ('unreachable', 'unreachable'), ('changed', 'changed'), ('ignored', 'ignored'), ('unknown', 'unknown')], default='unknown', max_length=25)),
|
||||
('content', models.BinaryField(max_length=4294967295)),
|
||||
('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='api.Host')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'results',
|
||||
|
@ -130,39 +132,29 @@ class Migration(migrations.Migration):
|
|||
('lineno', models.IntegerField()),
|
||||
('tags', models.BinaryField(max_length=4294967295)),
|
||||
('handler', models.BooleanField()),
|
||||
('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='api.File')),
|
||||
('files', models.ManyToManyField(to='api.File')),
|
||||
('play', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='api.Play')),
|
||||
('results', models.ManyToManyField(to='api.Result')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'tasks',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='playbook',
|
||||
name='results',
|
||||
field=models.ManyToManyField(to='api.Result'),
|
||||
model_name='result',
|
||||
name='task',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='api.Task'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='play',
|
||||
name='playbook',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plays', to='api.Playbook'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='play',
|
||||
name='results',
|
||||
field=models.ManyToManyField(to='api.Result'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='play',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='hosts', to='api.Play'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='host',
|
||||
name='results',
|
||||
field=models.ManyToManyField(to='api.Result'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='file',
|
||||
name='content',
|
||||
|
|
|
@ -77,42 +77,6 @@ class File(Base):
|
|||
return '<File %s:%s>' % (self.id, self.path)
|
||||
|
||||
|
||||
class Result(Duration):
|
||||
"""
|
||||
Data about Ansible results.
|
||||
A task can have many results if the task is run on multiple hosts.
|
||||
"""
|
||||
|
||||
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'
|
||||
|
||||
STATUS = (
|
||||
(OK, 'ok'),
|
||||
(FAILED, 'failed'),
|
||||
(SKIPPED, 'skipped'),
|
||||
(UNREACHABLE, 'unreachable'),
|
||||
(CHANGED, 'changed'),
|
||||
(IGNORED, 'ignored'),
|
||||
(UNKNOWN, 'unknown')
|
||||
)
|
||||
|
||||
status = models.CharField(max_length=25, choices=STATUS, default=UNKNOWN)
|
||||
content = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
|
||||
def __str__(self):
|
||||
return '<Result %s, %s>' % (self.id, self.status)
|
||||
|
||||
|
||||
class Playbook(Duration):
|
||||
"""
|
||||
An entry in the 'playbooks' table represents a single execution of the
|
||||
|
@ -126,8 +90,8 @@ class Playbook(Duration):
|
|||
ansible_version = models.CharField(max_length=255)
|
||||
completed = models.BooleanField(default=False)
|
||||
parameters = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
file = models.ForeignKey(File, on_delete=models.CASCADE, related_name='playbooks')
|
||||
files = models.ManyToManyField(File)
|
||||
results = models.ManyToManyField(Result)
|
||||
|
||||
def __str__(self):
|
||||
return '<Playbook %s>' % self.id
|
||||
|
@ -163,7 +127,6 @@ class Play(Duration):
|
|||
|
||||
name = models.CharField(max_length=255, blank=True, null=True)
|
||||
playbook = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='plays')
|
||||
results = models.ManyToManyField(Result)
|
||||
|
||||
def __str__(self):
|
||||
return '<Play %s:%s>' % (self.id, self.name)
|
||||
|
@ -182,7 +145,7 @@ class Task(Duration):
|
|||
handler = models.BooleanField()
|
||||
|
||||
play = models.ForeignKey(Play, on_delete=models.CASCADE, related_name='tasks')
|
||||
results = models.ManyToManyField(Result)
|
||||
file = models.ForeignKey(File, on_delete=models.CASCADE, related_name='tasks')
|
||||
files = models.ManyToManyField(File)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -208,7 +171,45 @@ class Host(Base):
|
|||
skipped = models.IntegerField(default=0)
|
||||
unreachable = models.IntegerField(default=0)
|
||||
play = models.ForeignKey(Play, on_delete=models.DO_NOTHING, related_name='hosts')
|
||||
results = models.ManyToManyField(Result)
|
||||
|
||||
def __str__(self):
|
||||
return '<Host %s:%s>' % (self.id, self.name)
|
||||
|
||||
|
||||
class Result(Duration):
|
||||
"""
|
||||
Data about Ansible results.
|
||||
A task can have many results if the task is run on multiple hosts.
|
||||
"""
|
||||
|
||||
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'
|
||||
|
||||
STATUS = (
|
||||
(OK, 'ok'),
|
||||
(FAILED, 'failed'),
|
||||
(SKIPPED, 'skipped'),
|
||||
(UNREACHABLE, 'unreachable'),
|
||||
(CHANGED, 'changed'),
|
||||
(IGNORED, 'ignored'),
|
||||
(UNKNOWN, 'unknown')
|
||||
)
|
||||
|
||||
status = models.CharField(max_length=25, choices=STATUS, default=UNKNOWN)
|
||||
# todo use a single Content table
|
||||
content = models.BinaryField(max_length=(2 ** 32) - 1)
|
||||
host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='results')
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='results')
|
||||
|
||||
def __str__(self):
|
||||
return '<Result %s, %s>' % (self.id, self.status)
|
||||
|
|
|
@ -116,24 +116,19 @@ class PlaybookSerializer(DurationSerializer):
|
|||
model = models.Playbook
|
||||
fields = '__all__'
|
||||
|
||||
parameters = CompressedObjectField(
|
||||
default=zlib.compress(json.dumps({}).encode('utf8')),
|
||||
help_text='A JSON dictionary containing Ansible command parameters'
|
||||
)
|
||||
parameters = CompressedObjectField(default=zlib.compress(json.dumps({}).encode('utf8')))
|
||||
file = FileSerializer()
|
||||
files = FileSerializer(many=True, default=[])
|
||||
results = ResultSerializer(read_only=True, many=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
file_dict = validated_data.pop('file')
|
||||
validated_data['file'] = models.File.objects.create(**file_dict)
|
||||
files = validated_data.pop('files')
|
||||
playbook = models.Playbook.objects.create(**validated_data)
|
||||
for file in files:
|
||||
playbook.files.add(models.File.objects.create(**file))
|
||||
return playbook
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
files = validated_data.pop('files')
|
||||
return super(PlaybookSerializer, self).update(instance, validated_data)
|
||||
|
||||
|
||||
class PlaySerializer(DurationSerializer):
|
||||
class Meta:
|
||||
|
|
|
@ -3,15 +3,6 @@ import factory
|
|||
from api import models
|
||||
|
||||
|
||||
class PlaybookFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = models.Playbook
|
||||
|
||||
ansible_version = '2.4.0'
|
||||
completed = True
|
||||
parameters = b'x\x9c\xabVJ\xcb\xcfW\xb2RPJJ,R\xaa\x05\x00 \x98\x04T'
|
||||
|
||||
|
||||
class FileContentFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = models.FileContent
|
||||
|
@ -27,3 +18,13 @@ class FileFactory(factory.DjangoModelFactory):
|
|||
|
||||
path = '/tmp/playbook.yml'
|
||||
content = factory.SubFactory(FileContentFactory)
|
||||
|
||||
|
||||
class PlaybookFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = models.Playbook
|
||||
|
||||
ansible_version = '2.4.0'
|
||||
completed = True
|
||||
parameters = b'x\x9c\xabVJ\xcb\xcfW\xb2RPJJ,R\xaa\x05\x00 \x98\x04T'
|
||||
file = factory.SubFactory(FileFactory)
|
||||
|
|
|
@ -43,6 +43,15 @@ class FileTestCase(APITestCase):
|
|||
self.assertEqual(2, models.File.objects.all().count())
|
||||
self.assertEqual(1, models.FileContent.objects.all().count())
|
||||
|
||||
def test_create_file(self):
|
||||
self.assertEqual(0, models.File.objects.count())
|
||||
request = self.client.post('/api/v1/files/', {
|
||||
'path': '/tmp/playbook.yml',
|
||||
'content': '# playbook'
|
||||
})
|
||||
self.assertEqual(201, request.status_code)
|
||||
self.assertEqual(1, models.File.objects.count())
|
||||
|
||||
def test_get_no_files(self):
|
||||
request = self.client.get('/api/v1/files/')
|
||||
self.assertEqual(0, len(request.data['results']))
|
||||
|
@ -53,21 +62,21 @@ class FileTestCase(APITestCase):
|
|||
self.assertEqual(1, len(request.data['results']))
|
||||
self.assertEqual(file.path, request.data['results'][0]['path'])
|
||||
|
||||
def test_delete_file(self):
|
||||
def test_get_file(self):
|
||||
file = factories.FileFactory()
|
||||
self.assertEqual(1, models.File.objects.all().count())
|
||||
request = self.client.delete('/api/v1/files/%s/' % file.id)
|
||||
self.assertEqual(204, request.status_code)
|
||||
self.assertEqual(0, models.File.objects.all().count())
|
||||
request = self.client.get('/api/v1/files/%s/' % file.id)
|
||||
self.assertEqual(file.path, request.data['path'])
|
||||
|
||||
def test_create_file(self):
|
||||
self.assertEqual(0, models.File.objects.count())
|
||||
request = self.client.post('/api/v1/files/', {
|
||||
'path': '/tmp/playbook.yml',
|
||||
def test_update_file(self):
|
||||
file = factories.FileFactory()
|
||||
self.assertNotEqual('/tmp/new_playbook.yml', file.path)
|
||||
request = self.client.put('/api/v1/files/%s/' % file.id, {
|
||||
"path": "/tmp/new_playbook.yml",
|
||||
'content': '# playbook'
|
||||
})
|
||||
self.assertEqual(201, request.status_code)
|
||||
self.assertEqual(1, models.File.objects.count())
|
||||
self.assertEqual(200, request.status_code)
|
||||
file_updated = models.File.objects.get(id=file.id)
|
||||
self.assertEqual('/tmp/new_playbook.yml', file_updated.path)
|
||||
|
||||
def test_partial_update_file(self):
|
||||
file = factories.FileFactory()
|
||||
|
@ -79,7 +88,9 @@ class FileTestCase(APITestCase):
|
|||
file_updated = models.File.objects.get(id=file.id)
|
||||
self.assertEqual('/tmp/new_playbook.yml', file_updated.path)
|
||||
|
||||
def test_get_file(self):
|
||||
def test_delete_file(self):
|
||||
file = factories.FileFactory()
|
||||
request = self.client.get('/api/v1/files/%s/' % file.id)
|
||||
self.assertEqual(file.path, request.data['path'])
|
||||
self.assertEqual(1, models.File.objects.all().count())
|
||||
request = self.client.delete('/api/v1/files/%s/' % file.id)
|
||||
self.assertEqual(204, request.status_code)
|
||||
self.assertEqual(0, models.File.objects.all().count())
|
||||
|
|
|
@ -13,7 +13,11 @@ class PlaybookTestCase(APITestCase):
|
|||
|
||||
def test_playbook_serializer(self):
|
||||
serializer = serializers.PlaybookSerializer(data={
|
||||
'ansible_version': '2.4.0'
|
||||
'ansible_version': '2.4.0',
|
||||
'file': {
|
||||
'path': '/tmp/playbook.yml',
|
||||
'content': '# playbook'
|
||||
}
|
||||
})
|
||||
serializer.is_valid()
|
||||
playbook = serializer.save()
|
||||
|
@ -23,6 +27,10 @@ class PlaybookTestCase(APITestCase):
|
|||
def test_playbook_serializer_compress_parameters(self):
|
||||
serializer = serializers.PlaybookSerializer(data={
|
||||
'ansible_version': '2.4.0',
|
||||
'file': {
|
||||
'path': '/tmp/playbook.yml',
|
||||
'content': '# playbook'
|
||||
},
|
||||
'parameters': {'foo': 'bar'}
|
||||
})
|
||||
serializer.is_valid()
|
||||
|
@ -56,14 +64,18 @@ class PlaybookTestCase(APITestCase):
|
|||
self.assertEqual(0, models.Playbook.objects.count())
|
||||
request = self.client.post('/api/v1/playbooks/', {
|
||||
"ansible_version": "2.4.0",
|
||||
'file': {
|
||||
'path': '/tmp/playbook.yml',
|
||||
'content': '# playbook'
|
||||
}
|
||||
})
|
||||
self.assertEqual(201, request.status_code)
|
||||
self.assertEqual(1, models.Playbook.objects.count())
|
||||
|
||||
def test_update_playbook(self):
|
||||
def test_partial_update_playbook(self):
|
||||
playbook = factories.PlaybookFactory()
|
||||
self.assertNotEqual('2.3.0', playbook.ansible_version)
|
||||
request = self.client.put('/api/v1/playbooks/%s/' % playbook.id, {
|
||||
request = self.client.patch('/api/v1/playbooks/%s/' % playbook.id, {
|
||||
"ansible_version": "2.3.0",
|
||||
})
|
||||
self.assertEqual(200, request.status_code)
|
||||
|
|
|
@ -12,10 +12,37 @@ class PlaybookFileTestCase(APITestCase):
|
|||
self.assertEqual(0, models.File.objects.all().count())
|
||||
self.client.post('/api/v1/playbooks/', {
|
||||
'ansible_version': '2.4.0',
|
||||
'files': [{
|
||||
'file': {
|
||||
'path': '/tmp/playbook.yml',
|
||||
'content': '# playbook'
|
||||
},
|
||||
'files': [{
|
||||
'path': '/tmp/host',
|
||||
'content': '# host'
|
||||
}],
|
||||
})
|
||||
self.assertEqual(1, models.Playbook.objects.all().count())
|
||||
self.assertEqual(2, models.File.objects.all().count())
|
||||
|
||||
def test_create_file_to_a_playbook(self):
|
||||
playbook = factories.PlaybookFactory()
|
||||
self.assertEqual(0, models.File.objects.all().count())
|
||||
self.client.post('/api/v1/playbooks/%s/files' % playbook.id, {
|
||||
'path': '/tmp/playbook.yml',
|
||||
'content': '# playbook'
|
||||
})
|
||||
self.assertEqual(1, models.File.objects.all().count())
|
||||
self.assertEqual(1, models.FileContent.objects.all().count())
|
||||
|
||||
def test_create_2_files_with_same_content(self):
|
||||
playbook = factories.PlaybookFactory()
|
||||
self.client.post('/api/v1/playbooks/%s/files' % playbook.id, {
|
||||
'path': '/tmp/1/playbook.yml',
|
||||
'content': '# playbook'
|
||||
})
|
||||
self.client.post('/api/v1/playbooks/%s/files' % playbook.id, {
|
||||
'path': '/tmp/2/playbook.yml',
|
||||
'content': '# playbook'
|
||||
})
|
||||
self.assertEqual(2, models.File.objects.all().count())
|
||||
self.assertEqual(1, models.FileContent.objects.all().count())
|
||||
|
|
Loading…
Reference in New Issue