blob: 2cf4cf0ea4c353dff20f4dd54082f395fa7e2916 [file] [log] [blame]
herbertxue34776bb2018-07-03 21:57:48 +08001#!/usr/bin/env python
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Gcloud setup runner."""
17
18from __future__ import print_function
19import logging
20import os
21import re
22import subprocess
23
24from acloud import errors
25from acloud.internal.lib import utils
26from acloud.public import config
27from acloud.setup import base_task_runner
28from acloud.setup import google_sdk
29
herbertxue1512f8a2019-06-27 13:56:23 +080030
31logger = logging.getLogger(__name__)
32
herbertxue34776bb2018-07-03 21:57:48 +080033# APIs that need to be enabled for GCP project.
34_ANDROID_BUILD_SERVICE = "androidbuildinternal.googleapis.com"
herbertxue776cf922019-05-20 17:51:13 +080035_ANDROID_BUILD_MSG = (
36 "This service (%s) help to download images from Android Build. If it isn't "
37 "enabled, acloud only supports local images to create AVD."
38 % _ANDROID_BUILD_SERVICE)
herbertxue34776bb2018-07-03 21:57:48 +080039_COMPUTE_ENGINE_SERVICE = "compute.googleapis.com"
herbertxue776cf922019-05-20 17:51:13 +080040_COMPUTE_ENGINE_MSG = (
41 "This service (%s) help to create instance in google cloud platform. If it "
42 "isn't enabled, acloud can't work anymore." % _COMPUTE_ENGINE_SERVICE)
herbertxue34776bb2018-07-03 21:57:48 +080043_GOOGLE_CLOUD_STORAGE_SERVICE = "storage-component.googleapis.com"
herbertxue776cf922019-05-20 17:51:13 +080044_GOOGLE_CLOUD_STORAGE_MSG = (
45 "This service (%s) help to manage storage in google cloud platform. If it "
46 "isn't enabled, acloud can't work anymore." % _GOOGLE_CLOUD_STORAGE_SERVICE)
47_OPEN_SERVICE_FAILED_MSG = (
48 "\n[Open Service Failed]\n"
49 "Service name: %(service_name)s\n"
50 "%(service_msg)s\n")
51
herbertxue34776bb2018-07-03 21:57:48 +080052_BUILD_SERVICE_ACCOUNT = "android-build-prod@system.gserviceaccount.com"
herbertxued69dc512019-05-30 15:37:15 +080053_BILLING_ENABLE_MSG = "billingEnabled: true"
herbertxue34776bb2018-07-03 21:57:48 +080054_DEFAULT_SSH_FOLDER = os.path.expanduser("~/.ssh")
55_DEFAULT_SSH_KEY = "acloud_rsa"
56_DEFAULT_SSH_PRIVATE_KEY = os.path.join(_DEFAULT_SSH_FOLDER,
57 _DEFAULT_SSH_KEY)
58_DEFAULT_SSH_PUBLIC_KEY = os.path.join(_DEFAULT_SSH_FOLDER,
59 _DEFAULT_SSH_KEY + ".pub")
herbertxued69dc512019-05-30 15:37:15 +080060_GCLOUD_COMPONENT_ALPHA = "alpha"
herbertxueefb02a82018-10-08 12:02:54 +080061# Bucket naming parameters
62_BUCKET_HEADER = "gs://"
63_BUCKET_LENGTH_LIMIT = 63
64_DEFAULT_BUCKET_HEADER = "acloud"
65_DEFAULT_BUCKET_REGION = "US"
66_INVALID_BUCKET_NAME_END_CHARS = "_-"
67_PROJECT_SEPARATOR = ":"
herbertxue34776bb2018-07-03 21:57:48 +080068# Regular expression to get project/zone/bucket information.
69_BUCKET_RE = re.compile(r"^gs://(?P<bucket>.+)/")
70_BUCKET_REGION_RE = re.compile(r"^Location constraint:(?P<region>.+)")
71_PROJECT_RE = re.compile(r"^project = (?P<project>.+)")
72_ZONE_RE = re.compile(r"^zone = (?P<zone>.+)")
73
herbertxue34776bb2018-07-03 21:57:48 +080074
75def UpdateConfigFile(config_path, item, value):
76 """Update config data.
77
78 Case A: config file contain this item.
79 In config, "project = A_project". New value is B_project
80 Set config "project = B_project".
81 Case B: config file didn't contain this item.
82 New value is B_project.
83 Setup config as "project = B_project".
84
85 Args:
86 config_path: String, acloud config path.
87 item: String, item name in config file. EX: project, zone
88 value: String, value of item in config file.
89
90 TODO(111574698): Refactor this to minimize writes to the config file.
91 TODO(111574698): Use proto method to update config.
92 """
93 write_lines = []
94 find_item = False
95 write_line = item + ": \"" + value + "\"\n"
96 if os.path.isfile(config_path):
97 with open(config_path, "r") as cfg_file:
98 for read_line in cfg_file.readlines():
99 if read_line.startswith(item + ":"):
100 find_item = True
101 write_lines.append(write_line)
102 else:
103 write_lines.append(read_line)
104 if not find_item:
105 write_lines.append(write_line)
106 with open(config_path, "w") as cfg_file:
107 cfg_file.writelines(write_lines)
108
109
110def SetupSSHKeys(config_path, private_key_path, public_key_path):
111 """Setup the pair of the ssh key for acloud.config.
112
113 User can use the default path: "~/.ssh/acloud_rsa".
114
115 Args:
116 config_path: String, acloud config path.
117 private_key_path: Path to the private key file.
118 e.g. ~/.ssh/acloud_rsa
119 public_key_path: Path to the public key file.
120 e.g. ~/.ssh/acloud_rsa.pub
121 """
122 private_key_path = os.path.expanduser(private_key_path)
123 if (private_key_path == "" or public_key_path == ""
124 or private_key_path == _DEFAULT_SSH_PRIVATE_KEY):
125 utils.CreateSshKeyPairIfNotExist(_DEFAULT_SSH_PRIVATE_KEY,
126 _DEFAULT_SSH_PUBLIC_KEY)
127 UpdateConfigFile(config_path, "ssh_private_key_path",
128 _DEFAULT_SSH_PRIVATE_KEY)
129 UpdateConfigFile(config_path, "ssh_public_key_path",
130 _DEFAULT_SSH_PUBLIC_KEY)
131
132
133def _InputIsEmpty(input_string):
134 """Check input string is empty.
135
136 Tool requests user to input client ID & client secret.
137 This basic check can detect user input is empty.
138
139 Args:
140 input_string: String, user input string.
141
142 Returns:
143 Boolean: True if input is empty, False otherwise.
144 """
145 if input_string is None:
146 return True
147 if input_string == "":
148 print("Please enter a non-empty value.")
149 return True
150 return False
151
152
153class GoogleSDKBins(object):
154 """Class to run tools in the Google SDK."""
155
156 def __init__(self, google_sdk_folder):
157 """GoogleSDKBins initialize.
158
159 Args:
160 google_sdk_folder: String, google sdk path.
161 """
162 self.gcloud_command_path = os.path.join(google_sdk_folder, "gcloud")
163 self.gsutil_command_path = os.path.join(google_sdk_folder, "gsutil")
164
165 def RunGcloud(self, cmd, **kwargs):
166 """Run gcloud command.
167
168 Args:
169 cmd: String list, command strings.
170 Ex: [config], then this function call "gcloud config".
171 **kwargs: dictionary of keyword based args to pass to func.
172
173 Returns:
174 String, return message after execute gcloud command.
175 """
176 return subprocess.check_output([self.gcloud_command_path] + cmd, **kwargs)
177
178 def RunGsutil(self, cmd, **kwargs):
179 """Run gsutil command.
180
181 Args:
182 cmd : String list, command strings.
183 Ex: [list], then this function call "gsutil list".
184 **kwargs: dictionary of keyword based args to pass to func.
185
186 Returns:
187 String, return message after execute gsutil command.
188 """
189 return subprocess.check_output([self.gsutil_command_path] + cmd, **kwargs)
190
191
herbertxue776cf922019-05-20 17:51:13 +0800192class GoogleAPIService(object):
193 """Class to enable api service in the gcp project."""
194
195 def __init__(self, service_name, error_msg, required=False):
196 """GoogleAPIService initialize.
197
198 Args:
199 service_name: String, name of api service.
200 error_msg: String, show messages if api service enable failed.
201 required: Boolean, True for service must be enabled for acloud.
202 """
203 self._name = service_name
204 self._error_msg = error_msg
205 self._required = required
206
207 def EnableService(self, gcloud_runner):
208 """Enable api service.
209
210 Args:
211 gcloud_runner: A GcloudRunner class to run "gcloud" command.
212 """
213 try:
214 gcloud_runner.RunGcloud(["services", "enable", self._name],
215 stderr=subprocess.STDOUT)
216 except subprocess.CalledProcessError as error:
217 self.ShowFailMessages(error.output)
218
219 def ShowFailMessages(self, error):
220 """Show fail messages.
221
222 Show the fail messages to hint users the impact if the api service
223 isn't enabled.
224
225 Args:
226 error: String of error message when opening api service failed.
227 """
228 msg_color = (utils.TextColors.FAIL if self._required else
229 utils.TextColors.WARNING)
230 utils.PrintColorString(
231 error + _OPEN_SERVICE_FAILED_MSG % {
232 "service_name": self._name,
233 "service_msg": self._error_msg}
234 , msg_color)
235
236 @property
237 def name(self):
238 """Return name."""
239 return self._name
240
241
herbertxue34776bb2018-07-03 21:57:48 +0800242class GcpTaskRunner(base_task_runner.BaseTaskRunner):
243 """Runner to setup google cloud user information."""
244
245 WELCOME_MESSAGE_TITLE = "Setup google cloud user information"
246 WELCOME_MESSAGE = (
247 "This step will walk you through gcloud SDK installation."
248 "Then configure gcloud user information."
249 "Finally enable some gcloud API services.")
250
251 def __init__(self, config_path):
252 """Initialize parameters.
253
254 Load config file to get current values.
255
256 Args:
257 config_path: String, acloud config path.
258 """
Kevin Cheng223acee2019-03-18 10:25:06 -0700259 # pylint: disable=invalid-name
herbertxue34776bb2018-07-03 21:57:48 +0800260 config_mgr = config.AcloudConfigManager(config_path)
261 cfg = config_mgr.Load()
262 self.config_path = config_mgr.user_config_path
herbertxue34776bb2018-07-03 21:57:48 +0800263 self.project = cfg.project
264 self.zone = cfg.zone
265 self.storage_bucket_name = cfg.storage_bucket_name
266 self.ssh_private_key_path = cfg.ssh_private_key_path
267 self.ssh_public_key_path = cfg.ssh_public_key_path
Kevin Chengcc6bf0d2018-10-10 14:18:47 -0700268 self.stable_host_image_name = cfg.stable_host_image_name
Kevin Cheng223acee2019-03-18 10:25:06 -0700269 self.client_id = cfg.client_id
270 self.client_secret = cfg.client_secret
271 self.service_account_name = cfg.service_account_name
272 self.service_account_private_key_path = cfg.service_account_private_key_path
273 self.service_account_json_private_key_path = cfg.service_account_json_private_key_path
herbertxue34776bb2018-07-03 21:57:48 +0800274
Kevin Cheng58b7e792018-10-05 02:26:53 -0700275 def ShouldRun(self):
276 """Check if we actually need to run GCP setup.
277
278 We'll only do the gcp setup if certain fields in the cfg are empty.
279
280 Returns:
281 True if reqired config fields are empty, False otherwise.
282 """
Kevin Cheng223acee2019-03-18 10:25:06 -0700283 # We need to ensure the config has the proper auth-related fields set,
284 # so config requires just 1 of the following:
285 # 1. client id/secret
286 # 2. service account name/private key path
287 # 3. service account json private key path
288 if ((not self.client_id or not self.client_secret)
289 and (not self.service_account_name or not self.service_account_private_key_path)
290 and not self.service_account_json_private_key_path):
291 return True
292
293 # If a project isn't set, then we need to run setup.
294 return not self.project
Kevin Cheng58b7e792018-10-05 02:26:53 -0700295
herbertxue34776bb2018-07-03 21:57:48 +0800296 def _Run(self):
297 """Run GCP setup task."""
298 self._SetupGcloudInfo()
299 SetupSSHKeys(self.config_path, self.ssh_private_key_path,
300 self.ssh_public_key_path)
301
302 def _SetupGcloudInfo(self):
303 """Setup Gcloud user information.
304 1. Setup Gcloud SDK tools.
305 2. Setup Gcloud project.
306 a. Setup Gcloud project and zone.
307 b. Setup Client ID and Client secret.
308 c. Setup Google Cloud Storage bucket.
309 3. Enable Gcloud API services.
310 """
311 google_sdk_init = google_sdk.GoogleSDK()
312 try:
313 google_sdk_runner = GoogleSDKBins(google_sdk_init.GetSDKBinPath())
herbertxued69dc512019-05-30 15:37:15 +0800314 google_sdk_init.InstallGcloudComponent(google_sdk_runner,
315 _GCLOUD_COMPONENT_ALPHA)
herbertxue34776bb2018-07-03 21:57:48 +0800316 self._SetupProject(google_sdk_runner)
317 self._EnableGcloudServices(google_sdk_runner)
Kevin Chengcc6bf0d2018-10-10 14:18:47 -0700318 self._CreateStableHostImage()
herbertxue34776bb2018-07-03 21:57:48 +0800319 finally:
320 google_sdk_init.CleanUp()
321
Kevin Chengcc6bf0d2018-10-10 14:18:47 -0700322 def _CreateStableHostImage(self):
323 """Create the stable host image."""
324 # Write default stable_host_image_name with dummy value.
325 # TODO(113091773): An additional step to create the host image.
326 if not self.stable_host_image_name:
327 UpdateConfigFile(self.config_path, "stable_host_image_name", "")
328
329
herbertxue34776bb2018-07-03 21:57:48 +0800330 def _NeedProjectSetup(self):
331 """Confirm project setup should run or not.
332
333 If the project settings (project name and zone) are blank (either one),
334 we'll run the project setup flow. If they are set, we'll check with
335 the user if they want to update them.
336
337 Returns:
338 Boolean: True if we need to setup the project, False otherwise.
339 """
340 user_question = (
341 "Your default Project/Zone settings are:\n"
342 "project:[%s]\n"
343 "zone:[%s]\n"
Sam Chiu705b9012019-01-19 12:11:35 +0800344 "Would you like to update them?[y/N]: \n") % (self.project, self.zone)
herbertxue34776bb2018-07-03 21:57:48 +0800345
346 if not self.project or not self.zone:
347 logger.info("Project or zone is empty. Start to run setup process.")
348 return True
349 return utils.GetUserAnswerYes(user_question)
350
351 def _NeedClientIDSetup(self, project_changed):
352 """Confirm client setup should run or not.
353
354 If project changed, client ID must also have to change.
355 So tool will force to run setup function.
356 If client ID or client secret is empty, tool force to run setup function.
357 If project didn't change and config hold user client ID/secret, tool
358 would skip client ID setup.
359
360 Args:
361 project_changed: Boolean, True for project changed.
362
363 Returns:
364 Boolean: True for run setup function.
365 """
366 if project_changed:
367 logger.info("Your project changed. Start to run setup process.")
368 return True
369 elif not self.client_id or not self.client_secret:
370 logger.info("Client ID or client secret is empty. Start to run setup process.")
371 return True
372 logger.info("Project was unchanged and client ID didn't need to changed.")
373 return False
374
375 def _SetupProject(self, gcloud_runner):
376 """Setup gcloud project information.
377
378 Setup project and zone.
379 Setup client ID and client secret.
herbertxued69dc512019-05-30 15:37:15 +0800380 Make sure billing account enabled in project.
herbertxue34776bb2018-07-03 21:57:48 +0800381 Setup Google Cloud Storage bucket.
382
383 Args:
384 gcloud_runner: A GcloudRunner class to run "gcloud" command.
385 """
386 project_changed = False
387 if self._NeedProjectSetup():
388 project_changed = self._UpdateProject(gcloud_runner)
389 if self._NeedClientIDSetup(project_changed):
390 self._SetupClientIDSecret()
herbertxued69dc512019-05-30 15:37:15 +0800391 self._CheckBillingEnable(gcloud_runner)
herbertxue34776bb2018-07-03 21:57:48 +0800392 self._SetupStorageBucket(gcloud_runner)
393
394 def _UpdateProject(self, gcloud_runner):
395 """Setup gcloud project name and zone name and check project changed.
396
397 Run "gcloud init" to handle gcloud project setup.
398 Then "gcloud list" to get user settings information include "project" & "zone".
399 Record project_changed for next setup steps.
400
401 Args:
402 gcloud_runner: A GcloudRunner class to run "gcloud" command.
403
404 Returns:
405 project_changed: True for project settings changed.
406 """
407 project_changed = False
408 gcloud_runner.RunGcloud(["init"])
409 gcp_config_list_out = gcloud_runner.RunGcloud(["config", "list"])
410 for line in gcp_config_list_out.splitlines():
411 project_match = _PROJECT_RE.match(line)
412 if project_match:
413 project = project_match.group("project")
414 project_changed = (self.project != project)
415 self.project = project
416 continue
417 zone_match = _ZONE_RE.match(line)
418 if zone_match:
419 self.zone = zone_match.group("zone")
420 continue
421 UpdateConfigFile(self.config_path, "project", self.project)
422 UpdateConfigFile(self.config_path, "zone", self.zone)
423 return project_changed
424
425 def _SetupClientIDSecret(self):
426 """Setup Client ID / Client Secret in config file.
427
428 User can use input new values for Client ID and Client Secret.
429 """
430 print("Please generate a new client ID/secret by following the instructions here:")
431 print("https://support.google.com/cloud/answer/6158849?hl=en")
432 # TODO: Create markdown readme instructions since the link isn't too helpful.
433 self.client_id = None
434 self.client_secret = None
435 while _InputIsEmpty(self.client_id):
436 self.client_id = str(raw_input("Enter Client ID: ").strip())
437 while _InputIsEmpty(self.client_secret):
438 self.client_secret = str(raw_input("Enter Client Secret: ").strip())
439 UpdateConfigFile(self.config_path, "client_id", self.client_id)
440 UpdateConfigFile(self.config_path, "client_secret", self.client_secret)
441
herbertxued69dc512019-05-30 15:37:15 +0800442 def _CheckBillingEnable(self, gcloud_runner):
443 """Check billing enabled in gcp project.
444
445 The billing info get by gcloud alpha command. Here is one example:
446 $ gcloud alpha billing projects describe project_name
447 billingAccountName: billingAccounts/011BXX-A30XXX-9XXXX
448 billingEnabled: true
449 name: projects/project_name/billingInfo
450 projectId: project_name
451
452 Args:
453 gcloud_runner: A GcloudRunner class to run "gcloud" command.
454
455 Raises:
456 NoBillingError: gcp project doesn't enable billing account.
457 """
458 billing_info = gcloud_runner.RunGcloud(
459 ["alpha", "billing", "projects", "describe", self.project])
460 if _BILLING_ENABLE_MSG not in billing_info:
461 raise errors.NoBillingError(
462 "Please set billing account to project(%s) by following the "
463 "instructions here: "
464 "https://cloud.google.com/billing/docs/how-to/modify-project"
465 % self.project)
466
herbertxue34776bb2018-07-03 21:57:48 +0800467 def _SetupStorageBucket(self, gcloud_runner):
468 """Setup storage_bucket_name in config file.
469
470 We handle the following cases:
471 1. Bucket set in the config && bucket is valid.
472 - Configure the bucket.
473 2. Bucket set in the config && bucket is invalid.
474 - Create a default acloud bucket and configure it
475 3. Bucket is not set in the config.
476 - Create a default acloud bucket and configure it.
477
478 Args:
479 gcloud_runner: A GcloudRunner class to run "gsutil" command.
480 """
481 if (not self.storage_bucket_name
482 or not self._BucketIsValid(self.storage_bucket_name, gcloud_runner)):
483 self.storage_bucket_name = self._CreateDefaultBucket(gcloud_runner)
484 self._ConfigureBucket(gcloud_runner)
485 UpdateConfigFile(self.config_path, "storage_bucket_name",
486 self.storage_bucket_name)
487 logger.info("Storage bucket name set to [%s]", self.storage_bucket_name)
488
489 def _ConfigureBucket(self, gcloud_runner):
490 """Setup write access right for Android Build service account.
491
492 To avoid confuse user, we don't show messages for processing messages.
493 e.g. "No changes to gs://acloud-bucket/"
494
495 Args:
496 gcloud_runner: A GcloudRunner class to run "gsutil" command.
497 """
498 gcloud_runner.RunGsutil([
499 "acl", "ch", "-u",
500 "%s:W" % (_BUILD_SERVICE_ACCOUNT),
501 "%s" % (_BUCKET_HEADER + self.storage_bucket_name)
502 ], stderr=subprocess.STDOUT)
503
504 def _BucketIsValid(self, bucket_name, gcloud_runner):
505 """Check bucket is valid or not.
506
507 If bucket exists and region is in default region,
508 then this bucket is valid.
509
510 Args:
511 bucket_name: String, name of storage bucket.
512 gcloud_runner: A GcloudRunner class to run "gsutil" command.
513
514 Returns:
515 Boolean: True if bucket is valid, otherwise False.
516 """
517 return (self._BucketExists(bucket_name, gcloud_runner) and
518 self._BucketInDefaultRegion(bucket_name, gcloud_runner))
519
520 def _CreateDefaultBucket(self, gcloud_runner):
521 """Setup bucket to default bucket name.
522
523 Default bucket name is "acloud-{project}".
524 If default bucket exist and its region is not "US",
525 then default bucket name is changed as "acloud-{project}-us"
526 If default bucket didn't exist, tool will create it.
527
528 Args:
529 gcloud_runner: A GcloudRunner class to run "gsutil" command.
530
531 Returns:
532 String: string of bucket name.
533 """
herbertxueefb02a82018-10-08 12:02:54 +0800534 bucket_name = self._GenerateBucketName(self.project)
herbertxue34776bb2018-07-03 21:57:48 +0800535 if (self._BucketExists(bucket_name, gcloud_runner) and
536 not self._BucketInDefaultRegion(bucket_name, gcloud_runner)):
537 bucket_name += ("-" + _DEFAULT_BUCKET_REGION.lower())
538 if not self._BucketExists(bucket_name, gcloud_runner):
539 self._CreateBucket(bucket_name, gcloud_runner)
540 return bucket_name
541
542 @staticmethod
herbertxueefb02a82018-10-08 12:02:54 +0800543 def _GenerateBucketName(project_name):
544 """Generate GCS bucket name that meets the naming guidelines.
545
546 Naming guidelines: https://cloud.google.com/storage/docs/naming
547 1. Filter out organization name.
548 2. Filter out illegal characters.
549 3. Length limit.
550 4. Name must end with a number or letter.
551
552 Args:
553 project_name: String, name of project.
554
555 Returns:
556 String: GCS bucket name compliant with naming guidelines.
557 """
558 # Sanitize the project name by filtering out the org name (e.g.
559 # AOSP:fake_project -> fake_project)
560 if _PROJECT_SEPARATOR in project_name:
561 _, project_name = project_name.split(_PROJECT_SEPARATOR)
562
563 bucket_name = "%s-%s" % (_DEFAULT_BUCKET_HEADER, project_name)
564
565 # Rule 1: A bucket name can contain lowercase alphanumeric characters,
566 # hyphens, and underscores.
567 bucket_name = re.sub("[^a-zA-Z_/-]+", "", bucket_name).lower()
568
569 # Rule 2: Bucket names must limit to 63 characters.
570 if len(bucket_name) > _BUCKET_LENGTH_LIMIT:
571 bucket_name = bucket_name[:_BUCKET_LENGTH_LIMIT]
572
573 # Rule 3: Bucket names must end with a letter, strip out any ending
574 # "-" or "_" at the end of the name.
575 bucket_name = bucket_name.rstrip(_INVALID_BUCKET_NAME_END_CHARS)
576
577 return bucket_name
578
579 @staticmethod
herbertxue34776bb2018-07-03 21:57:48 +0800580 def _BucketExists(bucket_name, gcloud_runner):
581 """Confirm bucket exist in project or not.
582
583 Args:
584 bucket_name: String, name of storage bucket.
585 gcloud_runner: A GcloudRunner class to run "gsutil" command.
586
587 Returns:
588 Boolean: True for bucket exist in project.
589 """
590 output = gcloud_runner.RunGsutil(["list"])
591 for output_line in output.splitlines():
592 match = _BUCKET_RE.match(output_line)
593 if match.group("bucket") == bucket_name:
594 return True
595 return False
596
597 @staticmethod
598 def _BucketInDefaultRegion(bucket_name, gcloud_runner):
599 """Confirm bucket region settings is "US" or not.
600
601 Args:
602 bucket_name: String, name of storage bucket.
603 gcloud_runner: A GcloudRunner class to run "gsutil" command.
604
605 Returns:
606 Boolean: True for bucket region is in default region.
607
608 Raises:
609 errors.SetupError: For parsing bucket region information error.
610 """
611 output = gcloud_runner.RunGsutil(
612 ["ls", "-L", "-b", "%s" % (_BUCKET_HEADER + bucket_name)])
613 for region_line in output.splitlines():
614 region_match = _BUCKET_REGION_RE.match(region_line.strip())
615 if region_match:
616 region = region_match.group("region").strip()
617 logger.info("Bucket[%s] is in %s (checking for %s)", bucket_name,
618 region, _DEFAULT_BUCKET_REGION)
619 if region == _DEFAULT_BUCKET_REGION:
620 return True
621 return False
622 raise errors.ParseBucketRegionError("Could not determine bucket region.")
623
624 @staticmethod
625 def _CreateBucket(bucket_name, gcloud_runner):
626 """Create new storage bucket in project.
627
628 Args:
629 bucket_name: String, name of storage bucket.
630 gcloud_runner: A GcloudRunner class to run "gsutil" command.
631 """
632 gcloud_runner.RunGsutil(["mb", "%s" % (_BUCKET_HEADER + bucket_name)])
633 logger.info("Create bucket [%s].", bucket_name)
634
635 @staticmethod
636 def _EnableGcloudServices(gcloud_runner):
637 """Enable 3 Gcloud API services.
638
639 1. Android build service
640 2. Compute engine service
641 3. Google cloud storage service
642 To avoid confuse user, we don't show messages for services processing
643 messages. e.g. "Waiting for async operation operations ...."
644
645 Args:
646 gcloud_runner: A GcloudRunner class to run "gcloud" command.
647 """
herbertxue776cf922019-05-20 17:51:13 +0800648 google_apis = [
649 GoogleAPIService(_GOOGLE_CLOUD_STORAGE_SERVICE, _GOOGLE_CLOUD_STORAGE_MSG),
650 GoogleAPIService(_ANDROID_BUILD_SERVICE, _ANDROID_BUILD_MSG),
651 GoogleAPIService(_COMPUTE_ENGINE_SERVICE, _COMPUTE_ENGINE_MSG, required=True)
652 ]
653 enabled_services = gcloud_runner.RunGcloud(
654 ["services", "list", "--enabled", "--format", "value(NAME)"],
655 stderr=subprocess.STDOUT).splitlines()
656
657 for service in google_apis:
658 if service.name not in enabled_services:
659 service.EnableService(gcloud_runner)