| #!/usr/bin/env python2 |
| # -*- coding: utf-8 -*- |
| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Helper script to deploy the app.""" |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| import contextlib |
| import os |
| import getpass |
| |
| import fire |
| |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import osutils |
| import jinja2 |
| from werkzeug import security |
| |
| |
| _CIDB_CRED_DIR = 'creds/cidb' |
| |
| # See "instance connection name" at |
| # https://pantheon.corp.google.com/sql/instances/ |
| # cidb-gen2-replica2/overview?project=cosmic-strategy-646 |
| _CIDB_INSTANCE = { # Indexed with $DEBUG |
| False: 'cosmic-strategy-646:us-central1:cidb-gen2', |
| True: 'cosmic-strategy-646:us-central1:debug-cidb-gen2', |
| } |
| _CIDB_UNIX_SOCKET_CONTENTS = '/cloudsql/{instance}' |
| |
| |
| def _PrepareCreds(basedir, debug): |
| """Deploy the prepared app from basedir. |
| |
| Args: |
| basedir: The base directory where the app has already been prepped. |
| debug: Whether to use debug creds. |
| """ |
| suffix = '.dbg' if debug else '' |
| cidb_cred_path = os.path.join(basedir, _CIDB_CRED_DIR + suffix) |
| contents = _CIDB_UNIX_SOCKET_CONTENTS.format( |
| instance=_CIDB_INSTANCE[debug]) |
| osutils.WriteFile( |
| os.path.join(cidb_cred_path, 'unix_socket.txt'), |
| contents) |
| |
| |
| @contextlib.contextmanager |
| def _PrepareAppFolder(debug): |
| """Copies this folder and its symlink'd dependencies into a temporary dir. |
| |
| Args: |
| debug: Whether to use debug creds |
| |
| Returns: |
| A contextmanager that yields a temporary directory and cleans up afterward. |
| """ |
| with osutils.TempDir() as tempdir: |
| # This is rsync in 'archive' mode, but symlinks are followed to copy actual |
| # files/directories. |
| rsync_cmd = ['rsync', '-qrLgotD', |
| '--exclude', '*/*.pyc', |
| '--exclude', 'venv', # Needed to avoid infinite chromite loop |
| '--exclude', 'env', # Our development virtualenv |
| '--exclude', 'creds/*/*.pem', # Sensitive cidb files |
| '--exclude', '*/appengine', # Appengine SDK not needed |
| '--exclude', '.git', |
| '--exclude', '*/.git'] |
| cros_build_lib.RunCommand(rsync_cmd + ['.', tempdir], |
| cwd=os.path.dirname(__file__)) |
| _PrepareCreds(tempdir, debug) |
| yield tempdir |
| |
| |
| def _FillAppTemplate(tempdir, app, debug, password_file): |
| """Fills the values in app.yaml template. |
| |
| Fills the ADMIN_PASSWORD_HASH value, and fills in other values depending on |
| whether we're deploying to the |debug| service. |
| |
| Args: |
| app: The yaml file to use. |
| tempdir: The deployment directory containing the app.yaml template. |
| debug: Whether to deploy as the debug service. |
| password_file: A path to the password file, or None for getpass() |
| """ |
| password = ( |
| osutils.ReadFile(password_file) if password_file |
| else getpass.getpass()).rstrip('\n') |
| |
| hashed = security.generate_password_hash(password) |
| |
| env = jinja2.Environment( |
| loader=jinja2.FileSystemLoader(tempdir)) |
| contents = env.get_template(app).stream( |
| admin_password_hash=hashed, |
| instance_connection_name=_CIDB_INSTANCE[debug], |
| debug=debug) |
| contents.dump(os.path.join(tempdir, app)) |
| |
| |
| def _GenerateRequirements(): |
| """Generates a requirements.txt file. |
| |
| Appengine uses the requirements.txt file to build the docker image. |
| |
| Side effect: |
| Creates a requirements.txt file in the same directory as this script. |
| """ |
| path = os.path.join(os.path.dirname(__file__), 'requirements.txt') |
| cros_build_lib.RunCommand( |
| ['pipenv', 'lock', '-r'], |
| log_stdout_to_file=path) |
| |
| |
| def Main(debug=False, password_file=None, app='app.yaml'): |
| """Deploys the app. |
| |
| Args: |
| debug: Whether to deploy as the debug service. |
| password_file: A path to the password file, or None for getpass(). |
| app: the .yaml file to deploy. |
| """ |
| # Debug logging is needed to see output from deploy RunCommand. |
| logging.basicConfig(level=logging.DEBUG) |
| # This can't be generated in the tempdir because pipenv associates directory |
| # paths with virtualenvs. |
| _GenerateRequirements() |
| with _PrepareAppFolder(debug=debug) as tempdir: |
| _FillAppTemplate( |
| tempdir, app=app, debug=debug, password_file=password_file) |
| # Put a pdb trace here if you want to debug the app folder setup. |
| cros_build_lib.RunCommand(['gcloud', 'app', 'deploy', app], cwd=tempdir) |
| |
| |
| if __name__ == '__main__': |
| fire.Fire(Main) |