Path: blob/develop/awscli/customizations/gamelift/uploadbuild.py
2635 views
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0/7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.12import threading13import contextlib14import os15import tempfile16import sys17import zipfile1819from s3transfer import S3Transfer2021from awscli.customizations.commands import BasicCommand22from awscli.customizations.s3.utils import human_readable_size23from awscli.utils import create_nested_client242526def parse_tags(raw_tags_list):27"""Parse tags from Key=Value format to GameLift API format."""28tags_list = []29if raw_tags_list:30for tag in raw_tags_list:31if '=' in tag:32key, value = tag.split('=', 1)33else:34key, value = tag, ''35tags_list.append({'Key': key, 'Value': value})36return tags_list373839class UploadBuildCommand(BasicCommand):40NAME = 'upload-build'41DESCRIPTION = 'Upload a new build to AWS GameLift.'42ARG_TABLE = [43{'name': 'name', 'required': True,44'help_text': 'The name of the build'},45{'name': 'build-version', 'required': True,46'help_text': 'The version of the build'},47{'name': 'build-root', 'required': True,48'help_text':49'The path to the directory containing the build to upload'},50{'name': 'server-sdk-version', 'required': False,51'help_text':52'The version of the GameLift server SDK used to '53'create the game server'},54{'name': 'operating-system', 'required': False,55'help_text': 'The operating system the build runs on'},56{'name': 'tags', 'required': False, 'nargs': '+',57'help_text': 'Tags to assign to the build. Format: Key=Value'}58]5960def _run_main(self, args, parsed_globals):61gamelift_client = create_nested_client(62self._session, 'gamelift', region_name=parsed_globals.region,63endpoint_url=parsed_globals.endpoint_url,64verify=parsed_globals.verify_ssl65)66# Validate a build directory67if not validate_directory(args.build_root):68sys.stderr.write(69f'Fail to upload {args.build_root}. '70'The build root directory is empty or does not exist.\n'71)7273return 25574# Create a build based on the operating system given.75create_build_kwargs = {76'Name': args.name,77'Version': args.build_version78}79if args.operating_system:80create_build_kwargs['OperatingSystem'] = args.operating_system81if args.server_sdk_version:82create_build_kwargs['ServerSdkVersion'] = args.server_sdk_version83if args.tags:84create_build_kwargs['Tags'] = parse_tags(args.tags)85response = gamelift_client.create_build(**create_build_kwargs)86build_id = response['Build']['BuildId']8788# Retrieve a set of credentials and the s3 bucket and key.89response = gamelift_client.request_upload_credentials(90BuildId=build_id)91upload_credentials = response['UploadCredentials']92bucket = response['StorageLocation']['Bucket']93key = response['StorageLocation']['Key']9495# Create the S3 Client for uploading the build based on the96# credentials returned from creating the build.97access_key = upload_credentials['AccessKeyId']98secret_key = upload_credentials['SecretAccessKey']99session_token = upload_credentials['SessionToken']100s3_client = create_nested_client(101self._session, 's3',102aws_access_key_id=access_key,103aws_secret_access_key=secret_key,104aws_session_token=session_token,105region_name=parsed_globals.region,106verify=parsed_globals.verify_ssl107)108109s3_transfer_mgr = S3Transfer(s3_client)110111try:112fd, temporary_zipfile = tempfile.mkstemp(f'{build_id}.zip')113zip_directory(temporary_zipfile, args.build_root)114s3_transfer_mgr.upload_file(115temporary_zipfile, bucket, key,116callback=ProgressPercentage(117temporary_zipfile,118label='Uploading ' + args.build_root + ':'119)120)121finally:122os.close(fd)123os.remove(temporary_zipfile)124125sys.stdout.write(126f'Successfully uploaded {args.build_root} to AWS GameLift\n'127f'Build ID: {build_id}\n')128129return 0130131132def zip_directory(zipfile_name, source_root):133source_root = os.path.abspath(source_root)134with open(zipfile_name, 'wb') as f:135zip_file = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED, True)136with contextlib.closing(zip_file) as zf:137for root, dirs, files in os.walk(source_root):138for filename in files:139full_path = os.path.join(root, filename)140relative_path = os.path.relpath(141full_path, source_root)142zf.write(full_path, relative_path)143144145def validate_directory(source_root):146# For Python26 on Windows, passing an empty string equates to the147# current directory, which is not intended behavior.148if not source_root:149return False150# We walk the root because we want to validate there's at least one file151# that exists recursively from the root directory152for path, dirs, files in os.walk(source_root):153if files:154return True155return False156157158# TODO: Remove this class once available to CLI from s3transfer159# docstring.160class ProgressPercentage:161def __init__(self, filename, label=None):162self._filename = filename163self._label = label164if self._label is None:165self._label = self._filename166self._size = float(os.path.getsize(filename))167self._seen_so_far = 0168self._lock = threading.Lock()169170def __call__(self, bytes_amount):171with self._lock:172self._seen_so_far += bytes_amount173if self._size > 0:174percentage = (self._seen_so_far / self._size) * 100175sys.stdout.write(176f"\r{self._label} {human_readable_size(self._seen_so_far)} / {human_readable_size(self._size)} ({percentage:.2f}%)"177)178sys.stdout.flush()179180181