1#!/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"""google SDK tools installer.
17
18This class will return the path to the google sdk tools and download them into
19a temporary dir if they don't exist. The caller is expected to call cleanup
20when they're done.
21
22Example usage:
23google_sdk = GoogleSDK()
24google_sdk_bin_path = google_sdk.GetSDKBinPath()
25
26# Caller is done.
27google_sdk.CleanUp()
28"""
29
30import logging
31import os
32import platform
33import shutil
34import sys
35import tempfile
36from six.moves import urllib
37
38from acloud import errors
39from acloud.internal.lib import utils
40
41
42logger = logging.getLogger(__name__)
43
44SDK_BIN_PATH = os.path.join("google-cloud-sdk", "bin")
45GCLOUD_BIN = "gcloud"
46GCLOUD_COMPONENT_NOT_INSTALLED = "Not Installed"
47GCP_SDK_VERSION = "209.0.0"
48GCP_SDK_TOOLS_URL = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads"
49LINUX_GCP_SDK_64_URL = "%s/google-cloud-sdk-%s-linux-x86_64.tar.gz" % (
50    GCP_SDK_TOOLS_URL, GCP_SDK_VERSION)
51LINUX_GCP_SDK_32_URL = "%s/google-cloud-sdk-%s-linux-x86.tar.gz" % (
52    GCP_SDK_TOOLS_URL, GCP_SDK_VERSION)
53WIN_GCP_SDK_64_URL = "%s/google-cloud-sdk-%s-windows-x86_64.zip" % (
54    GCP_SDK_TOOLS_URL, GCP_SDK_VERSION)
55WIN_GCP_SDK_32_URL = "%s/google-cloud-sdk-%s-windows-x86.zip" % (
56    GCP_SDK_TOOLS_URL, GCP_SDK_VERSION)
57MAC_GCP_SDK_64_URL = "%s/google-cloud-sdk-%s-darwin-x86_64.tar.gz" % (
58    GCP_SDK_TOOLS_URL, GCP_SDK_VERSION)
59MAC_GCP_SDK_32_URL = "%s/google-cloud-sdk-%s-darwin-x86.tar.gz" % (
60    GCP_SDK_TOOLS_URL, GCP_SDK_VERSION)
61LINUX = "linux"
62WIN = "windows"
63MAC = "darwin"
64
65
66def GetSdkUrl():
67    """Get google SDK tool url.
68
69    Return:
70        String, a URL of google SDK tools.
71
72    Raises:
73        OSTypeError when OS type is neither linux, mac, or windows.
74    """
75    is_64bits = sys.maxsize > 2**32
76    os_type = platform.system().lower()
77    if is_64bits:
78        if os_type == LINUX:
79            return LINUX_GCP_SDK_64_URL
80        elif os_type == MAC:
81            return MAC_GCP_SDK_64_URL
82        elif os_type == WIN:
83            return WIN_GCP_SDK_64_URL
84    else:
85        if os_type == LINUX:
86            return LINUX_GCP_SDK_32_URL
87        elif os_type == MAC:
88            return MAC_GCP_SDK_32_URL
89        elif os_type == WIN:
90            return WIN_GCP_SDK_32_URL
91    raise errors.OSTypeError("no gcloud for os type: %s" % (os_type))
92
93
94def SDKInstalled():
95    """Check google SDK tools installed.
96
97    We'll try to find where gcloud is and assume the other google sdk tools
98    live in the same location.
99
100    Return:
101        Boolean, return True if gcloud is installed, False otherwise.
102    """
103    if utils.FindExecutable(GCLOUD_BIN):
104        return True
105    return False
106
107
108class GoogleSDK(object):
109    """Google SDK tools installer."""
110
111    def __init__(self):
112        """GoogleSDKInstaller initialize.
113
114        Make sure the GCloud SDK is installed. If not, this function will assist
115        users to install.
116        """
117        self._tmp_path = None
118        self._tmp_sdk_path = None
119        if not SDKInstalled():
120            self.DownloadGcloudSDKAndExtract()
121
122    @staticmethod
123    def InstallGcloudComponent(gcloud_runner, component):
124        """Install gcloud component.
125
126        Args:
127            gcloud_runner: A GcloudRunner class to run "gcloud" command.
128            component: String, name of gcloud component.
129        """
130        result = gcloud_runner.RunGcloud([
131            "components", "list", "--format", "get(state.name)", "--filter",
132            "ID=%s" % component
133        ])
134        if result.strip() == GCLOUD_COMPONENT_NOT_INSTALLED:
135            gcloud_runner.RunGcloud(
136                ["components", "install", "--quiet", component])
137
138    def GetSDKBinPath(self):
139        """Get google SDK tools bin path.
140
141        We assumed there are google sdk tools somewhere and will raise if we
142        can't find it. The order we're looking for is:
143        1. Builtin gcloud (usually /usr/bin), we'll return /usr/bin with the
144           assumption other sdk tools live there.
145        2. Downloaded google sdk (self._tmp_dir), we assumed the caller already
146           downloaded/extracted and set the self._tmp_sdk_path for us to return.
147           We'll make sure it exists prior to returning it.
148
149        Return:
150            String, return google SDK tools bin path.
151
152        Raise:
153            NoGoogleSDKDetected if we can't find the sdk path.
154        """
155        builtin_gcloud = utils.FindExecutable(GCLOUD_BIN)
156        if builtin_gcloud:
157            return os.path.dirname(builtin_gcloud)
158        elif os.path.exists(self._tmp_sdk_path):
159            return self._tmp_sdk_path
160        raise errors.NoGoogleSDKDetected("no sdk path.")
161
162    def DownloadGcloudSDKAndExtract(self):
163        """Download the google SDK tools and decompress it.
164
165        Download the google SDK from the GCP web.
166        Reference https://cloud.google.com/sdk/docs/downloads-versioned-archives.
167        """
168        self._tmp_path = tempfile.mkdtemp(prefix="gcloud")
169        url = GetSdkUrl()
170        filename = url[url.rfind("/") + 1:]
171        file_path = os.path.join(self._tmp_path, filename)
172        logger.info("Download file from: %s", url)
173        logger.info("Save the file to: %s", file_path)
174        url_stream = urllib.request.urlopen(url)
175        metadata = url_stream.info()
176        file_size = int(metadata.get("Content-Length"))
177        logger.info("Downloading google SDK: %s bytes.", file_size)
178        with open(file_path, 'wb') as output:
179            output.write(url_stream.read())
180        utils.Decompress(file_path, self._tmp_path)
181        self._tmp_sdk_path = os.path.join(self._tmp_path, SDK_BIN_PATH)
182
183    def CleanUp(self):
184        """Clean google sdk tools install folder."""
185        if self._tmp_path and os.path.exists(self._tmp_path):
186            shutil.rmtree(self._tmp_path)
187