xref: /aosp_15_r20/tools/acloud/setup/gcp_setup_runner.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
1*800a58d9SAndroid Build Coastguard Worker#!/usr/bin/env python
2*800a58d9SAndroid Build Coastguard Worker#
3*800a58d9SAndroid Build Coastguard Worker# Copyright 2018 - The Android Open Source Project
4*800a58d9SAndroid Build Coastguard Worker#
5*800a58d9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*800a58d9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*800a58d9SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*800a58d9SAndroid Build Coastguard Worker#
9*800a58d9SAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
10*800a58d9SAndroid Build Coastguard Worker#
11*800a58d9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*800a58d9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*800a58d9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*800a58d9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*800a58d9SAndroid Build Coastguard Worker# limitations under the License.
16*800a58d9SAndroid Build Coastguard Worker"""Gcloud setup runner."""
17*800a58d9SAndroid Build Coastguard Worker
18*800a58d9SAndroid Build Coastguard Workerfrom __future__ import print_function
19*800a58d9SAndroid Build Coastguard Workerimport logging
20*800a58d9SAndroid Build Coastguard Workerimport os
21*800a58d9SAndroid Build Coastguard Workerimport re
22*800a58d9SAndroid Build Coastguard Workerimport subprocess
23*800a58d9SAndroid Build Coastguard Worker
24*800a58d9SAndroid Build Coastguard Workerimport six
25*800a58d9SAndroid Build Coastguard Worker
26*800a58d9SAndroid Build Coastguard Workerfrom acloud import errors
27*800a58d9SAndroid Build Coastguard Workerfrom acloud.internal.lib import utils
28*800a58d9SAndroid Build Coastguard Workerfrom acloud.public import config
29*800a58d9SAndroid Build Coastguard Workerfrom acloud.setup import base_task_runner
30*800a58d9SAndroid Build Coastguard Workerfrom acloud.setup import google_sdk
31*800a58d9SAndroid Build Coastguard Worker
32*800a58d9SAndroid Build Coastguard Worker
33*800a58d9SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__)
34*800a58d9SAndroid Build Coastguard Worker
35*800a58d9SAndroid Build Coastguard Worker# APIs that need to be enabled for GCP project.
36*800a58d9SAndroid Build Coastguard Worker_ANDROID_BUILD_SERVICE = "androidbuildinternal.googleapis.com"
37*800a58d9SAndroid Build Coastguard Worker_ANDROID_BUILD_MSG = (
38*800a58d9SAndroid Build Coastguard Worker    "This service (%s) help to download images from Android Build. If it isn't "
39*800a58d9SAndroid Build Coastguard Worker    "enabled, acloud only supports local images to create AVD."
40*800a58d9SAndroid Build Coastguard Worker    % _ANDROID_BUILD_SERVICE)
41*800a58d9SAndroid Build Coastguard Worker_COMPUTE_ENGINE_SERVICE = "compute.googleapis.com"
42*800a58d9SAndroid Build Coastguard Worker_COMPUTE_ENGINE_MSG = (
43*800a58d9SAndroid Build Coastguard Worker    "This service (%s) help to create instance in google cloud platform. If it "
44*800a58d9SAndroid Build Coastguard Worker    "isn't enabled, acloud can't work anymore." % _COMPUTE_ENGINE_SERVICE)
45*800a58d9SAndroid Build Coastguard Worker_OPEN_SERVICE_FAILED_MSG = (
46*800a58d9SAndroid Build Coastguard Worker    "\n[Open Service Failed]\n"
47*800a58d9SAndroid Build Coastguard Worker    "Service name: %(service_name)s\n"
48*800a58d9SAndroid Build Coastguard Worker    "%(service_msg)s\n")
49*800a58d9SAndroid Build Coastguard Worker
50*800a58d9SAndroid Build Coastguard Worker_BUILD_SERVICE_ACCOUNT = "[email protected]"
51*800a58d9SAndroid Build Coastguard Worker_BILLING_ENABLE_MSG = "billingEnabled: true"
52*800a58d9SAndroid Build Coastguard Worker_DEFAULT_SSH_FOLDER = os.path.expanduser("~/.ssh")
53*800a58d9SAndroid Build Coastguard Worker_DEFAULT_SSH_KEY = "acloud_rsa"
54*800a58d9SAndroid Build Coastguard Worker_DEFAULT_SSH_PRIVATE_KEY = os.path.join(_DEFAULT_SSH_FOLDER,
55*800a58d9SAndroid Build Coastguard Worker                                        _DEFAULT_SSH_KEY)
56*800a58d9SAndroid Build Coastguard Worker_DEFAULT_SSH_PUBLIC_KEY = os.path.join(_DEFAULT_SSH_FOLDER,
57*800a58d9SAndroid Build Coastguard Worker                                       _DEFAULT_SSH_KEY + ".pub")
58*800a58d9SAndroid Build Coastguard Worker_ENV_CLOUDSDK_PYTHON = "CLOUDSDK_PYTHON"
59*800a58d9SAndroid Build Coastguard Worker_GCLOUD_COMPONENT_ALPHA = "alpha"
60*800a58d9SAndroid Build Coastguard Worker# Regular expression to get project/zone information.
61*800a58d9SAndroid Build Coastguard Worker_PROJECT_RE = re.compile(r"^project = (?P<project>.+)")
62*800a58d9SAndroid Build Coastguard Worker_ZONE_RE = re.compile(r"^zone = (?P<zone>.+)")
63*800a58d9SAndroid Build Coastguard Worker
64*800a58d9SAndroid Build Coastguard Worker
65*800a58d9SAndroid Build Coastguard Workerdef UpdateConfigFile(config_path, item, value):
66*800a58d9SAndroid Build Coastguard Worker    """Update config data.
67*800a58d9SAndroid Build Coastguard Worker
68*800a58d9SAndroid Build Coastguard Worker    Case A: config file contain this item.
69*800a58d9SAndroid Build Coastguard Worker        In config, "project = A_project". New value is B_project
70*800a58d9SAndroid Build Coastguard Worker        Set config "project = B_project".
71*800a58d9SAndroid Build Coastguard Worker    Case B: config file didn't contain this item.
72*800a58d9SAndroid Build Coastguard Worker        New value is B_project.
73*800a58d9SAndroid Build Coastguard Worker        Setup config as "project = B_project".
74*800a58d9SAndroid Build Coastguard Worker
75*800a58d9SAndroid Build Coastguard Worker    Args:
76*800a58d9SAndroid Build Coastguard Worker        config_path: String, acloud config path.
77*800a58d9SAndroid Build Coastguard Worker        item: String, item name in config file. EX: project, zone
78*800a58d9SAndroid Build Coastguard Worker        value: String, value of item in config file.
79*800a58d9SAndroid Build Coastguard Worker
80*800a58d9SAndroid Build Coastguard Worker    TODO(111574698): Refactor this to minimize writes to the config file.
81*800a58d9SAndroid Build Coastguard Worker    TODO(111574698): Use proto method to update config.
82*800a58d9SAndroid Build Coastguard Worker    """
83*800a58d9SAndroid Build Coastguard Worker    write_lines = []
84*800a58d9SAndroid Build Coastguard Worker    find_item = False
85*800a58d9SAndroid Build Coastguard Worker    write_line = item + ": \"" + value + "\"\n"
86*800a58d9SAndroid Build Coastguard Worker    if os.path.isfile(config_path):
87*800a58d9SAndroid Build Coastguard Worker        with open(config_path, "r") as cfg_file:
88*800a58d9SAndroid Build Coastguard Worker            for read_line in cfg_file.readlines():
89*800a58d9SAndroid Build Coastguard Worker                if read_line.startswith(item + ":"):
90*800a58d9SAndroid Build Coastguard Worker                    find_item = True
91*800a58d9SAndroid Build Coastguard Worker                    write_lines.append(write_line)
92*800a58d9SAndroid Build Coastguard Worker                else:
93*800a58d9SAndroid Build Coastguard Worker                    write_lines.append(read_line)
94*800a58d9SAndroid Build Coastguard Worker    if not find_item:
95*800a58d9SAndroid Build Coastguard Worker        write_lines.append(write_line)
96*800a58d9SAndroid Build Coastguard Worker    with open(config_path, "w") as cfg_file:
97*800a58d9SAndroid Build Coastguard Worker        cfg_file.writelines(write_lines)
98*800a58d9SAndroid Build Coastguard Worker
99*800a58d9SAndroid Build Coastguard Worker
100*800a58d9SAndroid Build Coastguard Workerdef SetupSSHKeys(config_path, private_key_path, public_key_path):
101*800a58d9SAndroid Build Coastguard Worker    """Setup the pair of the ssh key for acloud.config.
102*800a58d9SAndroid Build Coastguard Worker
103*800a58d9SAndroid Build Coastguard Worker    User can use the default path: "~/.ssh/acloud_rsa".
104*800a58d9SAndroid Build Coastguard Worker
105*800a58d9SAndroid Build Coastguard Worker    Args:
106*800a58d9SAndroid Build Coastguard Worker        config_path: String, acloud config path.
107*800a58d9SAndroid Build Coastguard Worker        private_key_path: Path to the private key file.
108*800a58d9SAndroid Build Coastguard Worker                          e.g. ~/.ssh/acloud_rsa
109*800a58d9SAndroid Build Coastguard Worker        public_key_path: Path to the public key file.
110*800a58d9SAndroid Build Coastguard Worker                         e.g. ~/.ssh/acloud_rsa.pub
111*800a58d9SAndroid Build Coastguard Worker    """
112*800a58d9SAndroid Build Coastguard Worker    private_key_path = os.path.expanduser(private_key_path)
113*800a58d9SAndroid Build Coastguard Worker    if (private_key_path == "" or public_key_path == ""
114*800a58d9SAndroid Build Coastguard Worker            or private_key_path == _DEFAULT_SSH_PRIVATE_KEY):
115*800a58d9SAndroid Build Coastguard Worker        utils.CreateSshKeyPairIfNotExist(_DEFAULT_SSH_PRIVATE_KEY,
116*800a58d9SAndroid Build Coastguard Worker                                         _DEFAULT_SSH_PUBLIC_KEY)
117*800a58d9SAndroid Build Coastguard Worker        UpdateConfigFile(config_path, "ssh_private_key_path",
118*800a58d9SAndroid Build Coastguard Worker                         _DEFAULT_SSH_PRIVATE_KEY)
119*800a58d9SAndroid Build Coastguard Worker        UpdateConfigFile(config_path, "ssh_public_key_path",
120*800a58d9SAndroid Build Coastguard Worker                         _DEFAULT_SSH_PUBLIC_KEY)
121*800a58d9SAndroid Build Coastguard Worker
122*800a58d9SAndroid Build Coastguard Worker
123*800a58d9SAndroid Build Coastguard Workerdef _InputIsEmpty(input_string):
124*800a58d9SAndroid Build Coastguard Worker    """Check input string is empty.
125*800a58d9SAndroid Build Coastguard Worker
126*800a58d9SAndroid Build Coastguard Worker    Tool requests user to input client ID & client secret.
127*800a58d9SAndroid Build Coastguard Worker    This basic check can detect user input is empty.
128*800a58d9SAndroid Build Coastguard Worker
129*800a58d9SAndroid Build Coastguard Worker    Args:
130*800a58d9SAndroid Build Coastguard Worker        input_string: String, user input string.
131*800a58d9SAndroid Build Coastguard Worker
132*800a58d9SAndroid Build Coastguard Worker    Returns:
133*800a58d9SAndroid Build Coastguard Worker        Boolean: True if input is empty, False otherwise.
134*800a58d9SAndroid Build Coastguard Worker    """
135*800a58d9SAndroid Build Coastguard Worker    if input_string is None:
136*800a58d9SAndroid Build Coastguard Worker        return True
137*800a58d9SAndroid Build Coastguard Worker    if input_string == "":
138*800a58d9SAndroid Build Coastguard Worker        print("Please enter a non-empty value.")
139*800a58d9SAndroid Build Coastguard Worker        return True
140*800a58d9SAndroid Build Coastguard Worker    return False
141*800a58d9SAndroid Build Coastguard Worker
142*800a58d9SAndroid Build Coastguard Worker
143*800a58d9SAndroid Build Coastguard Workerclass GoogleSDKBins():
144*800a58d9SAndroid Build Coastguard Worker    """Class to run tools in the Google SDK."""
145*800a58d9SAndroid Build Coastguard Worker
146*800a58d9SAndroid Build Coastguard Worker    def __init__(self, google_sdk_folder):
147*800a58d9SAndroid Build Coastguard Worker        """GoogleSDKBins initialize.
148*800a58d9SAndroid Build Coastguard Worker
149*800a58d9SAndroid Build Coastguard Worker        Args:
150*800a58d9SAndroid Build Coastguard Worker            google_sdk_folder: String, google sdk path.
151*800a58d9SAndroid Build Coastguard Worker        """
152*800a58d9SAndroid Build Coastguard Worker        self.gcloud_command_path = os.path.join(google_sdk_folder, "gcloud")
153*800a58d9SAndroid Build Coastguard Worker        self.gsutil_command_path = os.path.join(google_sdk_folder, "gsutil")
154*800a58d9SAndroid Build Coastguard Worker        self._env = os.environ.copy()
155*800a58d9SAndroid Build Coastguard Worker        self._env[_ENV_CLOUDSDK_PYTHON] = "python"
156*800a58d9SAndroid Build Coastguard Worker
157*800a58d9SAndroid Build Coastguard Worker    def RunGcloud(self, cmd, **kwargs):
158*800a58d9SAndroid Build Coastguard Worker        """Run gcloud command.
159*800a58d9SAndroid Build Coastguard Worker
160*800a58d9SAndroid Build Coastguard Worker        Args:
161*800a58d9SAndroid Build Coastguard Worker            cmd: String list, command strings.
162*800a58d9SAndroid Build Coastguard Worker                  Ex: [config], then this function call "gcloud config".
163*800a58d9SAndroid Build Coastguard Worker            **kwargs: dictionary of keyword based args to pass to func.
164*800a58d9SAndroid Build Coastguard Worker
165*800a58d9SAndroid Build Coastguard Worker        Returns:
166*800a58d9SAndroid Build Coastguard Worker            String, return message after execute gcloud command.
167*800a58d9SAndroid Build Coastguard Worker        """
168*800a58d9SAndroid Build Coastguard Worker        return utils.CheckOutput([self.gcloud_command_path] + cmd,
169*800a58d9SAndroid Build Coastguard Worker                                 env=self._env, **kwargs)
170*800a58d9SAndroid Build Coastguard Worker
171*800a58d9SAndroid Build Coastguard Worker    def RunGsutil(self, cmd, **kwargs):
172*800a58d9SAndroid Build Coastguard Worker        """Run gsutil command.
173*800a58d9SAndroid Build Coastguard Worker
174*800a58d9SAndroid Build Coastguard Worker        Args:
175*800a58d9SAndroid Build Coastguard Worker            cmd : String list, command strings.
176*800a58d9SAndroid Build Coastguard Worker                  Ex: [list], then this function call "gsutil list".
177*800a58d9SAndroid Build Coastguard Worker            **kwargs: dictionary of keyword based args to pass to func.
178*800a58d9SAndroid Build Coastguard Worker
179*800a58d9SAndroid Build Coastguard Worker        Returns:
180*800a58d9SAndroid Build Coastguard Worker            String, return message after execute gsutil command.
181*800a58d9SAndroid Build Coastguard Worker        """
182*800a58d9SAndroid Build Coastguard Worker        return utils.CheckOutput([self.gsutil_command_path] + cmd,
183*800a58d9SAndroid Build Coastguard Worker                                 env=self._env, **kwargs)
184*800a58d9SAndroid Build Coastguard Worker
185*800a58d9SAndroid Build Coastguard Worker
186*800a58d9SAndroid Build Coastguard Workerclass GoogleAPIService():
187*800a58d9SAndroid Build Coastguard Worker    """Class to enable api service in the gcp project."""
188*800a58d9SAndroid Build Coastguard Worker
189*800a58d9SAndroid Build Coastguard Worker    def __init__(self, service_name, error_msg, required=False):
190*800a58d9SAndroid Build Coastguard Worker        """GoogleAPIService initialize.
191*800a58d9SAndroid Build Coastguard Worker
192*800a58d9SAndroid Build Coastguard Worker        Args:
193*800a58d9SAndroid Build Coastguard Worker            service_name: String, name of api service.
194*800a58d9SAndroid Build Coastguard Worker            error_msg: String, show messages if api service enable failed.
195*800a58d9SAndroid Build Coastguard Worker            required: Boolean, True for service must be enabled for acloud.
196*800a58d9SAndroid Build Coastguard Worker        """
197*800a58d9SAndroid Build Coastguard Worker        self._name = service_name
198*800a58d9SAndroid Build Coastguard Worker        self._error_msg = error_msg
199*800a58d9SAndroid Build Coastguard Worker        self._required = required
200*800a58d9SAndroid Build Coastguard Worker
201*800a58d9SAndroid Build Coastguard Worker    def EnableService(self, gcloud_runner):
202*800a58d9SAndroid Build Coastguard Worker        """Enable api service.
203*800a58d9SAndroid Build Coastguard Worker
204*800a58d9SAndroid Build Coastguard Worker        Args:
205*800a58d9SAndroid Build Coastguard Worker            gcloud_runner: A GcloudRunner class to run "gcloud" command.
206*800a58d9SAndroid Build Coastguard Worker        """
207*800a58d9SAndroid Build Coastguard Worker        try:
208*800a58d9SAndroid Build Coastguard Worker            gcloud_runner.RunGcloud(["services", "enable", self._name],
209*800a58d9SAndroid Build Coastguard Worker                                    stderr=subprocess.STDOUT)
210*800a58d9SAndroid Build Coastguard Worker        except subprocess.CalledProcessError as error:
211*800a58d9SAndroid Build Coastguard Worker            self.ShowFailMessages(error.output)
212*800a58d9SAndroid Build Coastguard Worker
213*800a58d9SAndroid Build Coastguard Worker    def ShowFailMessages(self, error):
214*800a58d9SAndroid Build Coastguard Worker        """Show fail messages.
215*800a58d9SAndroid Build Coastguard Worker
216*800a58d9SAndroid Build Coastguard Worker        Show the fail messages to hint users the impact if the api service
217*800a58d9SAndroid Build Coastguard Worker        isn't enabled.
218*800a58d9SAndroid Build Coastguard Worker
219*800a58d9SAndroid Build Coastguard Worker        Args:
220*800a58d9SAndroid Build Coastguard Worker            error: String of error message when opening api service failed.
221*800a58d9SAndroid Build Coastguard Worker        """
222*800a58d9SAndroid Build Coastguard Worker        msg_color = (utils.TextColors.FAIL if self._required else
223*800a58d9SAndroid Build Coastguard Worker                     utils.TextColors.WARNING)
224*800a58d9SAndroid Build Coastguard Worker        utils.PrintColorString(
225*800a58d9SAndroid Build Coastguard Worker            error + _OPEN_SERVICE_FAILED_MSG % {
226*800a58d9SAndroid Build Coastguard Worker                "service_name": self._name,
227*800a58d9SAndroid Build Coastguard Worker                "service_msg": self._error_msg}
228*800a58d9SAndroid Build Coastguard Worker            , msg_color)
229*800a58d9SAndroid Build Coastguard Worker
230*800a58d9SAndroid Build Coastguard Worker    @property
231*800a58d9SAndroid Build Coastguard Worker    def name(self):
232*800a58d9SAndroid Build Coastguard Worker        """Return name."""
233*800a58d9SAndroid Build Coastguard Worker        return self._name
234*800a58d9SAndroid Build Coastguard Worker
235*800a58d9SAndroid Build Coastguard Worker
236*800a58d9SAndroid Build Coastguard Workerclass GcpTaskRunner(base_task_runner.BaseTaskRunner):
237*800a58d9SAndroid Build Coastguard Worker    """Runner to setup google cloud user information."""
238*800a58d9SAndroid Build Coastguard Worker
239*800a58d9SAndroid Build Coastguard Worker    WELCOME_MESSAGE_TITLE = "Setup google cloud user information"
240*800a58d9SAndroid Build Coastguard Worker    WELCOME_MESSAGE = (
241*800a58d9SAndroid Build Coastguard Worker        "This step will walk you through gcloud SDK installation."
242*800a58d9SAndroid Build Coastguard Worker        "Then configure gcloud user information."
243*800a58d9SAndroid Build Coastguard Worker        "Finally enable some gcloud API services.")
244*800a58d9SAndroid Build Coastguard Worker
245*800a58d9SAndroid Build Coastguard Worker    def __init__(self, config_path):
246*800a58d9SAndroid Build Coastguard Worker        """Initialize parameters.
247*800a58d9SAndroid Build Coastguard Worker
248*800a58d9SAndroid Build Coastguard Worker        Load config file to get current values.
249*800a58d9SAndroid Build Coastguard Worker
250*800a58d9SAndroid Build Coastguard Worker        Args:
251*800a58d9SAndroid Build Coastguard Worker            config_path: String, acloud config path.
252*800a58d9SAndroid Build Coastguard Worker        """
253*800a58d9SAndroid Build Coastguard Worker        # pylint: disable=invalid-name
254*800a58d9SAndroid Build Coastguard Worker        config_mgr = config.AcloudConfigManager(config_path)
255*800a58d9SAndroid Build Coastguard Worker        cfg = config_mgr.Load()
256*800a58d9SAndroid Build Coastguard Worker        self.config_path = config_mgr.user_config_path
257*800a58d9SAndroid Build Coastguard Worker        self.project = cfg.project
258*800a58d9SAndroid Build Coastguard Worker        self.zone = cfg.zone
259*800a58d9SAndroid Build Coastguard Worker        self.ssh_private_key_path = cfg.ssh_private_key_path
260*800a58d9SAndroid Build Coastguard Worker        self.ssh_public_key_path = cfg.ssh_public_key_path
261*800a58d9SAndroid Build Coastguard Worker        self.stable_host_image_name = cfg.stable_host_image_name
262*800a58d9SAndroid Build Coastguard Worker        self.client_id = cfg.client_id
263*800a58d9SAndroid Build Coastguard Worker        self.client_secret = cfg.client_secret
264*800a58d9SAndroid Build Coastguard Worker        self.service_account_name = cfg.service_account_name
265*800a58d9SAndroid Build Coastguard Worker        self.service_account_private_key_path = cfg.service_account_private_key_path
266*800a58d9SAndroid Build Coastguard Worker        self.service_account_json_private_key_path = cfg.service_account_json_private_key_path
267*800a58d9SAndroid Build Coastguard Worker
268*800a58d9SAndroid Build Coastguard Worker    def ShouldRun(self):
269*800a58d9SAndroid Build Coastguard Worker        """Check if we actually need to run GCP setup.
270*800a58d9SAndroid Build Coastguard Worker
271*800a58d9SAndroid Build Coastguard Worker        We'll only do the gcp setup if certain fields in the cfg are empty.
272*800a58d9SAndroid Build Coastguard Worker
273*800a58d9SAndroid Build Coastguard Worker        Returns:
274*800a58d9SAndroid Build Coastguard Worker            True if reqired config fields are empty, False otherwise.
275*800a58d9SAndroid Build Coastguard Worker        """
276*800a58d9SAndroid Build Coastguard Worker        # We need to ensure the config has the proper auth-related fields set,
277*800a58d9SAndroid Build Coastguard Worker        # so config requires just 1 of the following:
278*800a58d9SAndroid Build Coastguard Worker        # 1. client id/secret
279*800a58d9SAndroid Build Coastguard Worker        # 2. service account name/private key path
280*800a58d9SAndroid Build Coastguard Worker        # 3. service account json private key path
281*800a58d9SAndroid Build Coastguard Worker        if ((not self.client_id or not self.client_secret)
282*800a58d9SAndroid Build Coastguard Worker                and (not self.service_account_name or not self.service_account_private_key_path)
283*800a58d9SAndroid Build Coastguard Worker                and not self.service_account_json_private_key_path):
284*800a58d9SAndroid Build Coastguard Worker            return True
285*800a58d9SAndroid Build Coastguard Worker
286*800a58d9SAndroid Build Coastguard Worker        # If a project isn't set, then we need to run setup.
287*800a58d9SAndroid Build Coastguard Worker        return not self.project
288*800a58d9SAndroid Build Coastguard Worker
289*800a58d9SAndroid Build Coastguard Worker    def _Run(self):
290*800a58d9SAndroid Build Coastguard Worker        """Run GCP setup task."""
291*800a58d9SAndroid Build Coastguard Worker        self._SetupGcloudInfo()
292*800a58d9SAndroid Build Coastguard Worker        SetupSSHKeys(self.config_path, self.ssh_private_key_path,
293*800a58d9SAndroid Build Coastguard Worker                     self.ssh_public_key_path)
294*800a58d9SAndroid Build Coastguard Worker
295*800a58d9SAndroid Build Coastguard Worker    def _SetupGcloudInfo(self):
296*800a58d9SAndroid Build Coastguard Worker        """Setup Gcloud user information.
297*800a58d9SAndroid Build Coastguard Worker            1. Setup Gcloud SDK tools.
298*800a58d9SAndroid Build Coastguard Worker            2. Setup Gcloud project.
299*800a58d9SAndroid Build Coastguard Worker                a. Setup Gcloud project and zone.
300*800a58d9SAndroid Build Coastguard Worker                b. Setup Client ID and Client secret.
301*800a58d9SAndroid Build Coastguard Worker                c. Setup Google Cloud Storage bucket.
302*800a58d9SAndroid Build Coastguard Worker            3. Enable Gcloud API services.
303*800a58d9SAndroid Build Coastguard Worker        """
304*800a58d9SAndroid Build Coastguard Worker        google_sdk_init = google_sdk.GoogleSDK()
305*800a58d9SAndroid Build Coastguard Worker        try:
306*800a58d9SAndroid Build Coastguard Worker            google_sdk_runner = GoogleSDKBins(google_sdk_init.GetSDKBinPath())
307*800a58d9SAndroid Build Coastguard Worker            google_sdk_init.InstallGcloudComponent(google_sdk_runner,
308*800a58d9SAndroid Build Coastguard Worker                                                   _GCLOUD_COMPONENT_ALPHA)
309*800a58d9SAndroid Build Coastguard Worker            self._SetupProject(google_sdk_runner)
310*800a58d9SAndroid Build Coastguard Worker            self._EnableGcloudServices(google_sdk_runner)
311*800a58d9SAndroid Build Coastguard Worker            self._CreateStableHostImage()
312*800a58d9SAndroid Build Coastguard Worker        finally:
313*800a58d9SAndroid Build Coastguard Worker            google_sdk_init.CleanUp()
314*800a58d9SAndroid Build Coastguard Worker
315*800a58d9SAndroid Build Coastguard Worker    def _CreateStableHostImage(self):
316*800a58d9SAndroid Build Coastguard Worker        """Create the stable host image."""
317*800a58d9SAndroid Build Coastguard Worker        # Write default stable_host_image_name with unused value.
318*800a58d9SAndroid Build Coastguard Worker        # TODO(113091773): An additional step to create the host image.
319*800a58d9SAndroid Build Coastguard Worker        if not self.stable_host_image_name:
320*800a58d9SAndroid Build Coastguard Worker            UpdateConfigFile(self.config_path, "stable_host_image_name", "")
321*800a58d9SAndroid Build Coastguard Worker
322*800a58d9SAndroid Build Coastguard Worker
323*800a58d9SAndroid Build Coastguard Worker    def _NeedProjectSetup(self):
324*800a58d9SAndroid Build Coastguard Worker        """Confirm project setup should run or not.
325*800a58d9SAndroid Build Coastguard Worker
326*800a58d9SAndroid Build Coastguard Worker        If the project settings (project name and zone) are blank (either one),
327*800a58d9SAndroid Build Coastguard Worker        we'll run the project setup flow. If they are set, we'll check with
328*800a58d9SAndroid Build Coastguard Worker        the user if they want to update them.
329*800a58d9SAndroid Build Coastguard Worker
330*800a58d9SAndroid Build Coastguard Worker        Returns:
331*800a58d9SAndroid Build Coastguard Worker            Boolean: True if we need to setup the project, False otherwise.
332*800a58d9SAndroid Build Coastguard Worker        """
333*800a58d9SAndroid Build Coastguard Worker        user_question = (
334*800a58d9SAndroid Build Coastguard Worker            "Your default Project/Zone settings are:\n"
335*800a58d9SAndroid Build Coastguard Worker            "project:[%s]\n"
336*800a58d9SAndroid Build Coastguard Worker            "zone:[%s]\n"
337*800a58d9SAndroid Build Coastguard Worker            "Would you like to update them?[y/N]: \n") % (self.project, self.zone)
338*800a58d9SAndroid Build Coastguard Worker
339*800a58d9SAndroid Build Coastguard Worker        if not self.project or not self.zone:
340*800a58d9SAndroid Build Coastguard Worker            logger.info("Project or zone is empty. Start to run setup process.")
341*800a58d9SAndroid Build Coastguard Worker            return True
342*800a58d9SAndroid Build Coastguard Worker        return utils.GetUserAnswerYes(user_question)
343*800a58d9SAndroid Build Coastguard Worker
344*800a58d9SAndroid Build Coastguard Worker    def _NeedClientIDSetup(self, project_changed):
345*800a58d9SAndroid Build Coastguard Worker        """Confirm client setup should run or not.
346*800a58d9SAndroid Build Coastguard Worker
347*800a58d9SAndroid Build Coastguard Worker        If project changed, client ID must also have to change.
348*800a58d9SAndroid Build Coastguard Worker        So tool will force to run setup function.
349*800a58d9SAndroid Build Coastguard Worker        If client ID or client secret is empty, tool force to run setup function.
350*800a58d9SAndroid Build Coastguard Worker        If project didn't change and config hold user client ID/secret, tool
351*800a58d9SAndroid Build Coastguard Worker        would skip client ID setup.
352*800a58d9SAndroid Build Coastguard Worker
353*800a58d9SAndroid Build Coastguard Worker        Args:
354*800a58d9SAndroid Build Coastguard Worker            project_changed: Boolean, True for project changed.
355*800a58d9SAndroid Build Coastguard Worker
356*800a58d9SAndroid Build Coastguard Worker        Returns:
357*800a58d9SAndroid Build Coastguard Worker            Boolean: True for run setup function.
358*800a58d9SAndroid Build Coastguard Worker        """
359*800a58d9SAndroid Build Coastguard Worker        if project_changed:
360*800a58d9SAndroid Build Coastguard Worker            logger.info("Your project changed. Start to run setup process.")
361*800a58d9SAndroid Build Coastguard Worker            return True
362*800a58d9SAndroid Build Coastguard Worker        if not self.client_id or not self.client_secret:
363*800a58d9SAndroid Build Coastguard Worker            logger.info("Client ID or client secret is empty. Start to run setup process.")
364*800a58d9SAndroid Build Coastguard Worker            return True
365*800a58d9SAndroid Build Coastguard Worker        logger.info("Project was unchanged and client ID didn't need to changed.")
366*800a58d9SAndroid Build Coastguard Worker        return False
367*800a58d9SAndroid Build Coastguard Worker
368*800a58d9SAndroid Build Coastguard Worker    def _SetupProject(self, gcloud_runner):
369*800a58d9SAndroid Build Coastguard Worker        """Setup gcloud project information.
370*800a58d9SAndroid Build Coastguard Worker
371*800a58d9SAndroid Build Coastguard Worker        Setup project and zone.
372*800a58d9SAndroid Build Coastguard Worker        Setup client ID and client secret.
373*800a58d9SAndroid Build Coastguard Worker        Make sure billing account enabled in project.
374*800a58d9SAndroid Build Coastguard Worker
375*800a58d9SAndroid Build Coastguard Worker        Args:
376*800a58d9SAndroid Build Coastguard Worker            gcloud_runner: A GcloudRunner class to run "gcloud" command.
377*800a58d9SAndroid Build Coastguard Worker        """
378*800a58d9SAndroid Build Coastguard Worker        project_changed = False
379*800a58d9SAndroid Build Coastguard Worker        if self._NeedProjectSetup():
380*800a58d9SAndroid Build Coastguard Worker            project_changed = self._UpdateProject(gcloud_runner)
381*800a58d9SAndroid Build Coastguard Worker        if self._NeedClientIDSetup(project_changed):
382*800a58d9SAndroid Build Coastguard Worker            self._SetupClientIDSecret()
383*800a58d9SAndroid Build Coastguard Worker        self._CheckBillingEnable(gcloud_runner)
384*800a58d9SAndroid Build Coastguard Worker
385*800a58d9SAndroid Build Coastguard Worker    def _UpdateProject(self, gcloud_runner):
386*800a58d9SAndroid Build Coastguard Worker        """Setup gcloud project name and zone name and check project changed.
387*800a58d9SAndroid Build Coastguard Worker
388*800a58d9SAndroid Build Coastguard Worker        Run "gcloud init" to handle gcloud project setup.
389*800a58d9SAndroid Build Coastguard Worker        Then "gcloud list" to get user settings information include "project" & "zone".
390*800a58d9SAndroid Build Coastguard Worker        Record project_changed for next setup steps.
391*800a58d9SAndroid Build Coastguard Worker
392*800a58d9SAndroid Build Coastguard Worker        Args:
393*800a58d9SAndroid Build Coastguard Worker            gcloud_runner: A GcloudRunner class to run "gcloud" command.
394*800a58d9SAndroid Build Coastguard Worker
395*800a58d9SAndroid Build Coastguard Worker        Returns:
396*800a58d9SAndroid Build Coastguard Worker            project_changed: True for project settings changed.
397*800a58d9SAndroid Build Coastguard Worker        """
398*800a58d9SAndroid Build Coastguard Worker        project_changed = False
399*800a58d9SAndroid Build Coastguard Worker        gcloud_runner.RunGcloud(["init"])
400*800a58d9SAndroid Build Coastguard Worker        gcp_config_list_out = gcloud_runner.RunGcloud(["config", "list"])
401*800a58d9SAndroid Build Coastguard Worker        for line in gcp_config_list_out.splitlines():
402*800a58d9SAndroid Build Coastguard Worker            project_match = _PROJECT_RE.match(line)
403*800a58d9SAndroid Build Coastguard Worker            if project_match:
404*800a58d9SAndroid Build Coastguard Worker                project = project_match.group("project")
405*800a58d9SAndroid Build Coastguard Worker                project_changed = (self.project != project)
406*800a58d9SAndroid Build Coastguard Worker                self.project = project
407*800a58d9SAndroid Build Coastguard Worker                continue
408*800a58d9SAndroid Build Coastguard Worker            zone_match = _ZONE_RE.match(line)
409*800a58d9SAndroid Build Coastguard Worker            if zone_match:
410*800a58d9SAndroid Build Coastguard Worker                self.zone = zone_match.group("zone")
411*800a58d9SAndroid Build Coastguard Worker                continue
412*800a58d9SAndroid Build Coastguard Worker        UpdateConfigFile(self.config_path, "project", self.project)
413*800a58d9SAndroid Build Coastguard Worker        UpdateConfigFile(self.config_path, "zone", self.zone)
414*800a58d9SAndroid Build Coastguard Worker        return project_changed
415*800a58d9SAndroid Build Coastguard Worker
416*800a58d9SAndroid Build Coastguard Worker    def _SetupClientIDSecret(self):
417*800a58d9SAndroid Build Coastguard Worker        """Setup Client ID / Client Secret in config file.
418*800a58d9SAndroid Build Coastguard Worker
419*800a58d9SAndroid Build Coastguard Worker        User can use input new values for Client ID and Client Secret.
420*800a58d9SAndroid Build Coastguard Worker        """
421*800a58d9SAndroid Build Coastguard Worker        print("Please generate a new client ID/secret by following the instructions here:")
422*800a58d9SAndroid Build Coastguard Worker        print("https://support.google.com/cloud/answer/6158849?hl=en")
423*800a58d9SAndroid Build Coastguard Worker        # TODO: Create markdown readme instructions since the link isn't too helpful.
424*800a58d9SAndroid Build Coastguard Worker        self.client_id = None
425*800a58d9SAndroid Build Coastguard Worker        self.client_secret = None
426*800a58d9SAndroid Build Coastguard Worker        while _InputIsEmpty(self.client_id):
427*800a58d9SAndroid Build Coastguard Worker            self.client_id = str(six.moves.input("Enter Client ID: ").strip())
428*800a58d9SAndroid Build Coastguard Worker        while _InputIsEmpty(self.client_secret):
429*800a58d9SAndroid Build Coastguard Worker            self.client_secret = str(six.moves.input("Enter Client Secret: ").strip())
430*800a58d9SAndroid Build Coastguard Worker        UpdateConfigFile(self.config_path, "client_id", self.client_id)
431*800a58d9SAndroid Build Coastguard Worker        UpdateConfigFile(self.config_path, "client_secret", self.client_secret)
432*800a58d9SAndroid Build Coastguard Worker
433*800a58d9SAndroid Build Coastguard Worker    def _CheckBillingEnable(self, gcloud_runner):
434*800a58d9SAndroid Build Coastguard Worker        """Check billing enabled in gcp project.
435*800a58d9SAndroid Build Coastguard Worker
436*800a58d9SAndroid Build Coastguard Worker        The billing info get by gcloud alpha command. Here is one example:
437*800a58d9SAndroid Build Coastguard Worker        $ gcloud alpha billing projects describe project_name
438*800a58d9SAndroid Build Coastguard Worker            billingAccountName: billingAccounts/011BXX-A30XXX-9XXXX
439*800a58d9SAndroid Build Coastguard Worker            billingEnabled: true
440*800a58d9SAndroid Build Coastguard Worker            name: projects/project_name/billingInfo
441*800a58d9SAndroid Build Coastguard Worker            projectId: project_name
442*800a58d9SAndroid Build Coastguard Worker
443*800a58d9SAndroid Build Coastguard Worker        Args:
444*800a58d9SAndroid Build Coastguard Worker            gcloud_runner: A GcloudRunner class to run "gcloud" command.
445*800a58d9SAndroid Build Coastguard Worker
446*800a58d9SAndroid Build Coastguard Worker        Raises:
447*800a58d9SAndroid Build Coastguard Worker            NoBillingError: gcp project doesn't enable billing account.
448*800a58d9SAndroid Build Coastguard Worker        """
449*800a58d9SAndroid Build Coastguard Worker        billing_info = gcloud_runner.RunGcloud(
450*800a58d9SAndroid Build Coastguard Worker            ["alpha", "billing", "projects", "describe", self.project])
451*800a58d9SAndroid Build Coastguard Worker        if _BILLING_ENABLE_MSG not in billing_info:
452*800a58d9SAndroid Build Coastguard Worker            raise errors.NoBillingError(
453*800a58d9SAndroid Build Coastguard Worker                "Please set billing account to project(%s) by following the "
454*800a58d9SAndroid Build Coastguard Worker                "instructions here: "
455*800a58d9SAndroid Build Coastguard Worker                "https://cloud.google.com/billing/docs/how-to/modify-project"
456*800a58d9SAndroid Build Coastguard Worker                % self.project)
457*800a58d9SAndroid Build Coastguard Worker
458*800a58d9SAndroid Build Coastguard Worker    @staticmethod
459*800a58d9SAndroid Build Coastguard Worker    def _EnableGcloudServices(gcloud_runner):
460*800a58d9SAndroid Build Coastguard Worker        """Enable 3 Gcloud API services.
461*800a58d9SAndroid Build Coastguard Worker
462*800a58d9SAndroid Build Coastguard Worker        1. Android build service
463*800a58d9SAndroid Build Coastguard Worker        2. Compute engine service
464*800a58d9SAndroid Build Coastguard Worker        To avoid confuse user, we don't show messages for services processing
465*800a58d9SAndroid Build Coastguard Worker        messages. e.g. "Waiting for async operation operations ...."
466*800a58d9SAndroid Build Coastguard Worker
467*800a58d9SAndroid Build Coastguard Worker        Args:
468*800a58d9SAndroid Build Coastguard Worker            gcloud_runner: A GcloudRunner class to run "gcloud" command.
469*800a58d9SAndroid Build Coastguard Worker        """
470*800a58d9SAndroid Build Coastguard Worker        google_apis = [
471*800a58d9SAndroid Build Coastguard Worker            GoogleAPIService(_ANDROID_BUILD_SERVICE, _ANDROID_BUILD_MSG),
472*800a58d9SAndroid Build Coastguard Worker            GoogleAPIService(_COMPUTE_ENGINE_SERVICE, _COMPUTE_ENGINE_MSG, required=True)
473*800a58d9SAndroid Build Coastguard Worker        ]
474*800a58d9SAndroid Build Coastguard Worker        enabled_services = gcloud_runner.RunGcloud(
475*800a58d9SAndroid Build Coastguard Worker            ["services", "list", "--enabled", "--format", "value(NAME)"],
476*800a58d9SAndroid Build Coastguard Worker            stderr=subprocess.STDOUT).splitlines()
477*800a58d9SAndroid Build Coastguard Worker
478*800a58d9SAndroid Build Coastguard Worker        for service in google_apis:
479*800a58d9SAndroid Build Coastguard Worker            if service.name not in enabled_services:
480*800a58d9SAndroid Build Coastguard Worker                service.EnableService(gcloud_runner)
481