blob: 5c03dd3a207436a5cb5dbe4bcc6dc16bf6e274be [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
Jack Rosenthal8de609d2023-02-09 20:20:3513import cherrypy # pylint: disable=import-error
Amin Hassani2aa34282020-11-18 01:18:1914from six.moves import urllib
15
Amin Hassani2aa34282020-11-18 01:18:1916
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:
Jack Rosenthal8de609d2023-02-09 20:20:3522 from nebraska import nebraska
Amin Hassani2aa34282020-11-18 01:18:1923except ImportError:
Jack Rosenthal8de609d2023-02-09 20:20:3524 import nebraska
Amin Hassani2aa34282020-11-18 01:18:1925
Amin Hassani2aa34282020-11-18 01:18:1926
27# Module-local log function.
28def _Log(message, *args):
Jack Rosenthal8de609d2023-02-09 20:20:3529 return logging.info(message, *args)
Amin Hassani3587fb32021-04-28 17:10:0130
Amin Hassani2aa34282020-11-18 01:18:1931
32class AutoupdateError(Exception):
Jack Rosenthal8de609d2023-02-09 20:20:3533 """Exception classes used by this module."""
34
35 # pylint: disable=unnecessary-pass
36 pass
Amin Hassani2aa34282020-11-18 01:18:1937
38
39def _ChangeUrlPort(url, new_port):
Jack Rosenthal8de609d2023-02-09 20:20:3540 """Return the URL passed in with a different port"""
41 scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
42 host_port = netloc.split(":")
Amin Hassani2aa34282020-11-18 01:18:1943
Jack Rosenthal8de609d2023-02-09 20:20:3544 if len(host_port) == 1:
45 host_port.append(new_port)
46 else:
47 host_port[1] = new_port
Amin Hassani2aa34282020-11-18 01:18:1948
Jack Rosenthal8de609d2023-02-09 20:20:3549 print(host_port)
50 netloc = "%s:%s" % tuple(host_port)
Amin Hassani2aa34282020-11-18 01:18:1951
Jack Rosenthal8de609d2023-02-09 20:20:3552 # pylint: disable=too-many-function-args
53 return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
54
Amin Hassani2aa34282020-11-18 01:18:1955
56def _NonePathJoin(*args):
Jack Rosenthal8de609d2023-02-09 20:20:3557 """os.path.join that filters None's from the argument list."""
58 return os.path.join(*[x for x in args if x is not None])
Amin Hassani2aa34282020-11-18 01:18:1959
60
61class Autoupdate(object):
Jack Rosenthal8de609d2023-02-09 20:20:3562 """Class that contains functionality that handles Chrome OS update pings."""
Amin Hassani2aa34282020-11-18 01:18:1963
Jack Rosenthal8de609d2023-02-09 20:20:3564 def __init__(self, xbuddy, static_dir=None):
65 """Initializes the class.
Amin Hassani2aa34282020-11-18 01:18:1966
Jack Rosenthal8de609d2023-02-09 20:20:3567 Args:
68 xbuddy: The xbuddy path.
69 static_dir: The path to the devserver static directory.
70 """
71 self.xbuddy = xbuddy
72 self.static_dir = static_dir
Amin Hassani2aa34282020-11-18 01:18:1973
Jack Rosenthal8de609d2023-02-09 20:20:3574 def GetDevserverUrl(self):
75 """Returns the devserver url base."""
76 x_forwarded_host = cherrypy.request.headers.get("X-Forwarded-Host")
77 if x_forwarded_host:
78 # Select the left most <ip>:<port> value so that the request is
79 # forwarded correctly.
80 x_forwarded_host = [x.strip() for x in x_forwarded_host.split(",")][
81 0
82 ]
83 hostname = "http://" + x_forwarded_host
84 else:
85 hostname = cherrypy.request.base
Amin Hassani2aa34282020-11-18 01:18:1986
Jack Rosenthal8de609d2023-02-09 20:20:3587 return hostname
Amin Hassani2aa34282020-11-18 01:18:1988
Jack Rosenthal8de609d2023-02-09 20:20:3589 def GetStaticUrl(self):
90 """Returns the static url base that should prefix all payload responses."""
91 hostname = self.GetDevserverUrl()
92 _Log("Handling update ping as %s", hostname)
Amin Hassani2aa34282020-11-18 01:18:1993
Jack Rosenthal8de609d2023-02-09 20:20:3594 static_urlbase = "%s/static" % hostname
95 _Log("Using static url base %s", static_urlbase)
96 return static_urlbase
Amin Hassani2aa34282020-11-18 01:18:1997
Jack Rosenthal8de609d2023-02-09 20:20:3598 def GetBuildID(self, label, board):
99 """Find the build id of the given lable and board.
Amin Hassani2aa34282020-11-18 01:18:19100
Jack Rosenthal8de609d2023-02-09 20:20:35101 Args:
102 label: from update request
103 board: from update request
Amin Hassani2aa34282020-11-18 01:18:19104
Jack Rosenthal8de609d2023-02-09 20:20:35105 Returns:
106 The build id of given label and board. e.g. reef-release/R88-13591.0.0
Amin Hassani2aa34282020-11-18 01:18:19107
Jack Rosenthal8de609d2023-02-09 20:20:35108 Raises:
109 AutoupdateError: If the update could not be found.
110 """
111 label = label or ""
112 label_list = label.split("/")
113 if label_list[0] == "xbuddy":
114 # If path explicitly calls xbuddy, pop off the tag.
115 label_list.pop()
116 x_label, _ = self.xbuddy.Translate(label_list, board=board)
117 return x_label
Amin Hassani2aa34282020-11-18 01:18:19118
Jack Rosenthal8de609d2023-02-09 20:20:35119 def HandleUpdatePing(self, data, label="", **kwargs):
120 """Handles an update ping from an update client.
Amin Hassani2aa34282020-11-18 01:18:19121
Jack Rosenthal8de609d2023-02-09 20:20:35122 Args:
123 data: XML blob from client.
124 label: optional label for the update.
125 kwargs: The map of query strings passed to the /update API.
Amin Hassani2aa34282020-11-18 01:18:19126
Jack Rosenthal8de609d2023-02-09 20:20:35127 Returns:
128 Update payload message for client.
129 """
130 # Change the URL's string query dictionary provided by cherrypy to a valid
131 # dictionary that has proper values for its keys. e.g. True instead of
132 # 'True'.
133 kwargs = nebraska.QueryDictToDict(kwargs)
Amin Hassani2aa34282020-11-18 01:18:19134
Jack Rosenthal8de609d2023-02-09 20:20:35135 # Process attributes of the update check.
136 request = nebraska.Request(data)
137 if request.request_type == nebraska.Request.RequestType.EVENT:
138 _Log("A non-update event notification received. Returning an ack.")
139 n = nebraska.Nebraska()
140 n.UpdateConfig(**kwargs)
141 return n.GetResponseToRequest(request)
Amin Hassani2aa34282020-11-18 01:18:19142
Jack Rosenthal8de609d2023-02-09 20:20:35143 _Log("Update Check Received.")
Amin Hassani2aa34282020-11-18 01:18:19144
Jack Rosenthal8de609d2023-02-09 20:20:35145 try:
146 build_id = self.GetBuildID(label, request.board)
147 base_url = "/".join((self.GetStaticUrl(), build_id))
148 local_payload_dir = _NonePathJoin(self.static_dir, build_id)
149 except AutoupdateError as e:
150 # Raised if we fail to generate an update payload.
151 _Log(
152 "Failed to process an update request, but we will defer to "
153 "nebraska to respond with no-update. The error was %s",
154 e,
155 )
Amin Hassani2aa34282020-11-18 01:18:19156
Jack Rosenthal8de609d2023-02-09 20:20:35157 _Log("Responding to client to use url %s to get image", base_url)
158 n = nebraska.Nebraska()
159 n.UpdateConfig(
160 update_payloads_address=base_url,
161 update_app_index=nebraska.AppIndex(local_payload_dir),
162 )
163 return n.GetResponseToRequest(request)