Fetch & deploy script
Change-Id: Ided5bc790b9ed65899573a2f1c9ede18569ce13a
(cherry picked from commit 00e4b5a3cd7253a32df5c04ad72e13a9d9f53a04)
diff --git a/deploy/deploy.py b/deploy/deploy.py
new file mode 100644
index 0000000..f23d3ca
--- /dev/null
+++ b/deploy/deploy.py
@@ -0,0 +1,143 @@
+"""Fetch and Deploy Cuttlefish images to specified GCE instance.
+"""
+
+import argparse
+import logging
+import os
+import sys
+import tempfile
+
+LOG_FORMAT = "%(levelname)1.1s %(asctime)s %(process)d %(filename)s:%(lineno)d] %(message)s"
+LOG = logging.getLogger()
+SSH_ARGS = ' '.join([
+ '-o', 'StrictHostKeyChecking=no',
+ '-o', 'UserKnownHostsFile=/dev/null',
+ '-q',
+])
+
+def setup_arg_parser():
+ """Set up command line argument parser."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--system_build', type=str, required=False,
+ default='latest',
+ help='Build number to fetch from Android Build server.')
+ parser.add_argument('--system_branch', type=str, required=False,
+ default='git_oc-gce-dev',
+ help='Android Build branch providing system images.')
+ parser.add_argument('--system_target', type=str, required=False,
+ default='cf_x86_phone-userdebug',
+ help='Android Build target providing system images.')
+ parser.add_argument('--kernel_branch', type=str, required=False,
+ default='kernel-n-dev-android-gce-3.18-x86_64',
+ help='Android Build branch providing kernel images.')
+ parser.add_argument('--kernel_target', type=str, required=False,
+ default='kernel',
+ help='Android Build target providing kernel images.')
+ parser.add_argument('-i', '--instance', type=str, required=True,
+ help='IP address of GCE instance.')
+ parser.add_argument('--tmpdir', type=str, required=False,
+ default='/tmp',
+ help='Temporary folder location.')
+ parser.add_argument('--keep', action='store_true',
+ help='Keep downloaded archive file after completion (=do not clean up).')
+ parser.add_argument('--instance_folder', type=str, required=False,
+ default='/srv/cf',
+ help='Folder on the remote machine where images should be deployed.')
+ return parser
+
+
+def setup_logger():
+ """Set up logging mechanism.
+
+ Logging mechanism will print logs out to standard error.
+ """
+ stdout_handler = logging.StreamHandler(sys.stderr)
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format=LOG_FORMAT,
+ handlers=[stdout_handler])
+
+
+def execute_remote(server, command):
+ """Execute supplied command on a remote server."""
+ LOG.info('Executing remote command: %s', command)
+ cmd = os.popen('ssh ${USER}@%s %s -- %s' % (server, SSH_ARGS, command))
+ cmd_out = [line for line in cmd.xreadlines()]
+ if cmd.close():
+ raise Exception('Could not execute: %s:\n\t%s',
+ command,
+ '\n\t'.join(cmd_out))
+
+
+def execute(command):
+ """Execute supplied command. Raise exception if execution failed."""
+ cmd = os.popen(command)
+ LOG.info('Executing: %s', command)
+ cmd_out = [line for line in cmd.xreadlines()]
+ if cmd.close():
+ raise Exception('Could not execute: %s:\n\t%s',
+ command,
+ '\n\t'.join(cmd_out))
+
+
+
+def main():
+ """Deploy Cuttlefish image to GCE."""
+ setup_logger()
+ parser = setup_arg_parser()
+ args = parser.parse_args()
+
+ # Do all work in temp folder.
+ # Allow system to fail here with an exception.
+ os.chdir(args.tmpdir)
+ target_dir = '%s/%s' % (args.instance_folder, args.system_build)
+ temp_image = '%s-%s.img' % (args.system_target, args.system_build)
+
+ try:
+ build_selector = '--latest'
+ if args.system_build != 'latest':
+ build_selector = '--bid=' + args.system_build
+
+ if not os.path.exists(temp_image):
+ execute('/google/data/ro/projects/android/fetch_artifact '
+ '%s --branch=%s --target=%s '
+ '\'cf_x86_phone-img-*\' \'%s\'' %
+ (build_selector, args.system_branch, args.system_target, temp_image))
+
+ execute('/google/data/ro/projects/android/fetch_artifact '
+ '--latest --branch=%s --target=%s '
+ 'bzImage kernel' %
+ (args.kernel_branch, args.kernel_target))
+ execute('unzip -u \'%s\' system.img boot.img' % temp_image)
+
+ execute_remote(args.instance, 'sudo mkdir -p %s' % target_dir)
+
+ # Give user and libvirt access rights to specified folder.
+ # Remote directory appears as 'no access rights' except for included
+ # users.
+ execute_remote(args.instance, 'sudo chmod ugo= %s' % target_dir)
+ execute_remote(args.instance, 'sudo setfacl -m u:${USER}:rwx %s' % target_dir)
+ execute_remote(args.instance, 'sudo setfacl -m u:libvirt-qemu:rwx %s' % target_dir)
+
+ LOG.info('Ensuring user is a member of relevant groups.')
+ # TODO(ender): This technically does not belong in here.
+ execute_remote(args.instance, 'sudo usermod -a -G libvirt ${USER}')
+
+ # Copy files to remote server location.
+ execute('scp %s kernel system.img boot.img ${USER}@%s:%s' %
+ (SSH_ARGS, args.instance, target_dir))
+ execute_remote(args.instance, 'setfacl -m u:${USER}:rw %s/*' % target_dir)
+ execute_remote(args.instance, 'setfacl -m u:libvirt-qemu:rw %s/*' % target_dir)
+
+ except Exception as exception:
+ LOG.exception('Could not complete: %s', exception)
+ finally:
+ if not args.keep:
+ os.unlink(temp_image)
+ os.unlink('boot.img')
+ os.unlink('kernel')
+ os.unlink('system.img')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/launcher/guest_definition.py b/launcher/guest_definition.py
index 078462e..9a4eadd 100644
--- a/launcher/guest_definition.py
+++ b/launcher/guest_definition.py
@@ -242,7 +242,6 @@
elem.set('type', stype)
src = ET.SubElement(elem, 'source')
if stype == 'file':
- src.set('append', 'no')
src.set('path', spath)
elif stype == 'unix':
src.set('mode', 'bind')