blob: 6429f7b667e812decf3f8f5a6a9a8dd6ccbf13fc [file] [log] [blame]
Jon Wayne Parrottbbc39432016-10-27 23:12:39 -07001# Copyright 2016 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Noxfile for automating system tests.
16
17This file handles setting up environments needed by the system tests. This
18separates the tests from their environment configuration.
19
20See the `nox docs`_ for details on how this file works:
21
22.. _nox docs: http://nox.readthedocs.io/en/latest/
23"""
24
25import os
Jon Wayne Parrottb551ef22016-10-28 12:35:15 -070026import subprocess
Jon Wayne Parrottbbc39432016-10-27 23:12:39 -070027
28from nox.command import which
29import py.path
30
31
Jon Wayne Parrottb551ef22016-10-28 12:35:15 -070032HERE = os.path.abspath(os.path.dirname(__file__))
Jon Wayne Parrottbbc39432016-10-27 23:12:39 -070033DATA_DIR = os.path.join(HERE, 'data')
34SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json')
35AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json')
36EXPLICIT_CREDENTIALS_ENV = 'GOOGLE_APPLICATION_CREDENTIALS'
37EXPLICIT_PROJECT_ENV = 'GOOGLE_CLOUD_PROJECT'
38EXPECT_PROJECT_ENV = 'EXPECT_PROJECT_ID'
39
Jon Wayne Parrottb551ef22016-10-28 12:35:15 -070040SKIP_GAE_TEST_ENV = 'SKIP_APP_ENGINE_SYSTEM_TEST'
41GAE_APP_URL_TMPL = 'https://{}-dot-{}.appspot.com'
42GAE_TEST_APP_SERVICE = 'google-auth-system-tests'
43
Jon Wayne Parrottbbc39432016-10-27 23:12:39 -070044# The download location for the Cloud SDK
45CLOUD_SDK_DIST_FILENAME = 'google-cloud-sdk.tar.gz'
46CLOUD_SDK_DOWNLOAD_URL = (
47 'https://dl.google.com/dl/cloudsdk/release/{}'.format(
48 CLOUD_SDK_DIST_FILENAME))
49
50# This environment variable is recognized by the Cloud SDK and overrides
51# the location of the SDK's configuration files (which is usually at
52# ${HOME}/.config).
53CLOUD_SDK_CONFIG_ENV = 'CLOUDSDK_CONFIG'
54
55# If set, this is where the environment setup will install the Cloud SDK.
56# If unset, it will download the SDK to a temporary directory.
57CLOUD_SDK_ROOT = os.environ.get('CLOUD_SDK_ROOT')
58
59if CLOUD_SDK_ROOT is not None:
60 CLOUD_SDK_ROOT = py.path.local(CLOUD_SDK_ROOT)
61 CLOUD_SDK_ROOT.ensure(dir=True) # Makes sure the directory exists.
62else:
63 CLOUD_SDK_ROOT = py.path.local.mkdtemp()
64
65# The full path the cloud sdk install directory
66CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.join('google-cloud-sdk')
67
68# The full path to the gcloud cli executable.
69GCLOUD = str(CLOUD_SDK_INSTALL_DIR.join('bin', 'gcloud'))
70
71# gcloud requires Python 2 and doesn't work on 3, so we need to tell it
72# where to find 2 when we're running in a 3 environment.
73CLOUD_SDK_PYTHON_ENV = 'CLOUDSDK_PYTHON'
74CLOUD_SDK_PYTHON = which('python2', None)
75
76# Cloud SDK helpers
77
78
79def install_cloud_sdk(session):
80 """Downloads and installs the Google Cloud SDK."""
81 # Configure environment variables needed by the SDK.
82 # This sets the config root to the tests' config root. This prevents
83 # our tests from clobbering a developer's configuration when running
84 # these tests locally.
85 session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT)
86 # This tells gcloud which Python interpreter to use (always use 2.7)
87 session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON
88
Jon Wayne Parrottb551ef22016-10-28 12:35:15 -070089 # If gcloud cli executable already exists, we don't need to do anything
90 # else.
Jon Wayne Parrottbbc39432016-10-27 23:12:39 -070091 # Note that because of this we do not attempt to update the sdk -
92 # if the CLOUD_SDK_ROOT is cached, it will need to be periodically cleared.
93 if py.path.local(GCLOUD).exists():
94 return
95
96 tar_path = CLOUD_SDK_ROOT.join(CLOUD_SDK_DIST_FILENAME)
97
98 # Download the release.
99 session.run(
100 'wget', CLOUD_SDK_DOWNLOAD_URL, '-O', str(tar_path), silent=True)
101
102 # Extract the release.
103 session.run(
104 'tar', 'xzf', str(tar_path), '-C', str(CLOUD_SDK_ROOT))
105 session.run(tar_path.remove)
106
107 # Run the install script.
108 session.run(
109 str(CLOUD_SDK_INSTALL_DIR.join('install.sh')),
110 '--usage-reporting', 'false',
111 '--path-update', 'false',
112 '--command-completion', 'false',
113 silent=True)
114
115
116def copy_credentials(credentials_path):
117 """Copies credentials into the SDK root as the application default
118 credentials."""
119 dest = CLOUD_SDK_ROOT.join('application_default_credentials.json')
120 if dest.exists():
121 dest.remove()
122 py.path.local(credentials_path).copy(dest)
123
124
125def configure_cloud_sdk(
126 session, application_default_credentials, project=False):
127 """Installs and configures the Cloud SDK with the given application default
128 credentials.
129
130 If project is True, then a project will be set in the active config.
131 If it is false, this will ensure no project is set.
132 """
133 install_cloud_sdk(session)
134
135 if project:
136 session.run(GCLOUD, 'config', 'set', 'project', 'example-project')
137 else:
138 session.run(GCLOUD, 'config', 'unset', 'project')
139
140 # Copy the credentials file to the config root. This is needed because
141 # unfortunately gcloud doesn't provide a clean way to tell it to use
142 # a particular set of credentials. However, this does verify that gcloud
143 # also considers the credentials valid by calling application-default
144 # print-access-token
145 session.run(copy_credentials, application_default_credentials)
146
147 # Calling this forces the Cloud SDK to read the credentials we just wrote
148 # and obtain a new access token with those credentials. This validates
149 # that our credentials matches the format expected by gcloud.
150 # Silent is set to True to prevent leaking secrets in test logs.
151 session.run(
152 GCLOUD, 'auth', 'application-default', 'print-access-token',
153 silent=True)
154
155
156# Test sesssions
157
158
159def session_service_account(session):
160 session.virtualenv = False
161 session.run('pytest', 'test_service_account.py')
162
163
164def session_oauth2_credentials(session):
165 session.virtualenv = False
166 session.run('pytest', 'test_oauth2_credentials.py')
167
168
169def session_default_explicit_service_account(session):
170 session.virtualenv = False
171 session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE
172 session.env[EXPECT_PROJECT_ENV] = '1'
173 session.run('pytest', 'test_default.py')
174
175
176def session_default_explicit_authorized_user(session):
177 session.virtualenv = False
178 session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE
179 session.run('pytest', 'test_default.py')
180
181
182def session_default_explicit_authorized_user_explicit_project(session):
183 session.virtualenv = False
184 session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE
185 session.env[EXPLICIT_PROJECT_ENV] = 'example-project'
186 session.env[EXPECT_PROJECT_ENV] = '1'
187 session.run('pytest', 'test_default.py')
188
189
190def session_default_cloud_sdk_service_account(session):
191 session.virtualenv = False
192 configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE)
193 session.env[EXPECT_PROJECT_ENV] = '1'
194 session.run('pytest', 'test_default.py')
195
196
197def session_default_cloud_sdk_authorized_user(session):
198 session.virtualenv = False
199 configure_cloud_sdk(session, AUTHORIZED_USER_FILE)
200 session.run('pytest', 'test_default.py')
201
202
203def session_default_cloud_sdk_authorized_user_configured_project(session):
204 session.virtualenv = False
205 configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True)
206 session.env[EXPECT_PROJECT_ENV] = '1'
207 session.run('pytest', 'test_default.py')
208
209
210def session_compute_engine(session):
211 session.virtualenv = False
212 session.run('pytest', 'test_compute_engine.py')
213
214
215def session_app_engine(session):
216 session.virtualenv = False
Jon Wayne Parrottb551ef22016-10-28 12:35:15 -0700217
218 if SKIP_GAE_TEST_ENV in os.environ:
219 session.log('Skipping App Engine tests.')
220 return
221
222 # Unlike the default tests above, the App Engine system test require a
223 # 'real' gcloud sdk installation that is configured to deploy to an
224 # app engine project.
225 # Grab the project ID from the cloud sdk.
226 project_id = subprocess.check_output([
227 'gcloud', 'config', 'list', 'project', '--format',
228 'value(core.project)']).strip()
229
230 if not project_id:
231 session.error(
232 'The Cloud SDK must be installed and configured to deploy to App '
233 'Engine.')
234
235 application_url = GAE_APP_URL_TMPL.format(
236 GAE_TEST_APP_SERVICE, project_id)
237
238 # Vendor in the test application's dependencies
239 session.chdir(os.path.join(HERE, 'app_engine_test_app'))
240 session.run(
241 'pip', 'install', '--target', 'lib', '-r', 'requirements.txt',
242 silent=True)
243
244 # Deploy the application.
245 session.run('gcloud', 'app', 'deploy', '-q', 'app.yaml')
246
247 # Run the tests
248 session.env['TEST_APP_URL'] = application_url
249 session.chdir(HERE)
250 session.run('pytest', 'test_app_engine.py')