summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster')
-rw-r--r--bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py17
-rw-r--r--bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py29
-rw-r--r--bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py138
-rw-r--r--bitbake/lib/toaster/bldcontrol/models.py2
-rw-r--r--bitbake/lib/toaster/toastergui/templates/build.html9
-rw-r--r--bitbake/lib/toaster/toastergui/templates/builddashboard.html11
-rw-r--r--bitbake/lib/toaster/toastergui/templates/managed_builds.html9
-rw-r--r--bitbake/lib/toaster/toastergui/templates/projectbuilds.html14
-rw-r--r--bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html17
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py94
-rw-r--r--bitbake/lib/toaster/toastermain/urls.py6
11 files changed, 306 insertions, 40 deletions
diff --git a/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py b/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
index 5d80bc7155..1ff5c92833 100644
--- a/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
+++ b/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
@@ -62,6 +62,23 @@ class Command(NoArgsCommand):
62 62
63 63
64 def handle(self, **options): 64 def handle(self, **options):
65 # verify that we have a settings for downloading artifacts
66 while ToasterSetting.objects.filter(name="ARTIFACTS_STORAGE_DIR").count() == 0:
67 guessedpath = os.getcwd() + "/toaster_build_artifacts/"
68 print("Toaster needs to know in which directory it can download build log files and other artifacts.\n Toaster suggests \"%s\"." % guessedpath)
69 artifacts_storage_dir = raw_input(" Press Enter to select \"%s\" or type the full path to a different directory: " % guessedpath)
70 if len(artifacts_storage_dir) == 0:
71 artifacts_storage_dir = guessedpath
72 if len(artifacts_storage_dir) > 0 and artifacts_storage_dir.startswith("/"):
73 try:
74 os.makedirs(artifacts_storage_dir)
75 except OSError as ose:
76 if "File exists" in str(ose):
77 pass
78 else:
79 raise ose
80 ToasterSetting.objects.create(name="ARTIFACTS_STORAGE_DIR", value=artifacts_storage_dir)
81
65 self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) 82 self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__)))))))
66 # refuse to start if we have no build environments 83 # refuse to start if we have no build environments
67 while BuildEnvironment.objects.count() == 0: 84 while BuildEnvironment.objects.count() == 0:
diff --git a/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py b/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
index 3de582cc86..808318f14f 100644
--- a/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
+++ b/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
@@ -1,6 +1,6 @@
1from django.core.management.base import NoArgsCommand, CommandError 1from django.core.management.base import NoArgsCommand, CommandError
2from django.db import transaction 2from django.db import transaction
3from orm.models import Build 3from orm.models import Build, ToasterSetting
4from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException, BuildSetupException 4from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException, BuildSetupException
5from bldcontrol.models import BuildRequest, BuildEnvironment, BRError, BRVariable 5from bldcontrol.models import BuildRequest, BuildEnvironment, BRError, BRVariable
6import os 6import os
@@ -93,7 +93,33 @@ class Command(NoArgsCommand):
93 bec.be.lock = BuildEnvironment.LOCK_FREE 93 bec.be.lock = BuildEnvironment.LOCK_FREE
94 bec.be.save() 94 bec.be.save()
95 95
96 def archive(self):
97 ''' archives data from the builds '''
98 artifact_storage_dir = ToasterSetting.objects.get(name="ARTIFACTS_STORAGE_DIR").value
99 for br in BuildRequest.objects.filter(state = BuildRequest.REQ_ARCHIVE):
100 # save cooker log
101 if br.build == None:
102 br.state = BuildRequest.REQ_FAILED
103 br.save()
104 continue
105 build_artifact_storage_dir = os.path.join(artifact_storage_dir, "%d" % br.build.pk)
106 try:
107 os.makedirs(build_artifact_storage_dir)
108 except OSError as ose:
109 if "File exists" in str(ose):
110 pass
111 else:
112 raise ose
113
114 file_name = os.path.join(build_artifact_storage_dir, "cooker_log.txt")
115 try:
116 with open(file_name, "w") as f:
117 f.write(br.environment.get_artifact(br.build.cooker_log_path).read())
118 except IOError:
119 os.unlink(file_name)
96 120
121 br.state = BuildRequest.REQ_COMPLETED
122 br.save()
97 123
98 def cleanup(self): 124 def cleanup(self):
99 from django.utils import timezone 125 from django.utils import timezone
@@ -104,4 +130,5 @@ class Command(NoArgsCommand):
104 130
105 def handle_noargs(self, **options): 131 def handle_noargs(self, **options):
106 self.cleanup() 132 self.cleanup()
133 self.archive()
107 self.schedule() 134 self.schedule()
diff --git a/bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py b/bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py
new file mode 100644
index 0000000000..f5469607f3
--- /dev/null
+++ b/bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py
@@ -0,0 +1,138 @@
1# -*- coding: utf-8 -*-
2from south.utils import datetime_utils as datetime
3from south.db import db
4from south.v2 import DataMigration
5from django.db import models
6
7class Migration(DataMigration):
8 # ids that cannot be imported from BuildRequest
9
10 def forwards(self, orm):
11 REQ_COMPLETED = 3
12 REQ_ARCHIVE = 6
13 "Write your forwards methods here."
14 # Note: Don't use "from appname.models import ModelName".
15 # Use orm.ModelName to refer to models in this application,
16 # and orm['appname.ModelName'] for models in other applications.
17 orm.BuildRequest.objects.filter(state=REQ_COMPLETED).update(state=REQ_ARCHIVE)
18
19 def backwards(self, orm):
20 REQ_COMPLETED = 3
21 REQ_ARCHIVE = 6
22 "Write your backwards methods here."
23 orm.BuildRequest.objects.filter(state=REQ_ARCHIVE).update(state=REQ_COMPLETED)
24
25 models = {
26 u'bldcontrol.brbitbake': {
27 'Meta': {'object_name': 'BRBitbake'},
28 'commit': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
29 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
30 'giturl': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
31 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
32 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']", 'unique': 'True'})
33 },
34 u'bldcontrol.brerror': {
35 'Meta': {'object_name': 'BRError'},
36 'errmsg': ('django.db.models.fields.TextField', [], {}),
37 'errtype': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
38 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
39 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"}),
40 'traceback': ('django.db.models.fields.TextField', [], {})
41 },
42 u'bldcontrol.brlayer': {
43 'Meta': {'object_name': 'BRLayer'},
44 'commit': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
45 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
46 'giturl': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
47 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
48 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
49 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"})
50 },
51 u'bldcontrol.brtarget': {
52 'Meta': {'object_name': 'BRTarget'},
53 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
54 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"}),
55 'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
56 'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
57 },
58 u'bldcontrol.brvariable': {
59 'Meta': {'object_name': 'BRVariable'},
60 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
62 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"}),
63 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
64 },
65 u'bldcontrol.buildenvironment': {
66 'Meta': {'object_name': 'BuildEnvironment'},
67 'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
68 'bbaddress': ('django.db.models.fields.CharField', [], {'max_length': '254', 'blank': 'True'}),
69 'bbport': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
70 'bbstate': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
71 'bbtoken': ('django.db.models.fields.CharField', [], {'max_length': '126', 'blank': 'True'}),
72 'betype': ('django.db.models.fields.IntegerField', [], {}),
73 'builddir': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
74 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
75 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76 'lock': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
77 'sourcedir': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
78 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
79 },
80 u'bldcontrol.buildrequest': {
81 'Meta': {'object_name': 'BuildRequest'},
82 'build': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Build']", 'unique': 'True', 'null': 'True'}),
83 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
84 'environment': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildEnvironment']", 'null': 'True'}),
85 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
87 'state': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
88 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
89 },
90 u'orm.bitbakeversion': {
91 'Meta': {'object_name': 'BitbakeVersion'},
92 'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
93 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
94 'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
95 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
97 },
98 u'orm.build': {
99 'Meta': {'object_name': 'Build'},
100 'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
101 'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
102 'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
103 'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
104 'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
105 'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
106 'errors_no': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
107 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108 'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
109 'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
110 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']", 'null': 'True'}),
111 'started_on': ('django.db.models.fields.DateTimeField', [], {}),
112 'timespent': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
113 'warnings_no': ('django.db.models.fields.IntegerField', [], {'default': '0'})
114 },
115 u'orm.project': {
116 'Meta': {'object_name': 'Project'},
117 'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
118 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
119 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
121 'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"}),
122 'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
123 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
124 'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
125 },
126 u'orm.release': {
127 'Meta': {'object_name': 'Release'},
128 'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
129 'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
130 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
131 'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
132 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
133 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
134 }
135 }
136
137 complete_apps = ['bldcontrol']
138 symmetrical = True
diff --git a/bitbake/lib/toaster/bldcontrol/models.py b/bitbake/lib/toaster/bldcontrol/models.py
index dc4afca2f7..25d94cd3fe 100644
--- a/bitbake/lib/toaster/bldcontrol/models.py
+++ b/bitbake/lib/toaster/bldcontrol/models.py
@@ -94,6 +94,7 @@ class BuildRequest(models.Model):
94 REQ_COMPLETED = 3 94 REQ_COMPLETED = 3
95 REQ_FAILED = 4 95 REQ_FAILED = 4
96 REQ_DELETED = 5 96 REQ_DELETED = 5
97 REQ_ARCHIVE = 6
97 98
98 REQUEST_STATE = ( 99 REQUEST_STATE = (
99 (REQ_CREATED, "created"), 100 (REQ_CREATED, "created"),
@@ -102,6 +103,7 @@ class BuildRequest(models.Model):
102 (REQ_COMPLETED, "completed"), 103 (REQ_COMPLETED, "completed"),
103 (REQ_FAILED, "failed"), 104 (REQ_FAILED, "failed"),
104 (REQ_DELETED, "deleted"), 105 (REQ_DELETED, "deleted"),
106 (REQ_ARCHIVE, "archive"),
105 ) 107 )
106 108
107 search_allowed_fields = ("brtarget__target",) 109 search_allowed_fields = ("brtarget__target",)
diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html
index e71e38feb9..684ec65884 100644
--- a/bitbake/lib/toaster/toastergui/templates/build.html
+++ b/bitbake/lib/toaster/toastergui/templates/build.html
@@ -40,7 +40,9 @@
40 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> 40 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
41 {% for build in objects %} 41 {% for build in objects %}
42 <tr class="data"> 42 <tr class="data">
43 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> 43 <td class="outcome">
44 <a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a> &nbsp;
45 </td>
44 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 46 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
45 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 47 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
46 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 48 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
@@ -61,11 +63,6 @@
61 <td class="errors_no"> 63 <td class="errors_no">
62 {% if build.errors_no %} 64 {% if build.errors_no %}
63 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> 65 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
64 {% if MANAGED and build.project %}
65 <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
66 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
67 </a>
68 {% endif %}
69 {%endif%} 66 {%endif%}
70 </td> 67 </td>
71 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td> 68 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
diff --git a/bitbake/lib/toaster/toastergui/templates/builddashboard.html b/bitbake/lib/toaster/toastergui/templates/builddashboard.html
index 2458cdb6d1..c0898e291d 100644
--- a/bitbake/lib/toaster/toastergui/templates/builddashboard.html
+++ b/bitbake/lib/toaster/toastergui/templates/builddashboard.html
@@ -36,7 +36,11 @@
36{% endif %} 36{% endif %}
37 <span > <i class="icon-warning-sign yellow"></i><strong><a href="#warnings" class="warning show-warnings"> {{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a></strong></span> 37 <span > <i class="icon-warning-sign yellow"></i><strong><a href="#warnings" class="warning show-warnings"> {{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a></strong></span>
38{% endif %} 38{% endif %}
39 <span class="pull-right">Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a></span> 39 <span class="pull-right">Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a> &nbsp;
40 {% if MANAGED and build.project %}
41 <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%else%}btn-danger{%endif%} pull-right" href="{% url 'build_artifact' build.id "cookerlog" build.id %}">Download build log</a>
42 {% endif %}
43 </span>
40{%endif%} 44{%endif%}
41 </div> 45 </div>
42 {% if build.toaster_exceptions.count > 0 %} 46 {% if build.toaster_exceptions.count > 0 %}
@@ -54,10 +58,7 @@
54<div class="accordion span10 pull-right" id="errors"> 58<div class="accordion span10 pull-right" id="errors">
55 <div class="accordion-group"> 59 <div class="accordion-group">
56 <div class="accordion-heading"> 60 <div class="accordion-heading">
57 {% if MANAGED and build.project %} 61 <a class="accordion-toggle error toggle-errors">
58 <a class="btn btn-large pull-right" href="{% url 'build_artifact' build.id "cookerlog" build.id %}" style="margin:15px;">Download build log</a>
59 {% endif %}
60 <a class="accordion-toggle error toggle-errors">
61 <h2 id="error-toggle"> 62 <h2 id="error-toggle">
62 <i class="icon-minus-sign"></i> 63 <i class="icon-minus-sign"></i>
63 {{build.errors_no}} error{{build.errors_no|pluralize}} 64 {{build.errors_no}} error{{build.errors_no|pluralize}}
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
index 121ad07898..a4db55b967 100644
--- a/bitbake/lib/toaster/toastergui/templates/managed_builds.html
+++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
@@ -46,7 +46,14 @@
46 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> 46 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
47 {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #} 47 {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #}
48 <tr class="data"> 48 <tr class="data">
49 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> 49 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a>
50 {% if build.project %}
51 &nbsp; <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
52 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
53 </a>
54 {% endif %}
55
56 </td>
50 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 57 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
51 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 58 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
52 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 59 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
diff --git a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
index 02d166341f..afcf5191b5 100644
--- a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
+++ b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
@@ -49,7 +49,14 @@
49 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> 49 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
50 {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} 50 {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #}
51 <tr class="data"> 51 <tr class="data">
52 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> 52 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a>
53 {% if build.project %}
54 &nbsp; <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
55 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
56 </a>
57 {% endif %}
58 </td>
59
53 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 60 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
54 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 61 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
55 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 62 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
@@ -70,11 +77,6 @@
70 <td class="errors_no"> 77 <td class="errors_no">
71 {% if build.errors_no %} 78 {% if build.errors_no %}
72 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> 79 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
73 {% if MANAGED and build.project %}
74 <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
75 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
76 </a>
77 {% endif %}
78 {%endif%} 80 {%endif%}
79 </td> 81 </td>
80 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td> 82 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
diff --git a/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html b/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html
new file mode 100644
index 0000000000..c93d4257db
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html
@@ -0,0 +1,17 @@
1{% extends "base.html" %}
2{% load projecttags %}
3{% load humanize %}
4{% load static %}
5
6{% block pagecontent %}
7<div class="section">
8</div>
9<div class="row-fluid">
10
11 <div class="alert alert-info">
12 <p class="lead"> The artifact you are seeking to download is not available. We are sorry. You can <a href="javascript:window.history.back()">go back</a>
13 </p>
14 </div>
15</div>
16{% endblock %}
17
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index b5ff9b1d53..eb323ec81d 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2914,9 +2914,6 @@ if toastermain.settings.MANAGED:
2914 if artifact_type == "imagefile": 2914 if artifact_type == "imagefile":
2915 file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name 2915 file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name
2916 2916
2917 elif artifact_type == "cookerlog":
2918 file_name = b.cooker_log_path
2919
2920 elif artifact_type == "buildartifact": 2917 elif artifact_type == "buildartifact":
2921 file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name 2918 file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name
2922 2919
@@ -2935,26 +2932,87 @@ if toastermain.settings.MANAGED:
2935 2932
2936 2933
2937 def build_artifact(request, build_id, artifact_type, artifact_id): 2934 def build_artifact(request, build_id, artifact_type, artifact_id):
2938 b = Build.objects.get(pk=build_id) 2935 if artifact_type in ["cookerlog"]:
2939 if b.buildrequest is None or b.buildrequest.environment is None: 2936 # these artifacts are saved after building, so they are on the server itself
2940 raise Exception("Artifact not available for download (missing build request or build environment)") 2937 def _mimetype_for_artifact(path):
2938 try:
2939 import magic
2940
2941 # fair warning: this is a mess; there are multiple competing and incompatible
2942 # magic modules floating around, so we try some of the most common combinations
2943
2944 try: # we try ubuntu's python-magic 5.4
2945 m = magic.open(magic.MAGIC_MIME_TYPE)
2946 m.load()
2947 return m.file(path)
2948 except AttributeError:
2949 pass
2950
2951 try: # we try python-magic 0.4.6
2952 m = magic.Magic(magic.MAGIC_MIME)
2953 return m.from_file(path)
2954 except AttributeError:
2955 pass
2956
2957 try: # we try pip filemagic 1.6
2958 m = magic.Magic(flags=magic.MAGIC_MIME_TYPE)
2959 return m.id_filename(path)
2960 except AttributeError:
2961 pass
2962
2963 return "binary/octet-stream"
2964 except ImportError:
2965 return "binary/octet-stream"
2966 try:
2967 # match code with runbuilds.Command.archive()
2968 build_artifact_storage_dir = os.path.join(ToasterSetting.objects.get(name="ARTIFACTS_STORAGE_DIR").value, "%d" % int(build_id))
2969 file_name = os.path.join(build_artifact_storage_dir, "cooker_log.txt")
2970
2971 fsock = open(file_name, "r")
2972 content_type=_mimetype_for_artifact(file_name)
2941 2973
2942 file_name = _file_name_for_artifact(b, artifact_type, artifact_id) 2974 response = HttpResponse(fsock, content_type = content_type)
2943 fsock = None 2975
2944 content_type='application/force-download' 2976 response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_name)
2977 return response
2978 except IOError:
2979 context = {
2980 'build' : Build.objects.get(pk = build_id),
2981 }
2982 return render(request, "unavailable_artifact.html", context)
2945 2983
2946 if file_name is None:
2947 raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
2948 else: 2984 else:
2949 content_type = b.buildrequest.environment.get_artifact_type(file_name) 2985 # retrieve the artifact directly from the build environment
2950 fsock = b.buildrequest.environment.get_artifact(file_name) 2986 return _get_be_artifact(request, build_id, artifact_type, artifact_id)
2951 file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
2952 2987
2953 response = HttpResponse(fsock, content_type = content_type)
2954 2988
2955 # returns a file from the environment 2989 def _get_be_artifact(request, build_id, artifact_type, artifact_id):
2956 response['Content-Disposition'] = 'attachment; filename=' + file_name 2990 try:
2957 return response 2991 b = Build.objects.get(pk=build_id)
2992 if b.buildrequest is None or b.buildrequest.environment is None:
2993 raise Exception("Artifact not available for download (missing build request or build environment)")
2994
2995 file_name = _file_name_for_artifact(b, artifact_type, artifact_id)
2996 fsock = None
2997 content_type='application/force-download'
2998
2999 if file_name is None:
3000 raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
3001 else:
3002 content_type = b.buildrequest.environment.get_artifact_type(file_name)
3003 fsock = b.buildrequest.environment.get_artifact(file_name)
3004 file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
3005
3006 response = HttpResponse(fsock, content_type = content_type)
3007
3008 # returns a file from the environment
3009 response['Content-Disposition'] = 'attachment; filename=' + file_name
3010 return response
3011 except IOError:
3012 context = {
3013 'build' : Build.objects.get(pk = build_id),
3014 }
3015 return render(request, "unavailable_artifact.html", context)
2958 3016
2959 3017
2960 3018
diff --git a/bitbake/lib/toaster/toastermain/urls.py b/bitbake/lib/toaster/toastermain/urls.py
index f66f11dcde..395c4e8c34 100644
--- a/bitbake/lib/toaster/toastermain/urls.py
+++ b/bitbake/lib/toaster/toastermain/urls.py
@@ -50,12 +50,12 @@ import toastermain.settings
50 50
51if toastermain.settings.FRESH_ENABLED: 51if toastermain.settings.FRESH_ENABLED:
52 urlpatterns.insert(1, url(r'', include('fresh.urls'))) 52 urlpatterns.insert(1, url(r'', include('fresh.urls')))
53 logger.info("Enabled django-fresh extension") 53 #logger.info("Enabled django-fresh extension")
54 54
55if toastermain.settings.DEBUG_PANEL_ENABLED: 55if toastermain.settings.DEBUG_PANEL_ENABLED:
56 import debug_toolbar 56 import debug_toolbar
57 urlpatterns.insert(1, url(r'', include(debug_toolbar.urls))) 57 urlpatterns.insert(1, url(r'', include(debug_toolbar.urls)))
58 logger.info("Enabled django_toolbar extension") 58 #logger.info("Enabled django_toolbar extension")
59 59
60 60
61if toastermain.settings.MANAGED: 61if toastermain.settings.MANAGED:
@@ -86,4 +86,4 @@ for t in os.walk(os.path.dirname(currentdir)):
86 logger.warn("Module \'%s\' has a regexp conflict, was not added to the urlpatterns" % modulename) 86 logger.warn("Module \'%s\' has a regexp conflict, was not added to the urlpatterns" % modulename)
87 87
88from pprint import pformat 88from pprint import pformat
89logger.debug("urlpatterns list %s", pformat(urlpatterns)) 89#logger.debug("urlpatterns list %s", pformat(urlpatterns))