blob: 19f90e91c4aa1c5077330d22fee4980550fe4dbb [file] [log] [blame]
Amin Hassani2aa34282020-11-18 01:18:191# -*- coding: utf-8 -*-
Mike Frysinger8b0fc372022-09-08 07:24:242# Copyright 2011 The ChromiumOS Authors
Amin Hassani2aa34282020-11-18 01:18:193# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Devserver module for handling update client requests."""
7
8from __future__ import print_function
9
Chris McDonald771446e2021-08-05 20:23:0810import logging
Amin Hassani2aa34282020-11-18 01:18:1911import os
12
13from six.moves import urllib
14
15import cherrypy # pylint: disable=import-error
16
17# TODO(crbug.com/872441): We try to import nebraska from different places
18# because when we install the devserver, we copy the nebraska.py into the main
19# directory. Once this bug is resolved, we can always import from nebraska
20# directory.
21try:
22 from nebraska import nebraska
23except ImportError:
24 import nebraska
25
Amin Hassani2aa34282020-11-18 01:18:1926
27# Module-local log function.
28def _Log(message, *args):
Amin Hassani3587fb32021-04-28 17:10:0129 return logging.info(message, *args)
30
Amin Hassani2aa34282020-11-18 01:18:1931
32class AutoupdateError(Exception):
33 """Exception classes used by this module."""
Amin Hassanibf9e0402021-02-24 03:41:0034 # pylint: disable=unnecessary-pass
Amin Hassani2aa34282020-11-18 01:18:1935 pass
36
37
38def _ChangeUrlPort(url, new_port):
39 """Return the URL passed in with a different port"""
40 scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
41 host_port = netloc.split(':')
42
43 if len(host_port) == 1:
44 host_port.append(new_port)
45 else:
46 host_port[1] = new_port
47
48 print(host_port)
49 netloc = '%s:%s' % tuple(host_port)
50
51 # pylint: disable=too-many-function-args
52 return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
53
54def _NonePathJoin(*args):
55 """os.path.join that filters None's from the argument list."""
56 return os.path.join(*[x for x in args if x is not None])
57
58
59class Autoupdate(object):
60 """Class that contains functionality that handles Chrome OS update pings."""
61
62 def __init__(self, xbuddy, static_dir=None):
63 """Initializes the class.
64
65 Args:
66 xbuddy: The xbuddy path.
67 static_dir: The path to the devserver static directory.
68 """
69 self.xbuddy = xbuddy
70 self.static_dir = static_dir
71
Amin Hassani2aa34282020-11-18 01:18:1972 def GetDevserverUrl(self):
73 """Returns the devserver url base."""
74 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
75 if x_forwarded_host:
76 # Select the left most <ip>:<port> value so that the request is
77 # forwarded correctly.
78 x_forwarded_host = [x.strip() for x in x_forwarded_host.split(',')][0]
79 hostname = 'http://' + x_forwarded_host
80 else:
81 hostname = cherrypy.request.base
82
83 return hostname
84
85 def GetStaticUrl(self):
86 """Returns the static url base that should prefix all payload responses."""
87 hostname = self.GetDevserverUrl()
88 _Log('Handling update ping as %s', hostname)
89
90 static_urlbase = '%s/static' % hostname
91 _Log('Using static url base %s', static_urlbase)
92 return static_urlbase
93
Amin Hassani624b7ae2020-12-04 18:55:4994 def GetBuildID(self, label, board):
95 """Find the build id of the given lable and board.
Amin Hassani2aa34282020-11-18 01:18:1996
97 Args:
98 label: from update request
99 board: from update request
100
101 Returns:
Amin Hassani624b7ae2020-12-04 18:55:49102 The build id of given label and board. e.g. reef-release/R88-13591.0.0
Amin Hassani2aa34282020-11-18 01:18:19103
104 Raises:
105 AutoupdateError: If the update could not be found.
106 """
107 label = label or ''
108 label_list = label.split('/')
Amin Hassani8b503582020-12-03 04:47:01109 if label_list[0] == 'xbuddy':
110 # If path explicitly calls xbuddy, pop off the tag.
111 label_list.pop()
112 x_label, _ = self.xbuddy.Translate(label_list, board=board)
Amin Hassani624b7ae2020-12-04 18:55:49113 return x_label
Amin Hassani2aa34282020-11-18 01:18:19114
115 def HandleUpdatePing(self, data, label='', **kwargs):
116 """Handles an update ping from an update client.
117
118 Args:
119 data: XML blob from client.
120 label: optional label for the update.
121 kwargs: The map of query strings passed to the /update API.
122
123 Returns:
124 Update payload message for client.
125 """
Amin Hassani2aa34282020-11-18 01:18:19126 # Change the URL's string query dictionary provided by cherrypy to a valid
127 # dictionary that has proper values for its keys. e.g. True instead of
128 # 'True'.
129 kwargs = nebraska.QueryDictToDict(kwargs)
130
131 # Process attributes of the update check.
132 request = nebraska.Request(data)
133 if request.request_type == nebraska.Request.RequestType.EVENT:
134 _Log('A non-update event notification received. Returning an ack.')
Amin Hassanibf9e0402021-02-24 03:41:00135 n = nebraska.Nebraska()
136 n.UpdateConfig(**kwargs)
137 return n.GetResponseToRequest(request)
Amin Hassani2aa34282020-11-18 01:18:19138
139 _Log('Update Check Received.')
140
141 try:
Amin Hassani624b7ae2020-12-04 18:55:49142 build_id = self.GetBuildID(label, request.board)
143 base_url = '/'.join((self.GetStaticUrl(), build_id))
144 local_payload_dir = _NonePathJoin(self.static_dir, build_id)
Amin Hassani2aa34282020-11-18 01:18:19145 except AutoupdateError as e:
146 # Raised if we fail to generate an update payload.
147 _Log('Failed to process an update request, but we will defer to '
148 'nebraska to respond with no-update. The error was %s', e)
149
150 _Log('Responding to client to use url %s to get image', base_url)
Amin Hassanibf9e0402021-02-24 03:41:00151 n = nebraska.Nebraska()
152 n.UpdateConfig(update_payloads_address=base_url,
153 update_app_index=nebraska.AppIndex(local_payload_dir))
154 return n.GetResponseToRequest(request)