blob: f54ae95bee40599ea55960d3216017945492b39e [file] [log] [blame]
#!/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)