blob: 74f9d569637137a376a61e48f1912dca5f6695f0 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002# BitBake Toaster Implementation
3#
4# Copyright (C) 2013 Intel Corporation
5#
Brad Bishopc342db32019-05-15 21:57:59 -04006# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008
Patrick Williamsc0f7c042017-02-23 20:41:17 -06009import re
Patrick Williamsc124f4f2015-09-15 14:41:29 -050010
Patrick Williamsc0f7c042017-02-23 20:41:17 -060011from django.db.models import F, Q, Sum
12from django.db import IntegrityError
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050013from django.shortcuts import render, redirect, get_object_or_404
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080014from django.utils.http import urlencode
Patrick Williamsc0f7c042017-02-23 20:41:17 -060015from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
16from orm.models import LogMessage, Variable, Package_Dependency, Package
17from orm.models import Task_Dependency, Package_File
18from orm.models import Target_Installed_Package, Target_File
19from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File
Patrick Williamsf1e5d692016-03-30 15:21:19 -050020from orm.models import BitbakeVersion, CustomImageRecipe
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021
Andrew Geissler82c905d2020-04-13 13:39:40 -050022from django.urls import reverse, resolve
23from django.core.exceptions import ObjectDoesNotExist
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
Brad Bishopd7bf8c12018-02-25 22:55:05 -050025from django.http import HttpResponseNotFound, JsonResponse
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026from django.utils import timezone
Patrick Williamsd7e96312015-09-22 08:09:05 -050027from datetime import timedelta, datetime
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028from toastergui.templatetags.projecttags import json as jsonfilter
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050029from decimal import Decimal
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030import json
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050031import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032from os.path import dirname
Patrick Williamsf1e5d692016-03-30 15:21:19 -050033import mimetypes
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034
35import logging
36
37logger = logging.getLogger("toaster")
38
Brad Bishopd7bf8c12018-02-25 22:55:05 -050039# Project creation and managed build enable
40project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080041is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -060042
Patrick Williamsd7e96312015-09-22 08:09:05 -050043class MimeTypeFinder(object):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050044 # setting this to False enables additional non-standard mimetypes
45 # to be included in the guess
46 _strict = False
Patrick Williamsd7e96312015-09-22 08:09:05 -050047
Patrick Williamsf1e5d692016-03-30 15:21:19 -050048 # returns the mimetype for a file path as a string,
49 # or 'application/octet-stream' if the type couldn't be guessed
Patrick Williamsd7e96312015-09-22 08:09:05 -050050 @classmethod
51 def get_mimetype(self, path):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050052 guess = mimetypes.guess_type(path, self._strict)
53 guessed_type = guess[0]
Andrew Geissler82c905d2020-04-13 13:39:40 -050054 if guessed_type is None:
Patrick Williamsf1e5d692016-03-30 15:21:19 -050055 guessed_type = 'application/octet-stream'
56 return guessed_type
Patrick Williamsd7e96312015-09-22 08:09:05 -050057
Brad Bishopd7bf8c12018-02-25 22:55:05 -050058# single point to add global values into the context before rendering
59def toaster_render(request, page, context):
60 context['project_enable'] = project_enable
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080061 context['project_specific'] = is_project_specific
Brad Bishopd7bf8c12018-02-25 22:55:05 -050062 return render(request, page, context)
63
64
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065# all new sessions should come through the landing page;
66# determine in which mode we are running in, and redirect appropriately
67def landing(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050068 # in build mode, we redirect to the command-line builds page
69 # if there are any builds for the default (cli builds) project
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050070 default_project = Project.objects.get_or_create_default_project()
Patrick Williamsf1e5d692016-03-30 15:21:19 -050071 default_project_builds = Build.objects.filter(project = default_project)
72
Patrick Williamsc124f4f2015-09-15 14:41:29 -050073 # we only redirect to projects page if there is a user-generated project
Patrick Williamsf1e5d692016-03-30 15:21:19 -050074 num_builds = Build.objects.all().count()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050075 user_projects = Project.objects.filter(is_default = False)
76 has_user_project = user_projects.count() > 0
77
Patrick Williamsf1e5d692016-03-30 15:21:19 -050078 if num_builds == 0 and has_user_project:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050079 return redirect(reverse('all-projects'), permanent = False)
80
Patrick Williamsf1e5d692016-03-30 15:21:19 -050081 if num_builds > 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 return redirect(reverse('all-builds'), permanent = False)
83
84 context = {'lvs_nos' : Layer_Version.objects.all().count()}
85
Brad Bishopd7bf8c12018-02-25 22:55:05 -050086 return toaster_render(request, 'landing.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087
Patrick Williamsc124f4f2015-09-15 14:41:29 -050088def objtojson(obj):
89 from django.db.models.query import QuerySet
90 from django.db.models import Model
91
92 if isinstance(obj, datetime):
93 return obj.isoformat()
94 elif isinstance(obj, timedelta):
95 return obj.total_seconds()
96 elif isinstance(obj, QuerySet) or isinstance(obj, set):
97 return list(obj)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050098 elif isinstance(obj, Decimal):
99 return str(obj)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100 elif type(obj).__name__ == "RelatedManager":
101 return [x.pk for x in obj.all()]
102 elif hasattr( obj, '__dict__') and isinstance(obj, Model):
103 d = obj.__dict__
104 nd = dict(d)
105 for di in d.keys():
106 if di.startswith("_"):
107 del nd[di]
108 elif isinstance(d[di], Model):
109 nd[di] = d[di].pk
110 elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
111 nd[di] = getattr(obj, "get_%s_display" % di)()
112 return nd
113 elif isinstance( obj, type(lambda x:x)):
114 import inspect
115 return inspect.getsourcelines(obj)[0]
116 else:
117 raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
118
119
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120def _lv_to_dict(prj, x = None):
121 if x is None:
122 def wrapper(x):
123 return _lv_to_dict(prj, x)
124 return wrapper
125
126 return {"id": x.pk,
127 "name": x.layer.name,
128 "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
Andrew Geissler82c905d2020-04-13 13:39:40 -0500129 "detail": "(%s" % x.layer.vcs_url + (")" if x.release is None else " | "+x.get_vcs_reference()+")"),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500130 "giturl": x.layer.vcs_url,
131 "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
132 "revision" : x.get_vcs_reference(),
133 }
134
135
136def _build_page_range(paginator, index = 1):
137 try:
138 page = paginator.page(index)
139 except PageNotAnInteger:
140 page = paginator.page(1)
141 except EmptyPage:
142 page = paginator.page(paginator.num_pages)
143
144
145 page.page_range = [page.number]
146 crt_range = 0
147 for i in range(1,5):
148 if (page.number + i) <= paginator.num_pages:
149 page.page_range = page.page_range + [ page.number + i]
150 crt_range +=1
151 if (page.number - i) > 0:
152 page.page_range = [page.number -i] + page.page_range
153 crt_range +=1
154 if crt_range == 4:
155 break
156 return page
157
158
159def _verify_parameters(g, mandatory_parameters):
160 miss = []
161 for mp in mandatory_parameters:
162 if not mp in g:
163 miss.append(mp)
164 if len(miss):
165 return miss
166 return None
167
168def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600169 try:
170 from urllib import unquote, urlencode
171 except ImportError:
172 from urllib.parse import unquote, urlencode
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173 url = reverse(view, kwargs=kwargs)
174 params = {}
175 for i in g:
176 params[i] = g[i]
177 for i in mandatory_parameters:
178 if not i in params:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600179 params[i] = unquote(str(mandatory_parameters[i]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600181 return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500182
183class RedirectException(Exception):
184 def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
185 super(RedirectException, self).__init__()
186 self.view = view
187 self.g = g
188 self.mandatory_parameters = mandatory_parameters
189 self.oargs = args
190 self.okwargs = kwargs
191
192 def get_redirect_response(self):
193 return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
194
195FIELD_SEPARATOR = ":"
196AND_VALUE_SEPARATOR = "!"
197OR_VALUE_SEPARATOR = "|"
198DESCENDING = "-"
199
200def __get_q_for_val(name, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600201 if "OR" in value or "AND" in value:
202 result = None
203 for x in value.split("OR"):
204 x = __get_q_for_val(name, x)
205 result = result | x if result else x
206 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207 if "AND" in value:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600208 result = None
209 for x in value.split("AND"):
210 x = __get_q_for_val(name, x)
211 result = result & x if result else x
212 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 if value.startswith("NOT"):
214 value = value[3:]
215 if value == 'None':
216 value = None
217 kwargs = { name : value }
218 return ~Q(**kwargs)
219 else:
220 if value == 'None':
221 value = None
222 kwargs = { name : value }
223 return Q(**kwargs)
224
225def _get_filtering_query(filter_string):
226
227 search_terms = filter_string.split(FIELD_SEPARATOR)
228 and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
229 and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
230
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600231 and_query = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500232 for kv in zip(and_keys, and_values):
233 or_keys = kv[0].split(OR_VALUE_SEPARATOR)
234 or_values = kv[1].split(OR_VALUE_SEPARATOR)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600235 query = None
236 for key, val in zip(or_keys, or_values):
237 x = __get_q_for_val(key, val)
238 query = query | x if query else x
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500239
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600240 and_query = and_query & query if and_query else query
241
242 return and_query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500243
244def _get_toggle_order(request, orderkey, toggle_reverse = False):
245 if toggle_reverse:
246 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
247 else:
248 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
249
250def _get_toggle_order_icon(request, orderkey):
251 if request.GET.get('orderby', "") == "%s:+"%orderkey:
252 return "down"
253 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
254 return "up"
255 else:
256 return None
257
258# we check that the input comes in a valid form that we can recognize
259def _validate_input(field_input, model):
260
261 invalid = None
262
263 if field_input:
264 field_input_list = field_input.split(FIELD_SEPARATOR)
265
266 # Check we have only one colon
267 if len(field_input_list) != 2:
268 invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
269 return None, invalid
270
271 # Check we have an equal number of terms both sides of the colon
272 if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
273 invalid = "Not all arg names got values"
274 return None, invalid + str(field_input_list)
275
276 # Check we are looking for a valid field
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500277 valid_fields = [f.name for f in model._meta.get_fields()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278 for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600279 if True in [field.startswith(x) for x in valid_fields]:
280 break
281 else:
282 return None, (field, valid_fields)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283
284 return field_input, invalid
285
286# uses search_allowed_fields in orm/models.py to create a search query
287# for these fields with the supplied input text
288def _get_search_results(search_term, queryset, model):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600289 search_object = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 for st in search_term.split(" "):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 queries = None
292 for field in model.search_allowed_fields:
293 query = Q(**{field + '__icontains': st})
294 queries = queries | query if queries else query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500295
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600296 search_object = search_object & queries if search_object else queries
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297 queryset = queryset.filter(search_object)
298
299 return queryset
300
301
302# function to extract the search/filter/ordering parameters from the request
303# it uses the request and the model to validate input for the filter and orderby values
304def _search_tuple(request, model):
305 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
306 if invalid:
307 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
308
309 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
310 if invalid:
311 raise BaseException("Invalid filter " + str(invalid))
312
313 search_term = request.GET.get('search', '')
314 return (filter_string, search_term, ordering_string)
315
316
317# returns a lazy-evaluated queryset for a filter/search/order combination
318def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
319 if filter_string:
320 filter_query = _get_filtering_query(filter_string)
321 queryset = queryset.filter(filter_query)
322 else:
323 queryset = queryset.all()
324
325 if search_term:
326 queryset = _get_search_results(search_term, queryset, model)
327
328 if ordering_string:
329 column, order = ordering_string.split(':')
330 if column == re.sub('-','',ordering_secondary):
331 ordering_secondary=''
332 if order.lower() == DESCENDING:
333 column = '-' + column
334 if ordering_secondary:
335 queryset = queryset.order_by(column, ordering_secondary)
336 else:
337 queryset = queryset.order_by(column)
338
339 # insure only distinct records (e.g. from multiple search hits) are returned
340 return queryset.distinct()
341
342# returns the value of entries per page and the name of the applied sorting field.
343# if the value is given explicitly as a GET parameter it will be the first selected,
344# otherwise the cookie value will be used.
345def _get_parameters_values(request, default_count, default_order):
346 current_url = resolve(request.path_info).url_name
347 pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
348 orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
349 return (pagesize, orderby)
350
351
352# set cookies for parameters. this is usefull in case parameters are set
353# manually from the GET values of the link
354def _set_parameters_values(pagesize, orderby, request):
Andrew Geissler82c905d2020-04-13 13:39:40 -0500355 from django.urls import resolve
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500356 current_url = resolve(request.path_info).url_name
357 request.session['%s_count' % current_url] = pagesize
358 request.session['%s_orderby' % current_url] =orderby
359
360# date range: normalize GUI's dd/mm/yyyy to date object
361def _normalize_input_date(date_str,default):
362 date_str=re.sub('/', '-', date_str)
363 # accept dd/mm/yyyy to d/m/yy
364 try:
365 date_in = datetime.strptime(date_str, "%d-%m-%Y")
366 except ValueError:
367 # courtesy try with two digit year
368 try:
369 date_in = datetime.strptime(date_str, "%d-%m-%y")
370 except ValueError:
371 return default
372 date_in = date_in.replace(tzinfo=default.tzinfo)
373 return date_in
374
375# convert and normalize any received date range filter, for example:
376# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
377# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
378def _modify_date_range_filter(filter_string):
379 # was the date range radio button selected?
380 if 0 > filter_string.find('_daterange'):
381 return filter_string,''
382 # normalize GUI dates to database format
383 filter_string = filter_string.replace('_daterange','').replace(':','!');
384 filter_list = filter_string.split('!');
385 if 4 != len(filter_list):
386 return filter_string
387 today = timezone.localtime(timezone.now())
388 date_id = filter_list[1]
389 date_from = _normalize_input_date(filter_list[2],today)
390 date_to = _normalize_input_date(filter_list[3],today)
391 # swap dates if manually set dates are out of order
392 if date_to < date_from:
393 date_to,date_from = date_from,date_to
394 # convert to strings, make 'date_to' inclusive by moving to begining of next day
395 date_from_str = date_from.strftime("%Y-%m-%d")
396 date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
397 filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
398 daterange_selected = re.sub('__.*','', date_id)
399 return filter_string,daterange_selected
400
401def _add_daterange_context(queryset_all, request, daterange_list):
402 # calculate the exact begining of local today and yesterday
403 today_begin = timezone.localtime(timezone.now())
Patrick Williamsd7e96312015-09-22 08:09:05 -0500404 yesterday_begin = today_begin - timedelta(days=1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500405 # add daterange persistent
406 context_date = {}
407 context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
408 context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from'])
409 # calculate the date ranges, avoid second sort for 'created'
410 # fetch the respective max range from the database
411 context_date['daterange_filter']=''
412 for key in daterange_list:
413 queryset_key = queryset_all.order_by(key)
414 try:
415 context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
416 except AttributeError:
417 context_date['dateMin_'+key]=timezone.localtime(timezone.now())
418 try:
419 context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
420 except AttributeError:
421 context_date['dateMax_'+key]=timezone.localtime(timezone.now())
422 return context_date,today_begin,yesterday_begin
423
424
425##
426# build dashboard for a single build, coming in as argument
427# Each build may contain multiple targets and each target
428# may generate multiple image files. display them all.
429#
430def builddashboard( request, build_id ):
431 template = "builddashboard.html"
432 if Build.objects.filter( pk=build_id ).count( ) == 0 :
433 return redirect( builds )
434 build = Build.objects.get( pk = build_id );
435 layerVersionId = Layer_Version.objects.filter( build = build_id );
436 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
437 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
438
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439 # set up custom target list with computed package and image data
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600440 targets = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500441 ntargets = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600442
443 # True if at least one target for this build has an SDK artifact
444 # or image file
445 has_artifacts = False
446
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500447 for t in tgts:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600448 elem = {}
449 elem['target'] = t
450
451 target_has_images = False
452 image_files = []
453
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500454 npkg = 0
455 pkgsz = 0
456 package = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500457 # Chunk the query to avoid "too many SQL variables" error
458 package_set = t.target_installed_package_set.all()
459 package_set_len = len(package_set)
460 for ps_start in range(0,package_set_len,500):
461 ps_stop = min(ps_start+500,package_set_len)
462 for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]):
463 pkgsz = pkgsz + package.size
464 if package.installed_name:
465 npkg = npkg + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600466 elem['npkg'] = npkg
467 elem['pkgsz'] = pkgsz
468 ti = Target_Image_File.objects.filter(target_id = t.id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500469 for i in ti:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600470 ndx = i.file_name.rfind('/')
471 if ndx < 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500472 ndx = 0;
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600473 f = i.file_name[ndx + 1:]
474 image_files.append({
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500475 'id': i.id,
476 'path': f,
477 'size': i.file_size,
478 'suffix': i.suffix
479 })
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600480 if len(image_files) > 0:
481 target_has_images = True
482 elem['targetHasImages'] = target_has_images
483
484 elem['imageFiles'] = image_files
485 elem['target_kernel_artifacts'] = t.targetkernelfile_set.all()
486
487 target_sdk_files = t.targetsdkfile_set.all()
488 target_sdk_artifacts_count = target_sdk_files.count()
489 elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count
490 elem['target_sdk_artifacts'] = target_sdk_files
491
492 if target_has_images or target_sdk_artifacts_count > 0:
493 has_artifacts = True
494
495 targets.append(elem)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496
497 ##
498 # how many packages in this build - ignore anonymous ones
499 #
500
501 packageCount = 0
502 packages = Package.objects.filter( build_id = build_id )
503 for p in packages:
504 if ( p.installed_name ):
505 packageCount = packageCount + 1
506
507 logmessages = list(LogMessage.objects.filter( build = build_id ))
508
509 context = {
510 'build' : build,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500511 'project' : build.project,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600512 'hasArtifacts' : has_artifacts,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500513 'ntargets' : ntargets,
514 'targets' : targets,
515 'recipecount' : recipeCount,
516 'packagecount' : packageCount,
517 'logmessages' : logmessages,
518 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500519 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520
521
522
523def generateCoveredList2( revlist = None ):
524 if not revlist:
525 revlist = []
526 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
527 while len(covered_list):
528 revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
529 if len(revlist) > 0:
530 return revlist
531
532 newlist = _find_task_revdep_list(covered_list)
533
534 revlist = list(set(revlist + newlist))
535 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
536 return revlist
537
538def task( request, build_id, task_id ):
539 template = "task.html"
540 tasks_list = Task.objects.filter( pk=task_id )
541 if tasks_list.count( ) == 0:
542 return redirect( builds )
543 task_object = tasks_list[ 0 ];
544 dependencies = sorted(
545 _find_task_dep( task_object ),
546 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
547 reverse_dependencies = sorted(
548 _find_task_revdep( task_object ),
549 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
550 coveredBy = '';
551 if ( task_object.outcome == Task.OUTCOME_COVERED ):
552# _list = generateCoveredList( task )
553 coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
554 log_head = ''
555 log_body = ''
556 if task_object.outcome == task_object.OUTCOME_FAILED:
557 pass
558
559 uri_list= [ ]
560 variables = Variable.objects.filter(build=build_id)
561 v=variables.filter(variable_name='SSTATE_DIR')
562 if v.count() > 0:
563 uri_list.append(v[0].variable_value)
564 v=variables.filter(variable_name='SSTATE_MIRRORS')
565 if (v.count() > 0):
566 for mirror in v[0].variable_value.split('\\n'):
567 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
568 if len(s):
569 uri_list.append(s)
570
571 context = {
572 'build' : Build.objects.filter( pk = build_id )[ 0 ],
573 'object' : task_object,
574 'task' : task_object,
575 'covered_by' : coveredBy,
576 'deps' : dependencies,
577 'rdeps' : reverse_dependencies,
578 'log_head' : log_head,
579 'log_body' : log_body,
580 'showing_matches' : False,
581 'uri_list' : uri_list,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600582 'task_in_tasks_table_pg': int(task_object.order / 25) + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500583 }
584 if request.GET.get( 'show_matches', "" ):
585 context[ 'showing_matches' ] = True
586 context[ 'matching_tasks' ] = Task.objects.filter(
587 sstate_checksum=task_object.sstate_checksum ).filter(
588 build__completed_on__lt=task_object.build.completed_on).exclude(
589 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
590
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500591 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592
593def recipe(request, build_id, recipe_id, active_tab="1"):
594 template = "recipe.html"
595 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
596 return redirect(builds)
597
598 recipe_object = Recipe.objects.get(pk=recipe_id)
599 layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
600 layer = Layer.objects.get(pk=layer_version.layer_id)
601 tasks_list = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA)
602 package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
603
604 if active_tab != '1' and active_tab != '3' and active_tab != '4' :
605 active_tab = '1'
606 tab_states = {'1': '', '3': '', '4': ''}
607 tab_states[active_tab] = 'active'
608
609 context = {
610 'build' : Build.objects.get(pk=build_id),
611 'object' : recipe_object,
612 'layer_version' : layer_version,
613 'layer' : layer,
614 'tasks' : tasks_list,
615 'package_count' : package_count,
616 'tab_states' : tab_states,
617 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500618 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500619
620def recipe_packages(request, build_id, recipe_id):
621 template = "recipe_packages.html"
622 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
623 return redirect(builds)
624
625 (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
626 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
627 retval = _verify_parameters( request.GET, mandatory_parameters )
628 if retval:
629 return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
630 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
631
632 recipe_object = Recipe.objects.get(pk=recipe_id)
633 queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
634 package_count = queryset.count()
635 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
636
637 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
638
639 context = {
640 'build' : Build.objects.get(pk=build_id),
641 'recipe' : recipe_object,
642 'objects' : packages,
643 'object_count' : package_count,
644 'tablecols':[
645 {
646 'name':'Package',
647 'orderfield': _get_toggle_order(request,"name"),
648 'ordericon': _get_toggle_order_icon(request,"name"),
649 'orderkey': "name",
650 },
651 {
652 'name':'Version',
653 },
654 {
655 'name':'Size',
656 'orderfield': _get_toggle_order(request,"size", True),
657 'ordericon': _get_toggle_order_icon(request,"size"),
658 'orderkey': 'size',
659 'dclass': 'sizecol span2',
660 },
661 ]
662 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500663 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500664 _set_parameters_values(pagesize, orderby, request)
665 return response
666
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667from django.http import HttpResponse
668def xhr_dirinfo(request, build_id, target_id):
669 top = request.GET.get('start', '/')
670 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
671
672from django.utils.functional import Promise
673from django.utils.encoding import force_text
674class LazyEncoder(json.JSONEncoder):
675 def default(self, obj):
676 if isinstance(obj, Promise):
677 return force_text(obj)
678 return super(LazyEncoder, self).default(obj)
679
680from toastergui.templatetags.projecttags import filtered_filesizeformat
681import os
682def _get_dir_entries(build_id, target_id, start):
683 node_str = {
684 Target_File.ITYPE_REGULAR : '-',
685 Target_File.ITYPE_DIRECTORY : 'd',
686 Target_File.ITYPE_SYMLINK : 'l',
687 Target_File.ITYPE_SOCKET : 's',
688 Target_File.ITYPE_FIFO : 'p',
689 Target_File.ITYPE_CHARACTER : 'c',
690 Target_File.ITYPE_BLOCK : 'b',
691 }
692 response = []
693 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
694 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
695 for o in objects:
696 # exclude root inode '/'
697 if o.path == '/':
698 continue
699 try:
700 entry = {}
701 entry['parent'] = start
702 entry['name'] = os.path.basename(o.path)
703 entry['fullpath'] = o.path
704
705 # set defaults, not all dentries have packages
706 entry['installed_package'] = None
707 entry['package_id'] = None
708 entry['package'] = None
709 entry['link_to'] = None
710 if o.inodetype == Target_File.ITYPE_DIRECTORY:
711 entry['isdir'] = 1
712 # is there content in directory
713 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
714 else:
715 entry['isdir'] = 0
716
717 # resolve the file to get the package from the resolved file
718 resolved_id = o.sym_target_id
719 resolved_path = o.path
720 if target_packages.count():
Andrew Geissler82c905d2020-04-13 13:39:40 -0500721 while resolved_id != "" and resolved_id is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500722 tf = Target_File.objects.get(pk=resolved_id)
723 resolved_path = tf.path
724 resolved_id = tf.sym_target_id
725
726 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
727 if thisfile.count():
728 p = Package.objects.get(pk=thisfile[0].package_id)
729 entry['installed_package'] = p.installed_name
730 entry['package_id'] = str(p.id)
731 entry['package'] = p.name
732 # don't use resolved path from above, show immediate link-to
Andrew Geissler82c905d2020-04-13 13:39:40 -0500733 if o.sym_target_id != "" and o.sym_target_id is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500734 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
735 entry['size'] = filtered_filesizeformat(o.size)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500736 if entry['link_to'] is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737 entry['permission'] = node_str[o.inodetype] + o.permission
738 else:
739 entry['permission'] = node_str[o.inodetype] + o.permission
740 entry['owner'] = o.owner
741 entry['group'] = o.group
742 response.append(entry)
743
744 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600745 print("Exception ", e)
746 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747
748 # sort by directories first, then by name
749 rsorted = sorted(response, key=lambda entry : entry['name'])
750 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
751 return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
752
753def dirinfo(request, build_id, target_id, file_path=None):
754 template = "dirinfo.html"
755 objects = _get_dir_entries(build_id, target_id, '/')
756 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
757 dir_list = None
Andrew Geissler82c905d2020-04-13 13:39:40 -0500758 if file_path is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500759 """
760 Link from the included package detail file list page and is
761 requesting opening the dir info to a specific file path.
762 Provide the list of directories to expand and the full path to
763 highlight in the page.
764 """
765 # Aassume target's path separator matches host's, that is, os.sep
766 sep = os.sep
767 dir_list = []
768 head = file_path
769 while head != sep:
770 (head, tail) = os.path.split(head)
771 if head != sep:
772 dir_list.insert(0, head)
773
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500774 build = Build.objects.get(pk=build_id)
775
776 context = { 'build': build,
777 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500778 'target': Target.objects.get(pk=target_id),
779 'packages_sum': packages_sum['installed_size__sum'],
780 'objects': objects,
781 'dir_list': dir_list,
782 'file_path': file_path,
783 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500784 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500785
786def _find_task_dep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600787 tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
788 tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
789 return [x.depends_on for x in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500790
791def _find_task_revdep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600792 tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
793 tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
794
795 # exclude self-dependencies to prevent infinite dependency loop
796 # in generateCoveredList2()
797 tdeps = tdeps.exclude(task=task_object)
798
799 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500800
801def _find_task_revdep_list(tasklist):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600802 tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
803 tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
804
805 # exclude self-dependencies to prevent infinite dependency loop
806 # in generateCoveredList2()
807 tdeps = tdeps.exclude(task=F('depends_on'))
808
809 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810
811def _find_task_provider(task_object):
812 task_revdeps = _find_task_revdep(task_object)
813 for tr in task_revdeps:
814 if tr.outcome != Task.OUTCOME_COVERED:
815 return tr
816 for tr in task_revdeps:
817 trc = _find_task_provider(tr)
818 if trc is not None:
819 return trc
820 return None
821
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500822def configuration(request, build_id):
823 template = 'configuration.html'
824
825 var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
826 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
827 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
828 .values_list('variable_name', 'variable_value'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500829 build = Build.objects.get(pk=build_id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500830 context.update({'objectname': 'configuration',
831 'object_search_display':'variables',
832 'filter_search_display':'variables',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500833 'build': build,
834 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500835 'targets': Target.objects.filter(build=build_id)})
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500836 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500837
838
839def configvars(request, build_id):
840 template = 'configvars.html'
841 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
842 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
843 retval = _verify_parameters( request.GET, mandatory_parameters )
844 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
845 if retval:
846 # if new search, clear the default filter
847 if search_term and len(search_term):
848 mandatory_parameters['filter']=''
849 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
850
851 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
852 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
853 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
854 # remove records where the value is empty AND there are no history files
855 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
856
857 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
858
859 # show all matching files (not just the last one)
860 file_filter= search_term + ":"
861 if filter_string.find('/conf/') > 0:
862 file_filter += 'conf/(local|bblayers).conf'
863 if filter_string.find('conf/machine/') > 0:
864 file_filter += 'conf/machine/'
865 if filter_string.find('conf/distro/') > 0:
866 file_filter += 'conf/distro/'
867 if filter_string.find('/bitbake.conf') > 0:
868 file_filter += '/bitbake.conf'
869 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
870
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500871 build = Build.objects.get(pk=build_id)
872
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500873 context = {
874 'objectname': 'configvars',
875 'object_search_display':'BitBake variables',
876 'filter_search_display':'variables',
877 'file_filter': file_filter,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500878 'build': build,
879 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500880 'objects' : variables,
881 'total_count':queryset_with_search.count(),
882 'default_orderby' : 'variable_name:+',
883 'search_term':search_term,
884 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
885 'tablecols' : [
886 {'name': 'Variable',
887 'qhelp': "BitBake is a generic task executor that considers a list of tasks with dependencies and handles metadata that consists of variables in a certain format that get passed to the tasks",
888 'orderfield': _get_toggle_order(request, "variable_name"),
889 'ordericon':_get_toggle_order_icon(request, "variable_name"),
890 },
891 {'name': 'Value',
892 'qhelp': "The value assigned to the variable",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893 },
894 {'name': 'Set in file',
895 'qhelp': "The last configuration file that touched the variable value",
896 'clclass': 'file', 'hidden' : 0,
897 'orderkey' : 'vhistory__file_name',
898 'filter' : {
899 'class' : 'vhistory__file_name',
900 'label': 'Show:',
901 'options' : [
902 ('Local configuration variables', 'vhistory__file_name__contains:'+build_dir+'/conf/',queryset_with_search.filter(vhistory__file_name__contains=build_dir+'/conf/').count(), 'Select this filter to see variables set by the <code>local.conf</code> and <code>bblayers.conf</code> configuration files inside the <code>/build/conf/</code> directory'),
903 ('Machine configuration variables', 'vhistory__file_name__contains:conf/machine/',queryset_with_search.filter(vhistory__file_name__contains='conf/machine').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/machine/</code> directory'),
904 ('Distro configuration variables', 'vhistory__file_name__contains:conf/distro/',queryset_with_search.filter(vhistory__file_name__contains='conf/distro').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/distro/</code> directory'),
905 ('Layer configuration variables', 'vhistory__file_name__contains:conf/layer.conf',queryset_with_search.filter(vhistory__file_name__contains='conf/layer.conf').count(), 'Select this filter to see variables set by the <code>layer.conf</code> configuration file inside your layers'),
906 ('bitbake.conf variables', 'vhistory__file_name__contains:/bitbake.conf',queryset_with_search.filter(vhistory__file_name__contains='/bitbake.conf').count(), 'Select this filter to see variables set by the <code>bitbake.conf</code> configuration file'),
907 ]
908 },
909 },
910 {'name': 'Description',
911 'qhelp': "A brief explanation of the variable",
912 'clclass': 'description', 'hidden' : 0,
913 'dclass': "span4",
914 'filter' : {
915 'class' : 'description',
916 'label': 'Show:',
917 'options' : [
918 ('Variables with description', 'description__regex:.+', queryset_with_search.filter(description__regex='.+').count(), 'We provide descriptions for the most common BitBake variables. The list of descriptions lives in <code>meta/conf/documentation.conf</code>'),
919 ]
920 },
921 },
922 ],
923 }
924
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500925 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500926 _set_parameters_values(pagesize, orderby, request)
927 return response
928
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500929def bfile(request, build_id, package_id):
930 template = 'bfile.html'
931 files = Package_File.objects.filter(package = package_id)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500932 build = Build.objects.get(pk=build_id)
933 context = {
934 'build': build,
935 'project': build.project,
936 'objects' : files
937 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500938 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500939
940
941# A set of dependency types valid for both included and built package views
942OTHER_DEPENDS_BASE = [
943 Package_Dependency.TYPE_RSUGGESTS,
944 Package_Dependency.TYPE_RPROVIDES,
945 Package_Dependency.TYPE_RREPLACES,
946 Package_Dependency.TYPE_RCONFLICTS,
947 ]
948
949# value for invalid row id
950INVALID_KEY = -1
951
952"""
953Given a package id, target_id retrieves two sets of this image and package's
954dependencies. The return value is a dictionary consisting of two other
955lists: a list of 'runtime' dependencies, that is, having RDEPENDS
956values in source package's recipe, and a list of other dependencies, that is
957the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
958the RRECOMMENDS or TRECOMMENDS value.
959The lists are built in the sort order specified for the package runtime
960dependency views.
961"""
962def _get_package_dependencies(package_id, target_id = INVALID_KEY):
963 runtime_deps = []
964 other_deps = []
965 other_depends_types = OTHER_DEPENDS_BASE
966
967 if target_id != INVALID_KEY :
968 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
969 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
970 else :
971 rdepends_type = Package_Dependency.TYPE_RDEPENDS
972 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
973
974 package = Package.objects.get(pk=package_id)
975 if target_id != INVALID_KEY :
976 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
977 else :
978 alldeps = package.package_dependencies_source.all()
979 for idep in alldeps:
980 dep_package = Package.objects.get(pk=idep.depends_on_id)
981 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
982 if dep_package.version == '' :
983 version = ''
984 else :
985 version = dep_package.version + "-" + dep_package.revision
986 installed = False
987 if target_id != INVALID_KEY :
988 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
989 installed = True
990 dep = {
991 'name' : dep_package.name,
992 'version' : version,
993 'size' : dep_package.size,
994 'dep_type' : idep.dep_type,
995 'dep_type_display' : dep_entry[0].capitalize(),
996 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
997 'depends_on_id' : dep_package.id,
998 'installed' : installed,
999 }
1000
1001 if target_id != INVALID_KEY:
1002 dep['alias'] = _get_package_alias(dep_package)
1003
1004 if idep.dep_type == rdepends_type :
1005 runtime_deps.append(dep)
1006 elif idep.dep_type in other_depends_types :
1007 other_deps.append(dep)
1008
1009 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1010 odep_sorted = sorted(
1011 sorted(other_deps, key=lambda k: k['name']),
1012 key=lambda k: k['dep_type'])
1013 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1014 return retvalues
1015
1016# Return the count of packages dependent on package for this target_id image
1017def _get_package_reverse_dep_count(package, target_id):
1018 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1019
1020# Return the count of the packages that this package_id is dependent on.
1021# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1022# installed, or else RDEPENDS if only built.
1023def _get_package_dependency_count(package, target_id, is_installed):
1024 if is_installed :
1025 return package.package_dependencies_source.filter(target_id__exact = target_id,
1026 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1027 else :
1028 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1029
1030def _get_package_alias(package):
1031 alias = package.installed_name
Andrew Geissler82c905d2020-04-13 13:39:40 -05001032 if alias is not None and alias != '' and alias != package.name:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001033 return alias
1034 else:
1035 return ''
1036
1037def _get_fullpackagespec(package):
1038 r = package.name
Andrew Geissler82c905d2020-04-13 13:39:40 -05001039 version_good = package.version is not None and package.version != ''
1040 revision_good = package.revision is not None and package.revision != ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001041 if version_good or revision_good:
1042 r += '_'
1043 if version_good:
1044 r += package.version
1045 if revision_good:
1046 r += '-'
1047 if revision_good:
1048 r += package.revision
1049 return r
1050
1051def package_built_detail(request, build_id, package_id):
1052 template = "package_built_detail.html"
1053 if Build.objects.filter(pk=build_id).count() == 0 :
1054 return redirect(builds)
1055
1056 # follow convention for pagination w/ search although not used for this view
1057 queryset = Package_File.objects.filter(package_id__exact=package_id)
1058 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1059 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1060 retval = _verify_parameters( request.GET, mandatory_parameters )
1061 if retval:
1062 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1063
1064 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1065 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1066
1067 package = Package.objects.get(pk=package_id)
1068 package.fullpackagespec = _get_fullpackagespec(package)
1069 context = {
1070 'build' : Build.objects.get(pk=build_id),
1071 'package' : package,
1072 'dependency_count' : _get_package_dependency_count(package, -1, False),
1073 'objects' : paths,
1074 'tablecols':[
1075 {
1076 'name':'File',
1077 'orderfield': _get_toggle_order(request, "path"),
1078 'ordericon':_get_toggle_order_icon(request, "path"),
1079 },
1080 {
1081 'name':'Size',
1082 'orderfield': _get_toggle_order(request, "size", True),
1083 'ordericon':_get_toggle_order_icon(request, "size"),
1084 'dclass': 'sizecol span2',
1085 },
1086 ]
1087 }
1088 if paths.all().count() < 2:
1089 context['disable_sort'] = True;
1090
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001091 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001092 _set_parameters_values(pagesize, orderby, request)
1093 return response
1094
1095def package_built_dependencies(request, build_id, package_id):
1096 template = "package_built_dependencies.html"
1097 if Build.objects.filter(pk=build_id).count() == 0 :
1098 return redirect(builds)
1099
1100 package = Package.objects.get(pk=package_id)
1101 package.fullpackagespec = _get_fullpackagespec(package)
1102 dependencies = _get_package_dependencies(package_id)
1103 context = {
1104 'build' : Build.objects.get(pk=build_id),
1105 'package' : package,
1106 'runtime_deps' : dependencies['runtime_deps'],
1107 'other_deps' : dependencies['other_deps'],
1108 'dependency_count' : _get_package_dependency_count(package, -1, False)
1109 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001110 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001111
1112
1113def package_included_detail(request, build_id, target_id, package_id):
1114 template = "package_included_detail.html"
1115 if Build.objects.filter(pk=build_id).count() == 0 :
1116 return redirect(builds)
1117
1118 # follow convention for pagination w/ search although not used for this view
1119 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1120 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1121 retval = _verify_parameters( request.GET, mandatory_parameters )
1122 if retval:
1123 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1124 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1125
1126 queryset = Package_File.objects.filter(package_id__exact=package_id)
1127 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1128
1129 package = Package.objects.get(pk=package_id)
1130 package.fullpackagespec = _get_fullpackagespec(package)
1131 package.alias = _get_package_alias(package)
1132 target = Target.objects.get(pk=target_id)
1133 context = {
1134 'build' : Build.objects.get(pk=build_id),
1135 'target' : target,
1136 'package' : package,
1137 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1138 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1139 'objects': paths,
1140 'tablecols':[
1141 {
1142 'name':'File',
1143 'orderfield': _get_toggle_order(request, "path"),
1144 'ordericon':_get_toggle_order_icon(request, "path"),
1145 },
1146 {
1147 'name':'Size',
1148 'orderfield': _get_toggle_order(request, "size", True),
1149 'ordericon':_get_toggle_order_icon(request, "size"),
1150 'dclass': 'sizecol span2',
1151 },
1152 ]
1153 }
1154 if paths.all().count() < 2:
1155 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001156 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001157 _set_parameters_values(pagesize, orderby, request)
1158 return response
1159
1160def package_included_dependencies(request, build_id, target_id, package_id):
1161 template = "package_included_dependencies.html"
1162 if Build.objects.filter(pk=build_id).count() == 0 :
1163 return redirect(builds)
1164
1165 package = Package.objects.get(pk=package_id)
1166 package.fullpackagespec = _get_fullpackagespec(package)
1167 package.alias = _get_package_alias(package)
1168 target = Target.objects.get(pk=target_id)
1169
1170 dependencies = _get_package_dependencies(package_id, target_id)
1171 context = {
1172 'build' : Build.objects.get(pk=build_id),
1173 'package' : package,
1174 'target' : target,
1175 'runtime_deps' : dependencies['runtime_deps'],
1176 'other_deps' : dependencies['other_deps'],
1177 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1178 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1179 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001180 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001181
1182def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1183 template = "package_included_reverse_dependencies.html"
1184 if Build.objects.filter(pk=build_id).count() == 0 :
1185 return redirect(builds)
1186
1187 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1188 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1189 retval = _verify_parameters( request.GET, mandatory_parameters )
1190 if retval:
1191 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1192 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1193
Andrew Geissler82c905d2020-04-13 13:39:40 -05001194 queryset = Package_Dependency.objects.select_related('depends_on').filter(depends_on=package_id, target_id=target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001195 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1196
1197 package = Package.objects.get(pk=package_id)
1198 package.fullpackagespec = _get_fullpackagespec(package)
1199 package.alias = _get_package_alias(package)
1200 target = Target.objects.get(pk=target_id)
1201 for o in objects:
1202 if o.package.version != '':
1203 o.package.version += '-' + o.package.revision
1204 o.alias = _get_package_alias(o.package)
1205 context = {
1206 'build' : Build.objects.get(pk=build_id),
1207 'package' : package,
1208 'target' : target,
1209 'objects' : objects,
1210 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1211 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1212 'tablecols':[
1213 {
1214 'name':'Package',
1215 'orderfield': _get_toggle_order(request, "package__name"),
1216 'ordericon': _get_toggle_order_icon(request, "package__name"),
1217 },
1218 {
1219 'name':'Version',
1220 },
1221 {
1222 'name':'Size',
1223 'orderfield': _get_toggle_order(request, "package__size", True),
1224 'ordericon': _get_toggle_order_icon(request, "package__size"),
1225 'dclass': 'sizecol span2',
1226 },
1227 ]
1228 }
1229 if objects.all().count() < 2:
1230 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001231 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001232 _set_parameters_values(pagesize, orderby, request)
1233 return response
1234
1235def image_information_dir(request, build_id, target_id, packagefile_id):
1236 # stubbed for now
1237 return redirect(builds)
1238 # the context processor that supplies data used across all the pages
1239
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001240# a context processor which runs on every request; this provides the
1241# projects and non_cli_projects (i.e. projects created by the user)
1242# variables referred to in templates, which used to determine the
1243# visibility of UI elements like the "New build" button
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001244def managedcontextprocessor(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001245 projects = Project.objects.all()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246 ret = {
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001247 "projects": projects,
1248 "non_cli_projects": projects.exclude(is_default=True),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001249 "DEBUG" : toastermain.settings.DEBUG,
1250 "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1251 "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1252 }
1253 return ret
1254
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001255# REST-based API calls to return build/building status to external Toaster
1256# managers and aggregators via JSON
1257
1258def _json_build_status(build_id,extend):
1259 build_stat = None
1260 try:
1261 build = Build.objects.get( pk = build_id )
1262 build_stat = {}
1263 build_stat['id'] = build.id
1264 build_stat['name'] = build.build_name
1265 build_stat['machine'] = build.machine
1266 build_stat['distro'] = build.distro
1267 build_stat['start'] = build.started_on
1268 # look up target name
1269 target= Target.objects.get( build = build )
1270 if target:
1271 if target.task:
1272 build_stat['target'] = '%s:%s' % (target.target,target.task)
1273 else:
1274 build_stat['target'] = '%s' % (target.target)
1275 else:
1276 build_stat['target'] = ''
1277 # look up project name
1278 project = Project.objects.get( build = build )
1279 if project:
1280 build_stat['project'] = project.name
1281 else:
1282 build_stat['project'] = ''
1283 if Build.IN_PROGRESS == build.outcome:
1284 now = timezone.now()
1285 timediff = now - build.started_on
1286 build_stat['seconds']='%.3f' % timediff.total_seconds()
1287 build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone)
1288 build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse)
1289 tf = Task.objects.filter(build = build)
1290 tfc = tf.count()
1291 if tfc > 0:
1292 tfd = tf.exclude(order__isnull=True).count()
1293 else:
1294 tfd = 0
1295 build_stat['task']='%d:%d' % (tfd,tfc)
1296 else:
1297 build_stat['outcome'] = build.get_outcome_text()
1298 timediff = build.completed_on - build.started_on
1299 build_stat['seconds']='%.3f' % timediff.total_seconds()
1300 build_stat['stop'] = build.completed_on
1301 messages = LogMessage.objects.all().filter(build = build)
1302 errors = len(messages.filter(level=LogMessage.ERROR) |
1303 messages.filter(level=LogMessage.EXCEPTION) |
1304 messages.filter(level=LogMessage.CRITICAL))
1305 build_stat['errors'] = errors
1306 warnings = len(messages.filter(level=LogMessage.WARNING))
1307 build_stat['warnings'] = warnings
1308 if extend:
1309 build_stat['cooker_log'] = build.cooker_log_path
1310 except Exception as e:
1311 build_state = str(e)
1312 return build_stat
1313
1314def json_builds(request):
1315 build_table = []
1316 builds = []
1317 try:
1318 builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on")
1319 for build in builds:
1320 build_table.append(_json_build_status(build.id,False))
1321 except Exception as e:
1322 build_table = str(e)
1323 return JsonResponse({'builds' : build_table, 'count' : len(builds)})
1324
1325def json_building(request):
1326 build_table = []
1327 builds = []
1328 try:
1329 builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on")
1330 for build in builds:
1331 build_table.append(_json_build_status(build.id,False))
1332 except Exception as e:
1333 build_table = str(e)
1334 return JsonResponse({'building' : build_table, 'count' : len(builds)})
1335
1336def json_build(request,build_id):
1337 return JsonResponse({'build' : _json_build_status(build_id,True)})
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001338
1339
1340import toastermain.settings
1341
Andrew Geissler82c905d2020-04-13 13:39:40 -05001342from orm.models import Project, ProjectLayer, ProjectVariable
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001343from bldcontrol.models import BuildEnvironment
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001344
1345# we have a set of functions if we're in managed mode, or
1346# a default "page not available" simple functions for interactive mode
1347
1348if True:
1349 from django.contrib.auth.models import User
1350 from django.contrib.auth import authenticate, login
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001351
Andrew Geissler82c905d2020-04-13 13:39:40 -05001352 from orm.models import LayerSource, ToasterSetting, Release
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353
1354 import traceback
1355
1356 class BadParameterException(Exception):
1357 ''' The exception raised on invalid POST requests '''
1358 pass
1359
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001360 # new project
1361 def newproject(request):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001362 if not project_enable:
1363 return redirect( landing )
1364
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001365 template = "newproject.html"
1366 context = {
Andrew Geissler82c905d2020-04-13 13:39:40 -05001367 'email': request.user.email if request.user.is_authenticated else '',
1368 'username': request.user.username if request.user.is_authenticated else '',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001369 'releases': Release.objects.order_by("description"),
1370 }
1371
1372 try:
1373 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1374 except ToasterSetting.DoesNotExist:
1375 pass
1376
1377 if request.method == "GET":
1378 # render new project page
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001379 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001380 elif request.method == "POST":
1381 mandatory_fields = ['projectname', 'ptype']
1382 try:
1383 ptype = request.POST.get('ptype')
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001384 if ptype == "import":
1385 mandatory_fields.append('importdir')
1386 else:
1387 mandatory_fields.append('projectversion')
1388 # make sure we have values for all mandatory_fields
1389 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1390 if missing:
1391 # set alert for missing fields
1392 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
1393
Andrew Geissler82c905d2020-04-13 13:39:40 -05001394 if not request.user.is_authenticated:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001395 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1396 if user is None:
1397 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1398
1399 user = authenticate(username = user.username, password = 'nopass')
1400 login(request, user)
1401
1402 # save the project
1403 if ptype == "import":
1404 if not os.path.isdir('%s/conf' % request.POST['importdir']):
1405 raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir'])
1406 from django.core import management
1407 management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'], interactive=False)
1408 prj = Project.objects.get(name = request.POST['projectname'])
1409 prj.merged_attr = True
1410 prj.save()
1411 else:
1412 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1413 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
1414 prj.user_id = request.user.pk
1415 if 'mergeattr' == request.POST.get('mergeattr', ''):
1416 prj.merged_attr = True
1417 prj.save()
1418
1419 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
1420
1421 except (IntegrityError, BadParameterException) as e:
1422 # fill in page with previously submitted values
1423 for field in mandatory_fields:
1424 context.__setitem__(field, request.POST.get(field, "-- missing"))
1425 if isinstance(e, IntegrityError) and "username" in str(e):
1426 context['alert'] = "Your chosen username is already used"
1427 else:
1428 context['alert'] = str(e)
1429 return toaster_render(request, template, context)
1430
1431 raise Exception("Invalid HTTP method for this page")
1432
1433 # new project
1434 def newproject_specific(request, pid):
1435 if not project_enable:
1436 return redirect( landing )
1437
1438 project = Project.objects.get(pk=pid)
1439 template = "newproject_specific.html"
1440 context = {
Andrew Geissler82c905d2020-04-13 13:39:40 -05001441 'email': request.user.email if request.user.is_authenticated else '',
1442 'username': request.user.username if request.user.is_authenticated else '',
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001443 'releases': Release.objects.order_by("description"),
1444 'projectname': project.name,
1445 'project_pk': project.pk,
1446 }
1447
1448 # WORKAROUND: if we already know release, redirect 'newproject_specific' to 'project_specific'
1449 if '1' == project.get_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE'):
1450 return redirect(reverse(project_specific, args=(project.pk,)))
1451
1452 try:
1453 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1454 except ToasterSetting.DoesNotExist:
1455 pass
1456
1457 if request.method == "GET":
1458 # render new project page
1459 return toaster_render(request, template, context)
1460 elif request.method == "POST":
1461 mandatory_fields = ['projectname', 'ptype']
1462 try:
1463 ptype = request.POST.get('ptype')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001464 if ptype == "build":
1465 mandatory_fields.append('projectversion')
1466 # make sure we have values for all mandatory_fields
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001467 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1468 if missing:
1469 # set alert for missing fields
1470 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001471
Andrew Geissler82c905d2020-04-13 13:39:40 -05001472 if not request.user.is_authenticated:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001473 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1474 if user is None:
1475 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1476
1477 user = authenticate(username = user.username, password = 'nopass')
1478 login(request, user)
1479
1480 # save the project
1481 if ptype == "analysis":
1482 release = None
1483 else:
1484 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1485
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001486 prj = Project.objects.create_project(name = request.POST['projectname'], release = release, existing_project = project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001487 prj.user_id = request.user.pk
1488 prj.save()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001489 return redirect(reverse(project_specific, args=(prj.pk,)) + "?notify=new-project")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001490
1491 except (IntegrityError, BadParameterException) as e:
1492 # fill in page with previously submitted values
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001493 for field in mandatory_fields:
1494 context.__setitem__(field, request.POST.get(field, "-- missing"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001495 if isinstance(e, IntegrityError) and "username" in str(e):
1496 context['alert'] = "Your chosen username is already used"
1497 else:
1498 context['alert'] = str(e)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001499 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001500
1501 raise Exception("Invalid HTTP method for this page")
1502
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001503 # Shows the edit project page
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001504 def project(request, pid):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001505 project = Project.objects.get(pk=pid)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001506
1507 if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
1508 if request.GET:
1509 #Example:request.GET=<QueryDict: {'setMachine': ['qemuarm']}>
1510 params = urlencode(request.GET).replace('%5B%27','').replace('%27%5D','')
1511 return redirect("%s?%s" % (reverse(project_specific, args=(project.pk,)),params))
1512 else:
1513 return redirect(reverse(project_specific, args=(project.pk,)))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001514 context = {"project": project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001515 return toaster_render(request, "project.html", context)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001516
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001517 # Shows the edit project-specific page
1518 def project_specific(request, pid):
1519 project = Project.objects.get(pk=pid)
1520
1521 # Are we refreshing from a successful project specific update clone?
1522 if Project.PROJECT_SPECIFIC_CLONING_SUCCESS == project.get_variable(Project.PROJECT_SPECIFIC_STATUS):
1523 return redirect(reverse(landing_specific,args=(project.pk,)))
1524
1525 context = {
1526 "project": project,
1527 "is_new" : project.get_variable(Project.PROJECT_SPECIFIC_ISNEW),
1528 "default_image_recipe" : project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE),
1529 "mru" : Build.objects.all().filter(project=project,outcome=Build.IN_PROGRESS),
1530 }
1531 if project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0:
1532 context['build_in_progress_none_completed'] = True
1533 else:
1534 context['build_in_progress_none_completed'] = False
1535 return toaster_render(request, "project.html", context)
1536
1537 # perform the final actions for the project specific page
1538 def project_specific_finalize(cmnd, pid):
1539 project = Project.objects.get(pk=pid)
1540 callback = project.get_variable(Project.PROJECT_SPECIFIC_CALLBACK)
1541 if "update" == cmnd:
1542 # Delete all '_PROJECT_PREPARE_' builds
1543 for b in Build.objects.all().filter(project=project):
1544 delete_build = False
1545 for t in b.target_set.all():
1546 if '_PROJECT_PREPARE_' == t.target:
1547 delete_build = True
1548 if delete_build:
1549 from django.core import management
1550 management.call_command('builddelete', str(b.id), interactive=False)
1551 # perform callback at this last moment if defined, in case Toaster gets shutdown next
1552 default_target = project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE)
1553 if callback:
1554 callback = callback.replace("<IMAGE>",default_target)
1555 if "cancel" == cmnd:
1556 if callback:
1557 callback = callback.replace("<IMAGE>","none")
1558 callback = callback.replace("--update","--cancel")
1559 # perform callback at this last moment if defined, in case this Toaster gets shutdown next
1560 ret = ''
1561 if callback:
1562 ret = os.system('bash -c "%s"' % callback)
1563 project.set_variable(Project.PROJECT_SPECIFIC_CALLBACK,'')
1564 # Delete the temp project specific variables
1565 project.set_variable(Project.PROJECT_SPECIFIC_ISNEW,'')
1566 project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_NONE)
1567 # WORKAROUND: Release this workaround flag
1568 project.set_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE','')
1569
1570 # Shows the final landing page for project specific update
1571 def landing_specific(request, pid):
1572 project_specific_finalize("update", pid)
1573 context = {
1574 "install_dir": os.environ['TOASTER_DIR'],
1575 }
1576 return toaster_render(request, "landing_specific.html", context)
1577
1578 # Shows the related landing-specific page
1579 def landing_specific_cancel(request, pid):
1580 project_specific_finalize("cancel", pid)
1581 context = {
1582 "install_dir": os.environ['TOASTER_DIR'],
1583 "status": "cancel",
1584 }
1585 return toaster_render(request, "landing_specific.html", context)
1586
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001587 def jsunittests(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001588 """ Provides a page for the js unit tests """
1589 bbv = BitbakeVersion.objects.filter(branch="master").first()
1590 release = Release.objects.filter(bitbake_version=bbv).first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001591
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001592 name = "_js_unit_test_prj_"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001593
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001594 # If there is an existing project by this name delete it.
1595 # We don't want Lots of duplicates cluttering up the projects.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001596 Project.objects.filter(name=name).delete()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001598 new_project = Project.objects.create_project(name=name,
1599 release=release)
1600 # Add a layer
1601 layer = new_project.get_all_compatible_layer_versions().first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001602
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001603 ProjectLayer.objects.get_or_create(layercommit=layer,
1604 project=new_project)
1605
1606 # make sure we have a machine set for this project
1607 ProjectVariable.objects.get_or_create(project=new_project,
1608 name="MACHINE",
1609 value="qemux86")
1610 context = {'project': new_project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001611 return toaster_render(request, "js-unit-tests.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001612
1613 from django.views.decorators.csrf import csrf_exempt
1614 @csrf_exempt
1615 def xhr_testreleasechange(request, pid):
1616 def response(data):
1617 return HttpResponse(jsonfilter(data),
1618 content_type="application/json")
1619
1620 """ returns layer versions that would be deleted on the new
1621 release__pk """
1622 try:
1623 prj = Project.objects.get(pk = pid)
1624 new_release_id = request.GET['new_release_id']
1625
1626 # If we're already on this project do nothing
1627 if prj.release.pk == int(new_release_id):
1628 return reponse({"error": "ok", "rows": []})
1629
1630 retval = []
1631
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001632 for project in prj.projectlayer_set.all():
1633 release = Release.objects.get(pk = new_release_id)
1634
1635 layer_versions = prj.get_all_compatible_layer_versions()
1636 layer_versions = layer_versions.filter(release = release)
1637 layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
1638
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001639 # there is no layer_version with the new release id,
1640 # and the same name
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001641 if layer_versions.count() < 1:
1642 retval.append(project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001643
1644 return response({"error":"ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001645 "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001646 })
1647
1648 except Exception as e:
1649 return response({"error": str(e) })
1650
1651 def xhr_configvaredit(request, pid):
1652 try:
1653 prj = Project.objects.get(id = pid)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001654 # There are cases where user can add variables which hold values
1655 # like http://, file:/// etc. In such case a simple split(":")
1656 # would fail. One example is SSTATE_MIRRORS variable. So we use
1657 # max_split var to handle them.
1658 max_split = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001659 # add conf variables
1660 if 'configvarAdd' in request.POST:
1661 t=request.POST['configvarAdd'].strip()
1662 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001663 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001664 else:
1665 variable = t
1666 value = ""
1667
1668 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
1669 # change conf variables
1670 if 'configvarChange' in request.POST:
1671 t=request.POST['configvarChange'].strip()
1672 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001673 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001674 else:
1675 variable = t
1676 value = ""
1677
1678 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
1679 pt.value=value
1680 pt.save()
1681 # remove conf variables
1682 if 'configvarDel' in request.POST:
1683 t=request.POST['configvarDel'].strip()
1684 pt = ProjectVariable.objects.get(pk = int(t)).delete()
1685
1686 # return all project settings, filter out blacklist and elsewhere-managed variables
1687 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1688 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1689 for var in vars_managed:
1690 configvars_query = configvars_query.exclude(name = var)
1691 for var in vars_blacklist:
1692 configvars_query = configvars_query.exclude(name = var)
1693
1694 return_data = {
1695 "error": "ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001696 'configvars': [(x.name, x.value, x.pk) for x in configvars_query]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001697 }
1698 try:
1699 return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
1700 except ProjectVariable.DoesNotExist:
1701 pass
1702 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001703 return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
1704 except ProjectVariable.DoesNotExist:
1705 pass
1706 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001707 return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
1708 except ProjectVariable.DoesNotExist:
1709 pass
1710 try:
Patrick Williams213cb262021-08-07 19:21:33 -05001711 return_data['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001712 except ProjectVariable.DoesNotExist:
1713 pass
1714 try:
1715 return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
1716 except ProjectVariable.DoesNotExist:
1717 pass
1718 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001719 return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001720 except ProjectVariable.DoesNotExist:
1721 pass
1722
1723 return HttpResponse(json.dumps( return_data ), content_type = "application/json")
1724
1725 except Exception as e:
1726 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1727
1728
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001729 def customrecipe_download(request, pid, recipe_id):
1730 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1731
1732 file_data = recipe.generate_recipe_file_contents()
1733
1734 response = HttpResponse(file_data, content_type='text/plain')
1735 response['Content-Disposition'] = \
1736 'attachment; filename="%s_%s.bb"' % (recipe.name,
1737 recipe.version)
1738
1739 return response
1740
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001741 def importlayer(request, pid):
1742 template = "importlayer.html"
1743 context = {
1744 'project': Project.objects.get(id=pid),
1745 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001746 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001747
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001748 def layerdetails(request, pid, layerid):
1749 project = Project.objects.get(pk=pid)
1750 layer_version = Layer_Version.objects.get(pk=layerid)
1751
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001752 project_layers = ProjectLayer.objects.filter(
1753 project=project).values_list("layercommit_id",
1754 flat=True)
1755
1756 context = {
1757 'project': project,
1758 'layer_source': LayerSource.types_dict(),
1759 'layerversion': layer_version,
1760 'layerdeps': {
1761 "list": [
1762 {
1763 "id": dep.id,
1764 "name": dep.layer.name,
1765 "layerdetailurl": reverse('layerdetails',
1766 args=(pid, dep.pk)),
1767 "vcs_url": dep.layer.vcs_url,
1768 "vcs_reference": dep.get_vcs_reference()
1769 }
1770 for dep in layer_version.get_alldeps(project.id)]
1771 },
1772 'projectlayers': list(project_layers)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001773 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001774
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001775 return toaster_render(request, 'layerdetails.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001776
1777
1778 def get_project_configvars_context():
1779 # Vars managed outside of this view
1780 vars_managed = {
1781 'MACHINE', 'BBLAYERS'
1782 }
1783
1784 vars_blacklist = {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001785 'PARALLEL_MAKE','BB_NUMBER_THREADS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001786 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001787 'PARALLEL_MAKE','TMPDIR',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001788 'all_proxy','ftp_proxy','http_proxy ','https_proxy'
1789 }
1790
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001791 vars_fstypes = Target_Image_File.SUFFIXES
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001792
1793 return(vars_managed,sorted(vars_fstypes),vars_blacklist)
1794
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001795 def projectconf(request, pid):
1796
1797 try:
1798 prj = Project.objects.get(id = pid)
1799 except Project.DoesNotExist:
1800 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1801
1802 # remove blacklist and externally managed varaibles from this list
1803 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1804 configvars = ProjectVariable.objects.filter(project_id = pid).all()
1805 for var in vars_managed:
1806 configvars = configvars.exclude(name = var)
1807 for var in vars_blacklist:
1808 configvars = configvars.exclude(name = var)
1809
1810 context = {
1811 'project': prj,
1812 'configvars': configvars,
1813 'vars_managed': vars_managed,
1814 'vars_fstypes': vars_fstypes,
1815 'vars_blacklist': vars_blacklist,
1816 }
1817
1818 try:
1819 context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value
1820 context['distro_defined'] = "1"
1821 except ProjectVariable.DoesNotExist:
1822 pass
1823 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001824 if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
1825 be = BuildEnvironment.objects.get(pk = str(1))
1826 dl_dir = os.path.join(dirname(be.builddir), "downloads")
1827 context['dl_dir'] = dl_dir
1828 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
1829 pv.value = dl_dir
1830 pv.save()
1831 else:
1832 context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
1833 context['dl_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001834 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001835 pass
1836 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001837 context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
1838 context['fstypes_defined'] = "1"
1839 except ProjectVariable.DoesNotExist:
1840 pass
1841 try:
Patrick Williams213cb262021-08-07 19:21:33 -05001842 context['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001843 context['image_install_append_defined'] = "1"
1844 except ProjectVariable.DoesNotExist:
1845 pass
1846 try:
1847 context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
1848 context['package_classes_defined'] = "1"
1849 except ProjectVariable.DoesNotExist:
1850 pass
1851 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001852 if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
1853 be = BuildEnvironment.objects.get(pk = str(1))
1854 sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
1855 context['sstate_dir'] = sstate_dir
1856 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
1857 pv.value = sstate_dir
1858 pv.save()
1859 else:
1860 context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
1861 context['sstate_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001862 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001863 pass
1864
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001865 return toaster_render(request, "projectconf.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001866
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001867 def _file_names_for_artifact(build, artifact_type, artifact_id):
1868 """
1869 Return a tuple (file path, file name for the download response) for an
1870 artifact of type artifact_type with ID artifact_id for build; if
1871 artifact type is not supported, returns (None, None)
1872 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001873 file_name = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001874 response_file_name = None
1875
1876 if artifact_type == "cookerlog":
1877 file_name = build.cooker_log_path
1878 response_file_name = "cooker.log"
1879
1880 elif artifact_type == "imagefile":
1881 file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001882
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001883 elif artifact_type == "targetkernelartifact":
1884 target = TargetKernelFile.objects.get(pk=artifact_id)
1885 file_name = target.file_name
1886
1887 elif artifact_type == "targetsdkartifact":
1888 target = TargetSDKFile.objects.get(pk=artifact_id)
1889 file_name = target.file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001890
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001891 elif artifact_type == "licensemanifest":
1892 file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001893
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001894 elif artifact_type == "packagemanifest":
1895 file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path
1896
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001897 elif artifact_type == "tasklogfile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001898 file_name = Task.objects.get(build = build, pk = artifact_id).logfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001899
1900 elif artifact_type == "logmessagefile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001901 file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001902
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001903 if file_name and not response_file_name:
1904 response_file_name = os.path.basename(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001905
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001906 return (file_name, response_file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001907
1908 def build_artifact(request, build_id, artifact_type, artifact_id):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001909 """
1910 View which returns a build artifact file as a response
1911 """
1912 file_name = None
1913 response_file_name = None
1914
1915 try:
1916 build = Build.objects.get(pk = build_id)
1917 file_name, response_file_name = _file_names_for_artifact(
1918 build, artifact_type, artifact_id
1919 )
1920
1921 if file_name and response_file_name:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001922 fsock = open(file_name, "rb")
Patrick Williamsd7e96312015-09-22 08:09:05 -05001923 content_type = MimeTypeFinder.get_mimetype(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001924
1925 response = HttpResponse(fsock, content_type = content_type)
1926
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001927 disposition = "attachment; filename=" + response_file_name
1928 response["Content-Disposition"] = disposition
Patrick Williamsd7e96312015-09-22 08:09:05 -05001929
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001930 return response
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001931 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001932 return toaster_render(request, "unavailable_artifact.html")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001933 except (ObjectDoesNotExist, IOError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001934 return toaster_render(request, "unavailable_artifact.html")
1935