blob: cc8517ba6c787a2ef657b9e88755282b81fb8375 [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
Andrew Geissler20137392023-10-12 04:59:14 -060037from toastermain.logs import log_view_mixin
38
Patrick Williamsc124f4f2015-09-15 14:41:29 -050039logger = logging.getLogger("toaster")
40
Brad Bishopd7bf8c12018-02-25 22:55:05 -050041# Project creation and managed build enable
42project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080043is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -060044
Patrick Williamsd7e96312015-09-22 08:09:05 -050045class MimeTypeFinder(object):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050046 # setting this to False enables additional non-standard mimetypes
47 # to be included in the guess
48 _strict = False
Patrick Williamsd7e96312015-09-22 08:09:05 -050049
Patrick Williamsf1e5d692016-03-30 15:21:19 -050050 # returns the mimetype for a file path as a string,
51 # or 'application/octet-stream' if the type couldn't be guessed
Patrick Williamsd7e96312015-09-22 08:09:05 -050052 @classmethod
53 def get_mimetype(self, path):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050054 guess = mimetypes.guess_type(path, self._strict)
55 guessed_type = guess[0]
Andrew Geissler82c905d2020-04-13 13:39:40 -050056 if guessed_type is None:
Patrick Williamsf1e5d692016-03-30 15:21:19 -050057 guessed_type = 'application/octet-stream'
58 return guessed_type
Patrick Williamsd7e96312015-09-22 08:09:05 -050059
Brad Bishopd7bf8c12018-02-25 22:55:05 -050060# single point to add global values into the context before rendering
Andrew Geissler20137392023-10-12 04:59:14 -060061@log_view_mixin
Brad Bishopd7bf8c12018-02-25 22:55:05 -050062def toaster_render(request, page, context):
63 context['project_enable'] = project_enable
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080064 context['project_specific'] = is_project_specific
Brad Bishopd7bf8c12018-02-25 22:55:05 -050065 return render(request, page, context)
66
67
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068# all new sessions should come through the landing page;
69# determine in which mode we are running in, and redirect appropriately
70def landing(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050071 # in build mode, we redirect to the command-line builds page
72 # if there are any builds for the default (cli builds) project
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050073 default_project = Project.objects.get_or_create_default_project()
Patrick Williamsf1e5d692016-03-30 15:21:19 -050074 default_project_builds = Build.objects.filter(project = default_project)
75
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076 # we only redirect to projects page if there is a user-generated project
Patrick Williamsf1e5d692016-03-30 15:21:19 -050077 num_builds = Build.objects.all().count()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050078 user_projects = Project.objects.filter(is_default = False)
79 has_user_project = user_projects.count() > 0
80
Patrick Williamsf1e5d692016-03-30 15:21:19 -050081 if num_builds == 0 and has_user_project:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 return redirect(reverse('all-projects'), permanent = False)
83
Patrick Williamsf1e5d692016-03-30 15:21:19 -050084 if num_builds > 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050085 return redirect(reverse('all-builds'), permanent = False)
86
87 context = {'lvs_nos' : Layer_Version.objects.all().count()}
88
Brad Bishopd7bf8c12018-02-25 22:55:05 -050089 return toaster_render(request, 'landing.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091def objtojson(obj):
92 from django.db.models.query import QuerySet
93 from django.db.models import Model
94
95 if isinstance(obj, datetime):
96 return obj.isoformat()
97 elif isinstance(obj, timedelta):
98 return obj.total_seconds()
99 elif isinstance(obj, QuerySet) or isinstance(obj, set):
100 return list(obj)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500101 elif isinstance(obj, Decimal):
102 return str(obj)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500103 elif type(obj).__name__ == "RelatedManager":
104 return [x.pk for x in obj.all()]
105 elif hasattr( obj, '__dict__') and isinstance(obj, Model):
106 d = obj.__dict__
107 nd = dict(d)
108 for di in d.keys():
109 if di.startswith("_"):
110 del nd[di]
111 elif isinstance(d[di], Model):
112 nd[di] = d[di].pk
113 elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
114 nd[di] = getattr(obj, "get_%s_display" % di)()
115 return nd
116 elif isinstance( obj, type(lambda x:x)):
117 import inspect
118 return inspect.getsourcelines(obj)[0]
119 else:
120 raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
121
122
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500123def _lv_to_dict(prj, x = None):
124 if x is None:
125 def wrapper(x):
126 return _lv_to_dict(prj, x)
127 return wrapper
128
129 return {"id": x.pk,
130 "name": x.layer.name,
131 "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
Andrew Geissler82c905d2020-04-13 13:39:40 -0500132 "detail": "(%s" % x.layer.vcs_url + (")" if x.release is None else " | "+x.get_vcs_reference()+")"),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500133 "giturl": x.layer.vcs_url,
134 "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
135 "revision" : x.get_vcs_reference(),
136 }
137
138
139def _build_page_range(paginator, index = 1):
140 try:
141 page = paginator.page(index)
142 except PageNotAnInteger:
143 page = paginator.page(1)
144 except EmptyPage:
145 page = paginator.page(paginator.num_pages)
146
147
148 page.page_range = [page.number]
149 crt_range = 0
150 for i in range(1,5):
151 if (page.number + i) <= paginator.num_pages:
152 page.page_range = page.page_range + [ page.number + i]
153 crt_range +=1
154 if (page.number - i) > 0:
155 page.page_range = [page.number -i] + page.page_range
156 crt_range +=1
157 if crt_range == 4:
158 break
159 return page
160
161
162def _verify_parameters(g, mandatory_parameters):
163 miss = []
164 for mp in mandatory_parameters:
165 if not mp in g:
166 miss.append(mp)
167 if len(miss):
168 return miss
169 return None
170
171def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600172 try:
173 from urllib import unquote, urlencode
174 except ImportError:
175 from urllib.parse import unquote, urlencode
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500176 url = reverse(view, kwargs=kwargs)
177 params = {}
178 for i in g:
179 params[i] = g[i]
180 for i in mandatory_parameters:
181 if not i in params:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600182 params[i] = unquote(str(mandatory_parameters[i]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600184 return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500185
186class RedirectException(Exception):
187 def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
188 super(RedirectException, self).__init__()
189 self.view = view
190 self.g = g
191 self.mandatory_parameters = mandatory_parameters
192 self.oargs = args
193 self.okwargs = kwargs
194
195 def get_redirect_response(self):
196 return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
197
198FIELD_SEPARATOR = ":"
199AND_VALUE_SEPARATOR = "!"
200OR_VALUE_SEPARATOR = "|"
201DESCENDING = "-"
202
203def __get_q_for_val(name, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600204 if "OR" in value or "AND" in value:
205 result = None
206 for x in value.split("OR"):
207 x = __get_q_for_val(name, x)
208 result = result | x if result else x
209 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 if "AND" in value:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600211 result = None
212 for x in value.split("AND"):
213 x = __get_q_for_val(name, x)
214 result = result & x if result else x
215 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500216 if value.startswith("NOT"):
217 value = value[3:]
218 if value == 'None':
219 value = None
220 kwargs = { name : value }
221 return ~Q(**kwargs)
222 else:
223 if value == 'None':
224 value = None
225 kwargs = { name : value }
226 return Q(**kwargs)
227
228def _get_filtering_query(filter_string):
229
230 search_terms = filter_string.split(FIELD_SEPARATOR)
231 and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
232 and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
233
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 and_query = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500235 for kv in zip(and_keys, and_values):
236 or_keys = kv[0].split(OR_VALUE_SEPARATOR)
237 or_values = kv[1].split(OR_VALUE_SEPARATOR)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600238 query = None
239 for key, val in zip(or_keys, or_values):
240 x = __get_q_for_val(key, val)
241 query = query | x if query else x
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500242
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600243 and_query = and_query & query if and_query else query
244
245 return and_query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246
247def _get_toggle_order(request, orderkey, toggle_reverse = False):
248 if toggle_reverse:
249 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
250 else:
251 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
252
253def _get_toggle_order_icon(request, orderkey):
254 if request.GET.get('orderby', "") == "%s:+"%orderkey:
255 return "down"
256 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
257 return "up"
258 else:
259 return None
260
261# we check that the input comes in a valid form that we can recognize
262def _validate_input(field_input, model):
263
264 invalid = None
265
266 if field_input:
267 field_input_list = field_input.split(FIELD_SEPARATOR)
268
269 # Check we have only one colon
270 if len(field_input_list) != 2:
271 invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
272 return None, invalid
273
274 # Check we have an equal number of terms both sides of the colon
275 if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
276 invalid = "Not all arg names got values"
277 return None, invalid + str(field_input_list)
278
279 # Check we are looking for a valid field
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500280 valid_fields = [f.name for f in model._meta.get_fields()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500281 for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600282 if True in [field.startswith(x) for x in valid_fields]:
283 break
284 else:
285 return None, (field, valid_fields)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500286
287 return field_input, invalid
288
289# uses search_allowed_fields in orm/models.py to create a search query
290# for these fields with the supplied input text
291def _get_search_results(search_term, queryset, model):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600292 search_object = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500293 for st in search_term.split(" "):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600294 queries = None
295 for field in model.search_allowed_fields:
296 query = Q(**{field + '__icontains': st})
297 queries = queries | query if queries else query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600299 search_object = search_object & queries if search_object else queries
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 queryset = queryset.filter(search_object)
301
302 return queryset
303
304
305# function to extract the search/filter/ordering parameters from the request
306# it uses the request and the model to validate input for the filter and orderby values
307def _search_tuple(request, model):
308 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
309 if invalid:
310 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
311
312 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
313 if invalid:
314 raise BaseException("Invalid filter " + str(invalid))
315
316 search_term = request.GET.get('search', '')
317 return (filter_string, search_term, ordering_string)
318
319
320# returns a lazy-evaluated queryset for a filter/search/order combination
321def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
322 if filter_string:
323 filter_query = _get_filtering_query(filter_string)
324 queryset = queryset.filter(filter_query)
325 else:
326 queryset = queryset.all()
327
328 if search_term:
329 queryset = _get_search_results(search_term, queryset, model)
330
331 if ordering_string:
332 column, order = ordering_string.split(':')
333 if column == re.sub('-','',ordering_secondary):
334 ordering_secondary=''
335 if order.lower() == DESCENDING:
336 column = '-' + column
337 if ordering_secondary:
338 queryset = queryset.order_by(column, ordering_secondary)
339 else:
340 queryset = queryset.order_by(column)
341
342 # insure only distinct records (e.g. from multiple search hits) are returned
343 return queryset.distinct()
344
345# returns the value of entries per page and the name of the applied sorting field.
346# if the value is given explicitly as a GET parameter it will be the first selected,
347# otherwise the cookie value will be used.
348def _get_parameters_values(request, default_count, default_order):
349 current_url = resolve(request.path_info).url_name
350 pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
351 orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
352 return (pagesize, orderby)
353
354
355# set cookies for parameters. this is usefull in case parameters are set
356# manually from the GET values of the link
357def _set_parameters_values(pagesize, orderby, request):
Andrew Geissler82c905d2020-04-13 13:39:40 -0500358 from django.urls import resolve
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359 current_url = resolve(request.path_info).url_name
360 request.session['%s_count' % current_url] = pagesize
361 request.session['%s_orderby' % current_url] =orderby
362
363# date range: normalize GUI's dd/mm/yyyy to date object
364def _normalize_input_date(date_str,default):
365 date_str=re.sub('/', '-', date_str)
366 # accept dd/mm/yyyy to d/m/yy
367 try:
368 date_in = datetime.strptime(date_str, "%d-%m-%Y")
369 except ValueError:
370 # courtesy try with two digit year
371 try:
372 date_in = datetime.strptime(date_str, "%d-%m-%y")
373 except ValueError:
374 return default
375 date_in = date_in.replace(tzinfo=default.tzinfo)
376 return date_in
377
378# convert and normalize any received date range filter, for example:
379# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
380# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
381def _modify_date_range_filter(filter_string):
382 # was the date range radio button selected?
383 if 0 > filter_string.find('_daterange'):
384 return filter_string,''
385 # normalize GUI dates to database format
386 filter_string = filter_string.replace('_daterange','').replace(':','!');
387 filter_list = filter_string.split('!');
388 if 4 != len(filter_list):
389 return filter_string
390 today = timezone.localtime(timezone.now())
391 date_id = filter_list[1]
392 date_from = _normalize_input_date(filter_list[2],today)
393 date_to = _normalize_input_date(filter_list[3],today)
394 # swap dates if manually set dates are out of order
395 if date_to < date_from:
396 date_to,date_from = date_from,date_to
397 # convert to strings, make 'date_to' inclusive by moving to begining of next day
398 date_from_str = date_from.strftime("%Y-%m-%d")
399 date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
400 filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
401 daterange_selected = re.sub('__.*','', date_id)
402 return filter_string,daterange_selected
403
404def _add_daterange_context(queryset_all, request, daterange_list):
405 # calculate the exact begining of local today and yesterday
406 today_begin = timezone.localtime(timezone.now())
Patrick Williamsd7e96312015-09-22 08:09:05 -0500407 yesterday_begin = today_begin - timedelta(days=1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500408 # add daterange persistent
409 context_date = {}
410 context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
411 context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from'])
412 # calculate the date ranges, avoid second sort for 'created'
413 # fetch the respective max range from the database
414 context_date['daterange_filter']=''
415 for key in daterange_list:
416 queryset_key = queryset_all.order_by(key)
417 try:
418 context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
419 except AttributeError:
420 context_date['dateMin_'+key]=timezone.localtime(timezone.now())
421 try:
422 context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
423 except AttributeError:
424 context_date['dateMax_'+key]=timezone.localtime(timezone.now())
425 return context_date,today_begin,yesterday_begin
426
427
428##
429# build dashboard for a single build, coming in as argument
430# Each build may contain multiple targets and each target
431# may generate multiple image files. display them all.
432#
433def builddashboard( request, build_id ):
434 template = "builddashboard.html"
435 if Build.objects.filter( pk=build_id ).count( ) == 0 :
436 return redirect( builds )
437 build = Build.objects.get( pk = build_id );
438 layerVersionId = Layer_Version.objects.filter( build = build_id );
439 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
440 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
441
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442 # set up custom target list with computed package and image data
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600443 targets = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500444 ntargets = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600445
446 # True if at least one target for this build has an SDK artifact
447 # or image file
448 has_artifacts = False
449
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450 for t in tgts:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600451 elem = {}
452 elem['target'] = t
453
454 target_has_images = False
455 image_files = []
456
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500457 npkg = 0
458 pkgsz = 0
459 package = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500460 # Chunk the query to avoid "too many SQL variables" error
461 package_set = t.target_installed_package_set.all()
462 package_set_len = len(package_set)
463 for ps_start in range(0,package_set_len,500):
464 ps_stop = min(ps_start+500,package_set_len)
465 for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]):
466 pkgsz = pkgsz + package.size
467 if package.installed_name:
468 npkg = npkg + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600469 elem['npkg'] = npkg
470 elem['pkgsz'] = pkgsz
471 ti = Target_Image_File.objects.filter(target_id = t.id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500472 for i in ti:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600473 ndx = i.file_name.rfind('/')
474 if ndx < 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500475 ndx = 0;
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600476 f = i.file_name[ndx + 1:]
477 image_files.append({
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500478 'id': i.id,
479 'path': f,
480 'size': i.file_size,
481 'suffix': i.suffix
482 })
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600483 if len(image_files) > 0:
484 target_has_images = True
485 elem['targetHasImages'] = target_has_images
486
487 elem['imageFiles'] = image_files
488 elem['target_kernel_artifacts'] = t.targetkernelfile_set.all()
489
490 target_sdk_files = t.targetsdkfile_set.all()
491 target_sdk_artifacts_count = target_sdk_files.count()
492 elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count
493 elem['target_sdk_artifacts'] = target_sdk_files
494
495 if target_has_images or target_sdk_artifacts_count > 0:
496 has_artifacts = True
497
498 targets.append(elem)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500499
500 ##
501 # how many packages in this build - ignore anonymous ones
502 #
503
504 packageCount = 0
505 packages = Package.objects.filter( build_id = build_id )
506 for p in packages:
507 if ( p.installed_name ):
508 packageCount = packageCount + 1
509
510 logmessages = list(LogMessage.objects.filter( build = build_id ))
511
512 context = {
513 'build' : build,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500514 'project' : build.project,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600515 'hasArtifacts' : has_artifacts,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500516 'ntargets' : ntargets,
517 'targets' : targets,
518 'recipecount' : recipeCount,
519 'packagecount' : packageCount,
520 'logmessages' : logmessages,
521 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500522 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523
524
525
526def generateCoveredList2( revlist = None ):
527 if not revlist:
528 revlist = []
529 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
530 while len(covered_list):
531 revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
532 if len(revlist) > 0:
533 return revlist
534
535 newlist = _find_task_revdep_list(covered_list)
536
537 revlist = list(set(revlist + newlist))
538 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
539 return revlist
540
541def task( request, build_id, task_id ):
542 template = "task.html"
543 tasks_list = Task.objects.filter( pk=task_id )
544 if tasks_list.count( ) == 0:
545 return redirect( builds )
546 task_object = tasks_list[ 0 ];
547 dependencies = sorted(
548 _find_task_dep( task_object ),
549 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
550 reverse_dependencies = sorted(
551 _find_task_revdep( task_object ),
552 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
553 coveredBy = '';
554 if ( task_object.outcome == Task.OUTCOME_COVERED ):
555# _list = generateCoveredList( task )
556 coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
557 log_head = ''
558 log_body = ''
559 if task_object.outcome == task_object.OUTCOME_FAILED:
560 pass
561
562 uri_list= [ ]
563 variables = Variable.objects.filter(build=build_id)
564 v=variables.filter(variable_name='SSTATE_DIR')
565 if v.count() > 0:
566 uri_list.append(v[0].variable_value)
567 v=variables.filter(variable_name='SSTATE_MIRRORS')
568 if (v.count() > 0):
569 for mirror in v[0].variable_value.split('\\n'):
570 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
571 if len(s):
572 uri_list.append(s)
573
574 context = {
575 'build' : Build.objects.filter( pk = build_id )[ 0 ],
576 'object' : task_object,
577 'task' : task_object,
578 'covered_by' : coveredBy,
579 'deps' : dependencies,
580 'rdeps' : reverse_dependencies,
581 'log_head' : log_head,
582 'log_body' : log_body,
583 'showing_matches' : False,
584 'uri_list' : uri_list,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600585 'task_in_tasks_table_pg': int(task_object.order / 25) + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586 }
587 if request.GET.get( 'show_matches', "" ):
588 context[ 'showing_matches' ] = True
589 context[ 'matching_tasks' ] = Task.objects.filter(
590 sstate_checksum=task_object.sstate_checksum ).filter(
591 build__completed_on__lt=task_object.build.completed_on).exclude(
592 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
593
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500594 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595
596def recipe(request, build_id, recipe_id, active_tab="1"):
597 template = "recipe.html"
598 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
599 return redirect(builds)
600
601 recipe_object = Recipe.objects.get(pk=recipe_id)
602 layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
603 layer = Layer.objects.get(pk=layer_version.layer_id)
604 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)
605 package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
606
607 if active_tab != '1' and active_tab != '3' and active_tab != '4' :
608 active_tab = '1'
609 tab_states = {'1': '', '3': '', '4': ''}
610 tab_states[active_tab] = 'active'
611
612 context = {
613 'build' : Build.objects.get(pk=build_id),
614 'object' : recipe_object,
615 'layer_version' : layer_version,
616 'layer' : layer,
617 'tasks' : tasks_list,
618 'package_count' : package_count,
619 'tab_states' : tab_states,
620 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500621 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500622
623def recipe_packages(request, build_id, recipe_id):
624 template = "recipe_packages.html"
625 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
626 return redirect(builds)
627
628 (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
629 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
630 retval = _verify_parameters( request.GET, mandatory_parameters )
631 if retval:
632 return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
633 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
634
635 recipe_object = Recipe.objects.get(pk=recipe_id)
636 queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
637 package_count = queryset.count()
638 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
639
640 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
641
642 context = {
643 'build' : Build.objects.get(pk=build_id),
644 'recipe' : recipe_object,
645 'objects' : packages,
646 'object_count' : package_count,
647 'tablecols':[
648 {
649 'name':'Package',
650 'orderfield': _get_toggle_order(request,"name"),
651 'ordericon': _get_toggle_order_icon(request,"name"),
652 'orderkey': "name",
653 },
654 {
655 'name':'Version',
656 },
657 {
658 'name':'Size',
659 'orderfield': _get_toggle_order(request,"size", True),
660 'ordericon': _get_toggle_order_icon(request,"size"),
661 'orderkey': 'size',
662 'dclass': 'sizecol span2',
663 },
664 ]
665 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500666 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 _set_parameters_values(pagesize, orderby, request)
668 return response
669
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670from django.http import HttpResponse
Andrew Geissler20137392023-10-12 04:59:14 -0600671@log_view_mixin
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500672def xhr_dirinfo(request, build_id, target_id):
673 top = request.GET.get('start', '/')
674 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
675
676from django.utils.functional import Promise
Andrew Geissler5082cc72023-09-11 08:41:39 -0400677from django.utils.encoding import force_str
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500678class LazyEncoder(json.JSONEncoder):
679 def default(self, obj):
680 if isinstance(obj, Promise):
Andrew Geissler5082cc72023-09-11 08:41:39 -0400681 return force_str(obj)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500682 return super(LazyEncoder, self).default(obj)
683
684from toastergui.templatetags.projecttags import filtered_filesizeformat
685import os
686def _get_dir_entries(build_id, target_id, start):
687 node_str = {
688 Target_File.ITYPE_REGULAR : '-',
689 Target_File.ITYPE_DIRECTORY : 'd',
690 Target_File.ITYPE_SYMLINK : 'l',
691 Target_File.ITYPE_SOCKET : 's',
692 Target_File.ITYPE_FIFO : 'p',
693 Target_File.ITYPE_CHARACTER : 'c',
694 Target_File.ITYPE_BLOCK : 'b',
695 }
696 response = []
697 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
698 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
699 for o in objects:
700 # exclude root inode '/'
701 if o.path == '/':
702 continue
703 try:
704 entry = {}
705 entry['parent'] = start
706 entry['name'] = os.path.basename(o.path)
707 entry['fullpath'] = o.path
708
709 # set defaults, not all dentries have packages
710 entry['installed_package'] = None
711 entry['package_id'] = None
712 entry['package'] = None
713 entry['link_to'] = None
714 if o.inodetype == Target_File.ITYPE_DIRECTORY:
715 entry['isdir'] = 1
716 # is there content in directory
717 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
718 else:
719 entry['isdir'] = 0
720
721 # resolve the file to get the package from the resolved file
722 resolved_id = o.sym_target_id
723 resolved_path = o.path
724 if target_packages.count():
Andrew Geissler82c905d2020-04-13 13:39:40 -0500725 while resolved_id != "" and resolved_id is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726 tf = Target_File.objects.get(pk=resolved_id)
727 resolved_path = tf.path
728 resolved_id = tf.sym_target_id
729
730 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
731 if thisfile.count():
732 p = Package.objects.get(pk=thisfile[0].package_id)
733 entry['installed_package'] = p.installed_name
734 entry['package_id'] = str(p.id)
735 entry['package'] = p.name
736 # don't use resolved path from above, show immediate link-to
Andrew Geissler82c905d2020-04-13 13:39:40 -0500737 if o.sym_target_id != "" and o.sym_target_id is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500738 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
739 entry['size'] = filtered_filesizeformat(o.size)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500740 if entry['link_to'] is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500741 entry['permission'] = node_str[o.inodetype] + o.permission
742 else:
743 entry['permission'] = node_str[o.inodetype] + o.permission
744 entry['owner'] = o.owner
745 entry['group'] = o.group
746 response.append(entry)
747
748 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600749 print("Exception ", e)
750 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751
752 # sort by directories first, then by name
753 rsorted = sorted(response, key=lambda entry : entry['name'])
754 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
755 return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
756
757def dirinfo(request, build_id, target_id, file_path=None):
758 template = "dirinfo.html"
759 objects = _get_dir_entries(build_id, target_id, '/')
760 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
761 dir_list = None
Andrew Geissler82c905d2020-04-13 13:39:40 -0500762 if file_path is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500763 """
764 Link from the included package detail file list page and is
765 requesting opening the dir info to a specific file path.
766 Provide the list of directories to expand and the full path to
767 highlight in the page.
768 """
769 # Aassume target's path separator matches host's, that is, os.sep
770 sep = os.sep
771 dir_list = []
772 head = file_path
773 while head != sep:
774 (head, tail) = os.path.split(head)
775 if head != sep:
776 dir_list.insert(0, head)
777
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500778 build = Build.objects.get(pk=build_id)
779
780 context = { 'build': build,
781 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 'target': Target.objects.get(pk=target_id),
783 'packages_sum': packages_sum['installed_size__sum'],
784 'objects': objects,
785 'dir_list': dir_list,
786 'file_path': file_path,
787 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500788 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789
790def _find_task_dep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600791 tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
792 tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
793 return [x.depends_on for x in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500794
795def _find_task_revdep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600796 tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
797 tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
798
799 # exclude self-dependencies to prevent infinite dependency loop
800 # in generateCoveredList2()
801 tdeps = tdeps.exclude(task=task_object)
802
803 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804
805def _find_task_revdep_list(tasklist):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600806 tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
807 tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
808
809 # exclude self-dependencies to prevent infinite dependency loop
810 # in generateCoveredList2()
811 tdeps = tdeps.exclude(task=F('depends_on'))
812
813 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500814
815def _find_task_provider(task_object):
816 task_revdeps = _find_task_revdep(task_object)
817 for tr in task_revdeps:
818 if tr.outcome != Task.OUTCOME_COVERED:
819 return tr
820 for tr in task_revdeps:
821 trc = _find_task_provider(tr)
822 if trc is not None:
823 return trc
824 return None
825
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500826def configuration(request, build_id):
827 template = 'configuration.html'
828
829 var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
830 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
831 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
832 .values_list('variable_name', 'variable_value'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500833 build = Build.objects.get(pk=build_id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834 context.update({'objectname': 'configuration',
835 'object_search_display':'variables',
836 'filter_search_display':'variables',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500837 'build': build,
838 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500839 'targets': Target.objects.filter(build=build_id)})
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500840 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500841
842
843def configvars(request, build_id):
844 template = 'configvars.html'
845 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
846 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
847 retval = _verify_parameters( request.GET, mandatory_parameters )
848 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
849 if retval:
850 # if new search, clear the default filter
851 if search_term and len(search_term):
852 mandatory_parameters['filter']=''
853 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
854
855 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
856 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
857 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
858 # remove records where the value is empty AND there are no history files
859 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
860
861 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
862
863 # show all matching files (not just the last one)
864 file_filter= search_term + ":"
865 if filter_string.find('/conf/') > 0:
866 file_filter += 'conf/(local|bblayers).conf'
867 if filter_string.find('conf/machine/') > 0:
868 file_filter += 'conf/machine/'
869 if filter_string.find('conf/distro/') > 0:
870 file_filter += 'conf/distro/'
871 if filter_string.find('/bitbake.conf') > 0:
872 file_filter += '/bitbake.conf'
873 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
874
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500875 build = Build.objects.get(pk=build_id)
876
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500877 context = {
878 'objectname': 'configvars',
879 'object_search_display':'BitBake variables',
880 'filter_search_display':'variables',
881 'file_filter': file_filter,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500882 'build': build,
883 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500884 'objects' : variables,
885 'total_count':queryset_with_search.count(),
886 'default_orderby' : 'variable_name:+',
887 'search_term':search_term,
888 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
889 'tablecols' : [
890 {'name': 'Variable',
891 '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",
892 'orderfield': _get_toggle_order(request, "variable_name"),
893 'ordericon':_get_toggle_order_icon(request, "variable_name"),
894 },
895 {'name': 'Value',
896 'qhelp': "The value assigned to the variable",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500897 },
898 {'name': 'Set in file',
899 'qhelp': "The last configuration file that touched the variable value",
900 'clclass': 'file', 'hidden' : 0,
901 'orderkey' : 'vhistory__file_name',
902 'filter' : {
903 'class' : 'vhistory__file_name',
904 'label': 'Show:',
905 'options' : [
906 ('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'),
907 ('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'),
908 ('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'),
909 ('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'),
910 ('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'),
911 ]
912 },
913 },
914 {'name': 'Description',
915 'qhelp': "A brief explanation of the variable",
916 'clclass': 'description', 'hidden' : 0,
917 'dclass': "span4",
918 'filter' : {
919 'class' : 'description',
920 'label': 'Show:',
921 'options' : [
922 ('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>'),
923 ]
924 },
925 },
926 ],
927 }
928
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500929 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500930 _set_parameters_values(pagesize, orderby, request)
931 return response
932
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500933def bfile(request, build_id, package_id):
934 template = 'bfile.html'
935 files = Package_File.objects.filter(package = package_id)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500936 build = Build.objects.get(pk=build_id)
937 context = {
938 'build': build,
939 'project': build.project,
940 'objects' : files
941 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500942 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500943
944
945# A set of dependency types valid for both included and built package views
946OTHER_DEPENDS_BASE = [
947 Package_Dependency.TYPE_RSUGGESTS,
948 Package_Dependency.TYPE_RPROVIDES,
949 Package_Dependency.TYPE_RREPLACES,
950 Package_Dependency.TYPE_RCONFLICTS,
951 ]
952
953# value for invalid row id
954INVALID_KEY = -1
955
956"""
957Given a package id, target_id retrieves two sets of this image and package's
958dependencies. The return value is a dictionary consisting of two other
959lists: a list of 'runtime' dependencies, that is, having RDEPENDS
960values in source package's recipe, and a list of other dependencies, that is
961the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
962the RRECOMMENDS or TRECOMMENDS value.
963The lists are built in the sort order specified for the package runtime
964dependency views.
965"""
966def _get_package_dependencies(package_id, target_id = INVALID_KEY):
967 runtime_deps = []
968 other_deps = []
969 other_depends_types = OTHER_DEPENDS_BASE
970
971 if target_id != INVALID_KEY :
972 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
973 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
974 else :
975 rdepends_type = Package_Dependency.TYPE_RDEPENDS
976 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
977
978 package = Package.objects.get(pk=package_id)
979 if target_id != INVALID_KEY :
980 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
981 else :
982 alldeps = package.package_dependencies_source.all()
983 for idep in alldeps:
984 dep_package = Package.objects.get(pk=idep.depends_on_id)
985 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
986 if dep_package.version == '' :
987 version = ''
988 else :
989 version = dep_package.version + "-" + dep_package.revision
990 installed = False
991 if target_id != INVALID_KEY :
992 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
993 installed = True
994 dep = {
995 'name' : dep_package.name,
996 'version' : version,
997 'size' : dep_package.size,
998 'dep_type' : idep.dep_type,
999 'dep_type_display' : dep_entry[0].capitalize(),
1000 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
1001 'depends_on_id' : dep_package.id,
1002 'installed' : installed,
1003 }
1004
1005 if target_id != INVALID_KEY:
1006 dep['alias'] = _get_package_alias(dep_package)
1007
1008 if idep.dep_type == rdepends_type :
1009 runtime_deps.append(dep)
1010 elif idep.dep_type in other_depends_types :
1011 other_deps.append(dep)
1012
1013 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1014 odep_sorted = sorted(
1015 sorted(other_deps, key=lambda k: k['name']),
1016 key=lambda k: k['dep_type'])
1017 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1018 return retvalues
1019
1020# Return the count of packages dependent on package for this target_id image
1021def _get_package_reverse_dep_count(package, target_id):
1022 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1023
1024# Return the count of the packages that this package_id is dependent on.
1025# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1026# installed, or else RDEPENDS if only built.
1027def _get_package_dependency_count(package, target_id, is_installed):
1028 if is_installed :
1029 return package.package_dependencies_source.filter(target_id__exact = target_id,
1030 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1031 else :
1032 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1033
1034def _get_package_alias(package):
1035 alias = package.installed_name
Andrew Geissler82c905d2020-04-13 13:39:40 -05001036 if alias is not None and alias != '' and alias != package.name:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001037 return alias
1038 else:
1039 return ''
1040
1041def _get_fullpackagespec(package):
1042 r = package.name
Andrew Geissler82c905d2020-04-13 13:39:40 -05001043 version_good = package.version is not None and package.version != ''
1044 revision_good = package.revision is not None and package.revision != ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001045 if version_good or revision_good:
1046 r += '_'
1047 if version_good:
1048 r += package.version
1049 if revision_good:
1050 r += '-'
1051 if revision_good:
1052 r += package.revision
1053 return r
1054
1055def package_built_detail(request, build_id, package_id):
1056 template = "package_built_detail.html"
1057 if Build.objects.filter(pk=build_id).count() == 0 :
1058 return redirect(builds)
1059
1060 # follow convention for pagination w/ search although not used for this view
1061 queryset = Package_File.objects.filter(package_id__exact=package_id)
1062 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1063 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1064 retval = _verify_parameters( request.GET, mandatory_parameters )
1065 if retval:
1066 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1067
1068 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1069 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1070
1071 package = Package.objects.get(pk=package_id)
1072 package.fullpackagespec = _get_fullpackagespec(package)
1073 context = {
1074 'build' : Build.objects.get(pk=build_id),
1075 'package' : package,
1076 'dependency_count' : _get_package_dependency_count(package, -1, False),
1077 'objects' : paths,
1078 'tablecols':[
1079 {
1080 'name':'File',
1081 'orderfield': _get_toggle_order(request, "path"),
1082 'ordericon':_get_toggle_order_icon(request, "path"),
1083 },
1084 {
1085 'name':'Size',
1086 'orderfield': _get_toggle_order(request, "size", True),
1087 'ordericon':_get_toggle_order_icon(request, "size"),
1088 'dclass': 'sizecol span2',
1089 },
1090 ]
1091 }
1092 if paths.all().count() < 2:
1093 context['disable_sort'] = True;
1094
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001095 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001096 _set_parameters_values(pagesize, orderby, request)
1097 return response
1098
1099def package_built_dependencies(request, build_id, package_id):
1100 template = "package_built_dependencies.html"
1101 if Build.objects.filter(pk=build_id).count() == 0 :
1102 return redirect(builds)
1103
1104 package = Package.objects.get(pk=package_id)
1105 package.fullpackagespec = _get_fullpackagespec(package)
1106 dependencies = _get_package_dependencies(package_id)
1107 context = {
1108 'build' : Build.objects.get(pk=build_id),
1109 'package' : package,
1110 'runtime_deps' : dependencies['runtime_deps'],
1111 'other_deps' : dependencies['other_deps'],
1112 'dependency_count' : _get_package_dependency_count(package, -1, False)
1113 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001114 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001115
1116
1117def package_included_detail(request, build_id, target_id, package_id):
1118 template = "package_included_detail.html"
1119 if Build.objects.filter(pk=build_id).count() == 0 :
1120 return redirect(builds)
1121
1122 # follow convention for pagination w/ search although not used for this view
1123 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1124 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1125 retval = _verify_parameters( request.GET, mandatory_parameters )
1126 if retval:
1127 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1128 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1129
1130 queryset = Package_File.objects.filter(package_id__exact=package_id)
1131 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1132
1133 package = Package.objects.get(pk=package_id)
1134 package.fullpackagespec = _get_fullpackagespec(package)
1135 package.alias = _get_package_alias(package)
1136 target = Target.objects.get(pk=target_id)
1137 context = {
1138 'build' : Build.objects.get(pk=build_id),
1139 'target' : target,
1140 'package' : package,
1141 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1142 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1143 'objects': paths,
1144 'tablecols':[
1145 {
1146 'name':'File',
1147 'orderfield': _get_toggle_order(request, "path"),
1148 'ordericon':_get_toggle_order_icon(request, "path"),
1149 },
1150 {
1151 'name':'Size',
1152 'orderfield': _get_toggle_order(request, "size", True),
1153 'ordericon':_get_toggle_order_icon(request, "size"),
1154 'dclass': 'sizecol span2',
1155 },
1156 ]
1157 }
1158 if paths.all().count() < 2:
1159 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001160 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001161 _set_parameters_values(pagesize, orderby, request)
1162 return response
1163
1164def package_included_dependencies(request, build_id, target_id, package_id):
1165 template = "package_included_dependencies.html"
1166 if Build.objects.filter(pk=build_id).count() == 0 :
1167 return redirect(builds)
1168
1169 package = Package.objects.get(pk=package_id)
1170 package.fullpackagespec = _get_fullpackagespec(package)
1171 package.alias = _get_package_alias(package)
1172 target = Target.objects.get(pk=target_id)
1173
1174 dependencies = _get_package_dependencies(package_id, target_id)
1175 context = {
1176 'build' : Build.objects.get(pk=build_id),
1177 'package' : package,
1178 'target' : target,
1179 'runtime_deps' : dependencies['runtime_deps'],
1180 'other_deps' : dependencies['other_deps'],
1181 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1182 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1183 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001184 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001185
1186def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1187 template = "package_included_reverse_dependencies.html"
1188 if Build.objects.filter(pk=build_id).count() == 0 :
1189 return redirect(builds)
1190
1191 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1192 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1193 retval = _verify_parameters( request.GET, mandatory_parameters )
1194 if retval:
1195 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1196 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1197
Andrew Geissler82c905d2020-04-13 13:39:40 -05001198 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 -05001199 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1200
1201 package = Package.objects.get(pk=package_id)
1202 package.fullpackagespec = _get_fullpackagespec(package)
1203 package.alias = _get_package_alias(package)
1204 target = Target.objects.get(pk=target_id)
1205 for o in objects:
1206 if o.package.version != '':
1207 o.package.version += '-' + o.package.revision
1208 o.alias = _get_package_alias(o.package)
1209 context = {
1210 'build' : Build.objects.get(pk=build_id),
1211 'package' : package,
1212 'target' : target,
1213 'objects' : objects,
1214 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1215 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1216 'tablecols':[
1217 {
1218 'name':'Package',
1219 'orderfield': _get_toggle_order(request, "package__name"),
1220 'ordericon': _get_toggle_order_icon(request, "package__name"),
1221 },
1222 {
1223 'name':'Version',
1224 },
1225 {
1226 'name':'Size',
1227 'orderfield': _get_toggle_order(request, "package__size", True),
1228 'ordericon': _get_toggle_order_icon(request, "package__size"),
1229 'dclass': 'sizecol span2',
1230 },
1231 ]
1232 }
1233 if objects.all().count() < 2:
1234 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001235 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001236 _set_parameters_values(pagesize, orderby, request)
1237 return response
1238
1239def image_information_dir(request, build_id, target_id, packagefile_id):
1240 # stubbed for now
1241 return redirect(builds)
1242 # the context processor that supplies data used across all the pages
1243
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001244# a context processor which runs on every request; this provides the
1245# projects and non_cli_projects (i.e. projects created by the user)
1246# variables referred to in templates, which used to determine the
1247# visibility of UI elements like the "New build" button
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001248def managedcontextprocessor(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001249 projects = Project.objects.all()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001250 ret = {
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001251 "projects": projects,
1252 "non_cli_projects": projects.exclude(is_default=True),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001253 "DEBUG" : toastermain.settings.DEBUG,
1254 "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1255 "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1256 }
1257 return ret
1258
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001259# REST-based API calls to return build/building status to external Toaster
1260# managers and aggregators via JSON
1261
1262def _json_build_status(build_id,extend):
1263 build_stat = None
1264 try:
1265 build = Build.objects.get( pk = build_id )
1266 build_stat = {}
1267 build_stat['id'] = build.id
1268 build_stat['name'] = build.build_name
1269 build_stat['machine'] = build.machine
1270 build_stat['distro'] = build.distro
1271 build_stat['start'] = build.started_on
1272 # look up target name
1273 target= Target.objects.get( build = build )
1274 if target:
1275 if target.task:
1276 build_stat['target'] = '%s:%s' % (target.target,target.task)
1277 else:
1278 build_stat['target'] = '%s' % (target.target)
1279 else:
1280 build_stat['target'] = ''
1281 # look up project name
1282 project = Project.objects.get( build = build )
1283 if project:
1284 build_stat['project'] = project.name
1285 else:
1286 build_stat['project'] = ''
1287 if Build.IN_PROGRESS == build.outcome:
1288 now = timezone.now()
1289 timediff = now - build.started_on
1290 build_stat['seconds']='%.3f' % timediff.total_seconds()
1291 build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone)
1292 build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse)
1293 tf = Task.objects.filter(build = build)
1294 tfc = tf.count()
1295 if tfc > 0:
1296 tfd = tf.exclude(order__isnull=True).count()
1297 else:
1298 tfd = 0
1299 build_stat['task']='%d:%d' % (tfd,tfc)
1300 else:
1301 build_stat['outcome'] = build.get_outcome_text()
1302 timediff = build.completed_on - build.started_on
1303 build_stat['seconds']='%.3f' % timediff.total_seconds()
1304 build_stat['stop'] = build.completed_on
1305 messages = LogMessage.objects.all().filter(build = build)
1306 errors = len(messages.filter(level=LogMessage.ERROR) |
1307 messages.filter(level=LogMessage.EXCEPTION) |
1308 messages.filter(level=LogMessage.CRITICAL))
1309 build_stat['errors'] = errors
1310 warnings = len(messages.filter(level=LogMessage.WARNING))
1311 build_stat['warnings'] = warnings
1312 if extend:
1313 build_stat['cooker_log'] = build.cooker_log_path
1314 except Exception as e:
1315 build_state = str(e)
1316 return build_stat
1317
1318def json_builds(request):
1319 build_table = []
1320 builds = []
1321 try:
1322 builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on")
1323 for build in builds:
1324 build_table.append(_json_build_status(build.id,False))
1325 except Exception as e:
1326 build_table = str(e)
1327 return JsonResponse({'builds' : build_table, 'count' : len(builds)})
1328
1329def json_building(request):
1330 build_table = []
1331 builds = []
1332 try:
1333 builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on")
1334 for build in builds:
1335 build_table.append(_json_build_status(build.id,False))
1336 except Exception as e:
1337 build_table = str(e)
1338 return JsonResponse({'building' : build_table, 'count' : len(builds)})
1339
1340def json_build(request,build_id):
1341 return JsonResponse({'build' : _json_build_status(build_id,True)})
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001342
1343
1344import toastermain.settings
1345
Andrew Geissler82c905d2020-04-13 13:39:40 -05001346from orm.models import Project, ProjectLayer, ProjectVariable
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001347from bldcontrol.models import BuildEnvironment
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001348
1349# we have a set of functions if we're in managed mode, or
1350# a default "page not available" simple functions for interactive mode
1351
1352if True:
1353 from django.contrib.auth.models import User
1354 from django.contrib.auth import authenticate, login
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001355
Andrew Geissler82c905d2020-04-13 13:39:40 -05001356 from orm.models import LayerSource, ToasterSetting, Release
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001357
1358 import traceback
1359
1360 class BadParameterException(Exception):
1361 ''' The exception raised on invalid POST requests '''
1362 pass
1363
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001364 # new project
1365 def newproject(request):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001366 if not project_enable:
1367 return redirect( landing )
1368
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001369 template = "newproject.html"
1370 context = {
Andrew Geissler82c905d2020-04-13 13:39:40 -05001371 'email': request.user.email if request.user.is_authenticated else '',
1372 'username': request.user.username if request.user.is_authenticated else '',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001373 'releases': Release.objects.order_by("description"),
1374 }
1375
1376 try:
1377 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1378 except ToasterSetting.DoesNotExist:
1379 pass
1380
1381 if request.method == "GET":
1382 # render new project page
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001383 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001384 elif request.method == "POST":
1385 mandatory_fields = ['projectname', 'ptype']
1386 try:
1387 ptype = request.POST.get('ptype')
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001388 if ptype == "import":
1389 mandatory_fields.append('importdir')
1390 else:
1391 mandatory_fields.append('projectversion')
1392 # make sure we have values for all mandatory_fields
1393 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1394 if missing:
1395 # set alert for missing fields
1396 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
1397
Andrew Geissler82c905d2020-04-13 13:39:40 -05001398 if not request.user.is_authenticated:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001399 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1400 if user is None:
1401 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1402
1403 user = authenticate(username = user.username, password = 'nopass')
1404 login(request, user)
1405
1406 # save the project
1407 if ptype == "import":
1408 if not os.path.isdir('%s/conf' % request.POST['importdir']):
1409 raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir'])
1410 from django.core import management
Andrew Geissler5082cc72023-09-11 08:41:39 -04001411 management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'])
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001412 prj = Project.objects.get(name = request.POST['projectname'])
1413 prj.merged_attr = True
1414 prj.save()
1415 else:
1416 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1417 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
1418 prj.user_id = request.user.pk
1419 if 'mergeattr' == request.POST.get('mergeattr', ''):
1420 prj.merged_attr = True
1421 prj.save()
1422
1423 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
1424
1425 except (IntegrityError, BadParameterException) as e:
1426 # fill in page with previously submitted values
1427 for field in mandatory_fields:
1428 context.__setitem__(field, request.POST.get(field, "-- missing"))
1429 if isinstance(e, IntegrityError) and "username" in str(e):
1430 context['alert'] = "Your chosen username is already used"
1431 else:
1432 context['alert'] = str(e)
1433 return toaster_render(request, template, context)
1434
1435 raise Exception("Invalid HTTP method for this page")
1436
1437 # new project
1438 def newproject_specific(request, pid):
1439 if not project_enable:
1440 return redirect( landing )
1441
1442 project = Project.objects.get(pk=pid)
1443 template = "newproject_specific.html"
1444 context = {
Andrew Geissler82c905d2020-04-13 13:39:40 -05001445 'email': request.user.email if request.user.is_authenticated else '',
1446 'username': request.user.username if request.user.is_authenticated else '',
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001447 'releases': Release.objects.order_by("description"),
1448 'projectname': project.name,
1449 'project_pk': project.pk,
1450 }
1451
1452 # WORKAROUND: if we already know release, redirect 'newproject_specific' to 'project_specific'
1453 if '1' == project.get_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE'):
1454 return redirect(reverse(project_specific, args=(project.pk,)))
1455
1456 try:
1457 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1458 except ToasterSetting.DoesNotExist:
1459 pass
1460
1461 if request.method == "GET":
1462 # render new project page
1463 return toaster_render(request, template, context)
1464 elif request.method == "POST":
1465 mandatory_fields = ['projectname', 'ptype']
1466 try:
1467 ptype = request.POST.get('ptype')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001468 if ptype == "build":
1469 mandatory_fields.append('projectversion')
1470 # make sure we have values for all mandatory_fields
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001471 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1472 if missing:
1473 # set alert for missing fields
1474 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001475
Andrew Geissler82c905d2020-04-13 13:39:40 -05001476 if not request.user.is_authenticated:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1478 if user is None:
1479 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1480
1481 user = authenticate(username = user.username, password = 'nopass')
1482 login(request, user)
1483
1484 # save the project
1485 if ptype == "analysis":
1486 release = None
1487 else:
1488 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1489
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001490 prj = Project.objects.create_project(name = request.POST['projectname'], release = release, existing_project = project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001491 prj.user_id = request.user.pk
1492 prj.save()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001493 return redirect(reverse(project_specific, args=(prj.pk,)) + "?notify=new-project")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001494
1495 except (IntegrityError, BadParameterException) as e:
1496 # fill in page with previously submitted values
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001497 for field in mandatory_fields:
1498 context.__setitem__(field, request.POST.get(field, "-- missing"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001499 if isinstance(e, IntegrityError) and "username" in str(e):
1500 context['alert'] = "Your chosen username is already used"
1501 else:
1502 context['alert'] = str(e)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001503 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001504
1505 raise Exception("Invalid HTTP method for this page")
1506
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507 # Shows the edit project page
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001508 def project(request, pid):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001509 project = Project.objects.get(pk=pid)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001510
1511 if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
1512 if request.GET:
1513 #Example:request.GET=<QueryDict: {'setMachine': ['qemuarm']}>
1514 params = urlencode(request.GET).replace('%5B%27','').replace('%27%5D','')
1515 return redirect("%s?%s" % (reverse(project_specific, args=(project.pk,)),params))
1516 else:
1517 return redirect(reverse(project_specific, args=(project.pk,)))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001518 context = {"project": project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001519 return toaster_render(request, "project.html", context)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001520
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001521 # Shows the edit project-specific page
1522 def project_specific(request, pid):
1523 project = Project.objects.get(pk=pid)
1524
1525 # Are we refreshing from a successful project specific update clone?
1526 if Project.PROJECT_SPECIFIC_CLONING_SUCCESS == project.get_variable(Project.PROJECT_SPECIFIC_STATUS):
1527 return redirect(reverse(landing_specific,args=(project.pk,)))
1528
1529 context = {
1530 "project": project,
1531 "is_new" : project.get_variable(Project.PROJECT_SPECIFIC_ISNEW),
1532 "default_image_recipe" : project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE),
1533 "mru" : Build.objects.all().filter(project=project,outcome=Build.IN_PROGRESS),
1534 }
1535 if project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0:
1536 context['build_in_progress_none_completed'] = True
1537 else:
1538 context['build_in_progress_none_completed'] = False
1539 return toaster_render(request, "project.html", context)
1540
1541 # perform the final actions for the project specific page
1542 def project_specific_finalize(cmnd, pid):
1543 project = Project.objects.get(pk=pid)
1544 callback = project.get_variable(Project.PROJECT_SPECIFIC_CALLBACK)
1545 if "update" == cmnd:
1546 # Delete all '_PROJECT_PREPARE_' builds
1547 for b in Build.objects.all().filter(project=project):
1548 delete_build = False
1549 for t in b.target_set.all():
1550 if '_PROJECT_PREPARE_' == t.target:
1551 delete_build = True
1552 if delete_build:
1553 from django.core import management
1554 management.call_command('builddelete', str(b.id), interactive=False)
1555 # perform callback at this last moment if defined, in case Toaster gets shutdown next
1556 default_target = project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE)
1557 if callback:
1558 callback = callback.replace("<IMAGE>",default_target)
1559 if "cancel" == cmnd:
1560 if callback:
1561 callback = callback.replace("<IMAGE>","none")
1562 callback = callback.replace("--update","--cancel")
1563 # perform callback at this last moment if defined, in case this Toaster gets shutdown next
1564 ret = ''
1565 if callback:
1566 ret = os.system('bash -c "%s"' % callback)
1567 project.set_variable(Project.PROJECT_SPECIFIC_CALLBACK,'')
1568 # Delete the temp project specific variables
1569 project.set_variable(Project.PROJECT_SPECIFIC_ISNEW,'')
1570 project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_NONE)
1571 # WORKAROUND: Release this workaround flag
1572 project.set_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE','')
1573
1574 # Shows the final landing page for project specific update
1575 def landing_specific(request, pid):
1576 project_specific_finalize("update", pid)
1577 context = {
1578 "install_dir": os.environ['TOASTER_DIR'],
1579 }
1580 return toaster_render(request, "landing_specific.html", context)
1581
1582 # Shows the related landing-specific page
1583 def landing_specific_cancel(request, pid):
1584 project_specific_finalize("cancel", pid)
1585 context = {
1586 "install_dir": os.environ['TOASTER_DIR'],
1587 "status": "cancel",
1588 }
1589 return toaster_render(request, "landing_specific.html", context)
1590
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001591 def jsunittests(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001592 """ Provides a page for the js unit tests """
1593 bbv = BitbakeVersion.objects.filter(branch="master").first()
1594 release = Release.objects.filter(bitbake_version=bbv).first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001595
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001596 name = "_js_unit_test_prj_"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001598 # If there is an existing project by this name delete it.
1599 # We don't want Lots of duplicates cluttering up the projects.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001600 Project.objects.filter(name=name).delete()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001601
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001602 new_project = Project.objects.create_project(name=name,
1603 release=release)
1604 # Add a layer
1605 layer = new_project.get_all_compatible_layer_versions().first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001607 ProjectLayer.objects.get_or_create(layercommit=layer,
1608 project=new_project)
1609
1610 # make sure we have a machine set for this project
1611 ProjectVariable.objects.get_or_create(project=new_project,
1612 name="MACHINE",
1613 value="qemux86")
1614 context = {'project': new_project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001615 return toaster_render(request, "js-unit-tests.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001616
1617 from django.views.decorators.csrf import csrf_exempt
1618 @csrf_exempt
Andrew Geissler20137392023-10-12 04:59:14 -06001619 @log_view_mixin
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001620 def xhr_testreleasechange(request, pid):
1621 def response(data):
1622 return HttpResponse(jsonfilter(data),
1623 content_type="application/json")
1624
1625 """ returns layer versions that would be deleted on the new
1626 release__pk """
1627 try:
1628 prj = Project.objects.get(pk = pid)
1629 new_release_id = request.GET['new_release_id']
1630
1631 # If we're already on this project do nothing
1632 if prj.release.pk == int(new_release_id):
1633 return reponse({"error": "ok", "rows": []})
1634
1635 retval = []
1636
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001637 for project in prj.projectlayer_set.all():
1638 release = Release.objects.get(pk = new_release_id)
1639
1640 layer_versions = prj.get_all_compatible_layer_versions()
1641 layer_versions = layer_versions.filter(release = release)
1642 layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
1643
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001644 # there is no layer_version with the new release id,
1645 # and the same name
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001646 if layer_versions.count() < 1:
1647 retval.append(project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001648
1649 return response({"error":"ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001650 "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001651 })
1652
1653 except Exception as e:
1654 return response({"error": str(e) })
1655
Andrew Geissler20137392023-10-12 04:59:14 -06001656 @log_view_mixin
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001657 def xhr_configvaredit(request, pid):
1658 try:
1659 prj = Project.objects.get(id = pid)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001660 # There are cases where user can add variables which hold values
1661 # like http://, file:/// etc. In such case a simple split(":")
1662 # would fail. One example is SSTATE_MIRRORS variable. So we use
1663 # max_split var to handle them.
1664 max_split = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001665 # add conf variables
1666 if 'configvarAdd' in request.POST:
1667 t=request.POST['configvarAdd'].strip()
1668 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001669 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001670 else:
1671 variable = t
1672 value = ""
1673
1674 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
1675 # change conf variables
1676 if 'configvarChange' in request.POST:
1677 t=request.POST['configvarChange'].strip()
1678 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001679 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001680 else:
1681 variable = t
1682 value = ""
1683
1684 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
1685 pt.value=value
1686 pt.save()
1687 # remove conf variables
1688 if 'configvarDel' in request.POST:
1689 t=request.POST['configvarDel'].strip()
1690 pt = ProjectVariable.objects.get(pk = int(t)).delete()
1691
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001692 # return all project settings, filter out disallowed and elsewhere-managed variables
1693 vars_managed,vars_fstypes,vars_disallowed = get_project_configvars_context()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001694 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1695 for var in vars_managed:
1696 configvars_query = configvars_query.exclude(name = var)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001697 for var in vars_disallowed:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001698 configvars_query = configvars_query.exclude(name = var)
1699
1700 return_data = {
1701 "error": "ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001702 'configvars': [(x.name, x.value, x.pk) for x in configvars_query]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001703 }
1704 try:
1705 return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
1706 except ProjectVariable.DoesNotExist:
1707 pass
1708 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001709 return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
1710 except ProjectVariable.DoesNotExist:
1711 pass
1712 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001713 return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
1714 except ProjectVariable.DoesNotExist:
1715 pass
1716 try:
Patrick Williams213cb262021-08-07 19:21:33 -05001717 return_data['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001718 except ProjectVariable.DoesNotExist:
1719 pass
1720 try:
1721 return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
1722 except ProjectVariable.DoesNotExist:
1723 pass
1724 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001725 return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001726 except ProjectVariable.DoesNotExist:
1727 pass
1728
1729 return HttpResponse(json.dumps( return_data ), content_type = "application/json")
1730
1731 except Exception as e:
1732 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1733
1734
Andrew Geissler20137392023-10-12 04:59:14 -06001735 @log_view_mixin
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001736 def customrecipe_download(request, pid, recipe_id):
1737 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1738
1739 file_data = recipe.generate_recipe_file_contents()
1740
1741 response = HttpResponse(file_data, content_type='text/plain')
1742 response['Content-Disposition'] = \
1743 'attachment; filename="%s_%s.bb"' % (recipe.name,
1744 recipe.version)
1745
1746 return response
1747
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001748 def importlayer(request, pid):
1749 template = "importlayer.html"
1750 context = {
1751 'project': Project.objects.get(id=pid),
1752 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001753 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001754
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001755 def layerdetails(request, pid, layerid):
1756 project = Project.objects.get(pk=pid)
1757 layer_version = Layer_Version.objects.get(pk=layerid)
1758
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001759 project_layers = ProjectLayer.objects.filter(
1760 project=project).values_list("layercommit_id",
1761 flat=True)
1762
1763 context = {
1764 'project': project,
1765 'layer_source': LayerSource.types_dict(),
1766 'layerversion': layer_version,
1767 'layerdeps': {
1768 "list": [
1769 {
1770 "id": dep.id,
1771 "name": dep.layer.name,
1772 "layerdetailurl": reverse('layerdetails',
1773 args=(pid, dep.pk)),
1774 "vcs_url": dep.layer.vcs_url,
1775 "vcs_reference": dep.get_vcs_reference()
1776 }
1777 for dep in layer_version.get_alldeps(project.id)]
1778 },
1779 'projectlayers': list(project_layers)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001780 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001781
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001782 return toaster_render(request, 'layerdetails.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001783
1784
1785 def get_project_configvars_context():
1786 # Vars managed outside of this view
1787 vars_managed = {
1788 'MACHINE', 'BBLAYERS'
1789 }
1790
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001791 vars_disallowed = {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001792 'PARALLEL_MAKE','BB_NUMBER_THREADS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001793 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001794 'PARALLEL_MAKE','TMPDIR',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001795 'all_proxy','ftp_proxy','http_proxy ','https_proxy'
1796 }
1797
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001798 vars_fstypes = Target_Image_File.SUFFIXES
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001799
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001800 return(vars_managed,sorted(vars_fstypes),vars_disallowed)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001801
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001802 def projectconf(request, pid):
1803
1804 try:
1805 prj = Project.objects.get(id = pid)
1806 except Project.DoesNotExist:
1807 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1808
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001809 # remove disallowed and externally managed varaibles from this list
1810 vars_managed,vars_fstypes,vars_disallowed = get_project_configvars_context()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001811 configvars = ProjectVariable.objects.filter(project_id = pid).all()
1812 for var in vars_managed:
1813 configvars = configvars.exclude(name = var)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001814 for var in vars_disallowed:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001815 configvars = configvars.exclude(name = var)
1816
1817 context = {
1818 'project': prj,
1819 'configvars': configvars,
1820 'vars_managed': vars_managed,
1821 'vars_fstypes': vars_fstypes,
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001822 'vars_disallowed': vars_disallowed,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001823 }
1824
1825 try:
1826 context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value
1827 context['distro_defined'] = "1"
1828 except ProjectVariable.DoesNotExist:
1829 pass
1830 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001831 if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
1832 be = BuildEnvironment.objects.get(pk = str(1))
1833 dl_dir = os.path.join(dirname(be.builddir), "downloads")
1834 context['dl_dir'] = dl_dir
1835 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
1836 pv.value = dl_dir
1837 pv.save()
1838 else:
1839 context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
1840 context['dl_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001841 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001842 pass
1843 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001844 context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
1845 context['fstypes_defined'] = "1"
1846 except ProjectVariable.DoesNotExist:
1847 pass
1848 try:
Patrick Williams213cb262021-08-07 19:21:33 -05001849 context['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001850 context['image_install_append_defined'] = "1"
1851 except ProjectVariable.DoesNotExist:
1852 pass
1853 try:
1854 context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
1855 context['package_classes_defined'] = "1"
1856 except ProjectVariable.DoesNotExist:
1857 pass
1858 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001859 if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
1860 be = BuildEnvironment.objects.get(pk = str(1))
1861 sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
1862 context['sstate_dir'] = sstate_dir
1863 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
1864 pv.value = sstate_dir
1865 pv.save()
1866 else:
1867 context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
1868 context['sstate_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001869 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001870 pass
1871
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001872 return toaster_render(request, "projectconf.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001873
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001874 def _file_names_for_artifact(build, artifact_type, artifact_id):
1875 """
1876 Return a tuple (file path, file name for the download response) for an
1877 artifact of type artifact_type with ID artifact_id for build; if
1878 artifact type is not supported, returns (None, None)
1879 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001880 file_name = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001881 response_file_name = None
1882
1883 if artifact_type == "cookerlog":
1884 file_name = build.cooker_log_path
1885 response_file_name = "cooker.log"
1886
1887 elif artifact_type == "imagefile":
1888 file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001889
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001890 elif artifact_type == "targetkernelartifact":
1891 target = TargetKernelFile.objects.get(pk=artifact_id)
1892 file_name = target.file_name
1893
1894 elif artifact_type == "targetsdkartifact":
1895 target = TargetSDKFile.objects.get(pk=artifact_id)
1896 file_name = target.file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001897
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001898 elif artifact_type == "licensemanifest":
1899 file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001900
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001901 elif artifact_type == "packagemanifest":
1902 file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path
1903
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001904 elif artifact_type == "tasklogfile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001905 file_name = Task.objects.get(build = build, pk = artifact_id).logfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001906
1907 elif artifact_type == "logmessagefile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001908 file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001909
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001910 if file_name and not response_file_name:
1911 response_file_name = os.path.basename(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001912
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001913 return (file_name, response_file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001914
1915 def build_artifact(request, build_id, artifact_type, artifact_id):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001916 """
1917 View which returns a build artifact file as a response
1918 """
1919 file_name = None
1920 response_file_name = None
1921
1922 try:
1923 build = Build.objects.get(pk = build_id)
1924 file_name, response_file_name = _file_names_for_artifact(
1925 build, artifact_type, artifact_id
1926 )
1927
1928 if file_name and response_file_name:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001929 fsock = open(file_name, "rb")
Patrick Williamsd7e96312015-09-22 08:09:05 -05001930 content_type = MimeTypeFinder.get_mimetype(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001931
1932 response = HttpResponse(fsock, content_type = content_type)
1933
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001934 disposition = "attachment; filename=" + response_file_name
1935 response["Content-Disposition"] = disposition
Patrick Williamsd7e96312015-09-22 08:09:05 -05001936
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001937 return response
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001938 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001939 return toaster_render(request, "unavailable_artifact.html")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001940 except (ObjectDoesNotExist, IOError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001941 return toaster_render(request, "unavailable_artifact.html")
1942