def Run(self, args): if args.migrate and len(args.splits) > 1: raise TrafficSplitError( 'The migrate flag can only be used with splits ' 'to a single version.') api_client = appengine_api_client.GetApiClient() all_services = api_client.ListServices() services = service_util.GetMatchingServices(all_services, args.services) allocations = service_util.ParseTrafficAllocations( args.splits, args.split_by) display_allocations = [] for service in services: for version, split in allocations.iteritems(): display_allocations.append('{0}/{1}/{2}: {3}'.format( api_client.project, service.id, version, split)) fmt = 'list[title="Setting the following traffic allocations:"]' resource_printer.Print(display_allocations, fmt, out=log.status) log.status.Print('Any other versions on the specified services will ' 'receive zero traffic.') console_io.PromptContinue(cancel_on_no=True) errors = {} for service in services: try: api_client.SetTrafficSplit(service.id, allocations, args.split_by.upper(), args.migrate) except (calliope_exceptions.HttpException, operations_util.OperationError, operations_util.OperationTimeoutError) as err: errors[service.id] = str(err) if errors: printable_errors = {} for service, error_msg in errors.items(): printable_errors[service] = error_msg raise TrafficSplitError( 'Issue setting traffic on service(s): {0}\n\n'.format( ', '.join(printable_errors.keys())) + '\n\n'.join(printable_errors.values()))
def Run(self, args): api_client = appengine_api_client.GetApiClient() all_instances = api_client.GetAllInstances(args.service, args.version) # Only VM instances can be placed in debug mode for now. all_instances = filter(operator.attrgetter('instance.vmName'), all_instances) instance = instances_util.GetMatchingInstance(all_instances, service=args.service, version=args.version, instance=args.instance) console_io.PromptContinue( 'About to enable debug mode for instance [{0}].'.format(instance), cancel_on_no=True) message = 'Enabling debug mode for instance [{0}]'.format(instance) with console_io.ProgressTracker(message): api_client.DebugInstance(service=instance.service, version=instance.version, instance=instance.id)
def Run(self, args): api_client = appengine_api_client.GetApiClient() all_instances = api_client.GetAllInstances(args.service, args.version) # Only VM instances can be placed in debug mode for now. all_instances = filter(operator.attrgetter('instance.vmName'), all_instances) instance = instances_util.GetMatchingInstance( all_instances, service=args.service, version=args.version, instance=args.instance) console_io.PromptContinue( 'About to disable debug mode for instance [{0}].\n\n' 'Any local changes will be LOST. New instance(s) may spawn depending ' 'on the app\'s scaling settings.'.format(instance), cancel_on_no=True) message = 'Disabling debug mode for instance [{0}]'.format(instance) with progress_tracker.ProgressTracker(message): api_client.DeleteInstance(service=instance.service, version=instance.version, instance=instance.id)
def Run(self, args): api_client = appengine_api_client.GetApiClient() message = 'You are about to delete the following module versions:\n\t' message += '\n\t'.join([ '{0}/{1}/{2}'.format(api_client.project, m, args.version) for m in args.modules ]) console_io.PromptContinue(message=message, cancel_on_no=True) # Will delete each specified version. # In event of a failure, will still attempt to delete the remaining modules. # Prints out a warning or error as appropriate for each module deletion. delete_results = [] for module in args.modules: delete_results.append( api_client.DeleteVersion(module, args.version)) if not all(delete_results): raise exceptions.ToolException('Not all deletions succeeded.')
def Run(self, args): api_client = appengine_api_client.GetApiClient() all_instances = list( api_client.GetAllInstances(args.service, args.version, version_filter=lambda v: util. Environment.IsFlexible(v.environment))) try: res = resources.REGISTRY.Parse(args.instance) except Exception: # pylint:disable=broad-except # Either the commandline argument is an instance ID, or is empty. # If empty, use interactive selection to choose an instance. instance = instances_util.GetMatchingInstance( all_instances, service=args.service, version=args.version, instance=args.instance) else: instance = instances_util.GetMatchingInstance( all_instances, service=res.servicesId, version=res.versionsId, instance=res.instancesId) console_io.PromptContinue( 'About to disable debug mode for instance [{0}].\n\n' 'Any local changes will be LOST. New instance(s) may spawn depending ' 'on the app\'s scaling settings.'.format(instance), cancel_on_no=True) message = 'Disabling debug mode for instance [{0}]'.format(instance) res = resources.REGISTRY.Parse( instance.id, params={ 'appsId': properties.VALUES.core.project.GetOrFail, 'servicesId': instance.service, 'versionsId': instance.version, }, collection='appengine.apps.services.versions.instances') with progress_tracker.ProgressTracker(message): api_client.DeleteInstance(res)
def Run(self, args): # TODO(user): This fails with "module/version does not exist" even # when it exists if the scaling mode is set to auto. It would be good # to improve that error message. api_client = appengine_api_client.GetApiClient() services = api_client.ListServices() versions = version_util.GetMatchingVersions( api_client.ListVersions(services), args.versions, args.service) if not versions: log.warn('No matching versions found.') return fmt = 'list[title="Starting the following versions:"]' resource_printer.Print(versions, fmt, out=log.status) console_io.PromptContinue(cancel_on_no=True) errors = {} # Sort versions to make behavior deterministic enough for unit testing. for version in sorted(versions): try: with progress_tracker.ProgressTracker( 'Starting [{0}]'.format(version)): api_client.StartVersion(version.service, version.id) except (calliope_exceptions.HttpException, operations.OperationError, operations.OperationTimeoutError) as err: errors[version] = str(err) if errors: printable_errors = {} for version, error_msg in errors.items(): short_name = '[{0}/{1}]'.format(version.service, version.id) printable_errors[short_name] = '{0}: {1}'.format( short_name, error_msg) raise VersionsStartError( 'Issues starting version(s): {0}\n\n'.format(', '.join( printable_errors.keys())) + '\n\n'.join(printable_errors.values()))
def Run(self, args): api_client = appengine_api_client.GetApiClient() all_instances = api_client.GetAllInstances( args.service, args.version, version_filter=lambda v: util.Environment.IsFlexible(v.environment )) try: res = resources.REGISTRY.Parse(args.instance) except Exception: # pylint:disable=broad-except # If parsing fails, use interactive selection or provided instance ID. instance = instances_util.GetMatchingInstance( all_instances, service=args.service, version=args.version, instance=args.instance) else: instance = instances_util.GetMatchingInstance( all_instances, service=res.servicesId, version=res.versionsId, instance=res.instancesId) console_io.PromptContinue( 'About to enable debug mode for instance [{0}].'.format(instance), cancel_on_no=True) message = 'Enabling debug mode for instance [{0}]'.format(instance) res = resources.REGISTRY.Parse( instance.id, params={ 'appsId': properties.VALUES.core.project.GetOrFail, 'versionsId': instance.version, 'instancesId': instance.id, 'servicesId': instance.service, }, collection='appengine.apps.services.versions.instances') with progress_tracker.ProgressTracker(message): api_client.DebugInstance(res)
def Run(self, args): # TODO(user): This fails with "module/version does not exist" even # when it exists if the scaling mode is set to auto. It would be good # to improve that error message. api_client = appengine_api_client.GetApiClient() services = api_client.ListServices() versions = version_util.GetMatchingVersions( api_client.ListVersions(services), args.versions, args.service) if not versions: log.warn('No matching versions found.') return printer = console_io.ListPrinter('Starting the following versions:') printer.Print(versions, output_stream=log.status) console_io.PromptContinue(cancel_on_no=True) client = appengine_client.AppengineClient() errors = {} for version in versions: try: with console_io.ProgressTracker( 'Starting [{0}]'.format(version)): client.StartModule(module=version.service, version=version.id) except util.RPCError as err: errors[version] = str(err) if errors: printable_errors = {} for version, error_msg in errors.items(): short_name = '[{0}/{1}]'.format(version.service, version.id) printable_errors[short_name] = '{0}: {1}'.format( short_name, error_msg) raise VersionsStartError( 'Issues starting version(s): {0}\n\n'.format(', '.join( printable_errors.keys())) + '\n\n'.join(printable_errors.values()))
def Run(self, args): api_client = appengine_api_client.GetApiClient() services = api_client.ListServices() service_ids = [s.id for s in services] log.debug('All services: {0}'.format(service_ids)) if args.service and args.service not in service_ids: raise ServiceNotFoundError('Service [{0}] not found.'.format( args.service)) # Filter the services list to make fewer ListVersions calls. if args.service: services = [s for s in services if s.id == args.service] versions = api_client.ListVersions(services) # Filter for service. if args.service: versions = [v for v in versions if v.service == args.service] # Filter for traffic. if args.hide_no_traffic: versions = [v for v in versions if v.traffic_split] return sorted(versions)
def Run(self, args): api_client = appengine_api_client.GetApiClient() return api_client.GetVersionResource(service=args.service, version=args.version)
def Run(self, args): api_client = appengine_api_client.GetApiClient() return api_client.GetInstanceResource(service=args.service, version=args.version, instance=args.instance)
def Run(self, args): client = appengine_api_client.GetApiClient() if args.service: service = client.GetServiceResource(args.service) traffic_split = {} if service.split: for split in service.split.allocations.additionalProperties: traffic_split[split.key] = split.value services = [ service_util.Service(client.project, service.id, traffic_split) ] else: services = client.ListServices() all_versions = client.ListVersions(services) if args.version not in {v.id for v in all_versions}: if args.service: raise VersionsMigrateError( 'Version [{0}/{1}] does not exist.'.format( args.service, args.version)) else: raise VersionsMigrateError( 'Version [{0}] does not exist.'.format(args.version)) service_names = { v.service for v in all_versions if v.id == args.version } def WillBeMigrated(v): return (v.service in service_names and v.traffic_split and v.traffic_split > 0 and v.id != args.version) # All versions that will stop receiving traffic. versions_to_migrate = filter(WillBeMigrated, all_versions) for version in versions_to_migrate: short_name = '{0}/{1}'.format(version.service, version.id) promoted_name = '{0}/{1}'.format(version.service, args.version) log.status.Print('Migrating all traffic from version ' '[{0}] to [{1}]'.format(short_name, promoted_name)) console_io.PromptContinue(cancel_on_no=True) errors = {} for service in sorted(set([v.service for v in versions_to_migrate])): allocations = {args.version: 1.0} try: client.SetTrafficSplit(service, allocations, shard_by='ip', migrate=True) except (api_exceptions.HttpException, operations_util.OperationError, operations_util.OperationTimeoutError, util.RPCError) as e: errors[service] = str(e) if errors: error_string = ('Issues migrating all traffic of ' 'service(s): [{0}]\n\n{1}'.format( ', '.join(errors.keys()), '\n\n'.join(errors.values()))) raise VersionsMigrateError(error_string)
def RunDeploy(unused_self, args, enable_endpoints=False, app_create=False): """Perform a deployment based on the given args.""" version_id = args.version or util.GenerateVersionId() flags.ValidateVersion(version_id) project = properties.VALUES.core.project.Get(required=True) deploy_options = DeployOptions.FromProperties(enable_endpoints, app_create) # Parse existing app.yamls or try to generate a new one if the directory is # empty. if not args.deployables: yaml_path = deploy_command_util.DEFAULT_DEPLOYABLE if not os.path.exists(deploy_command_util.DEFAULT_DEPLOYABLE): log.warning('Automatic app detection is currently in Beta') yaml_path = deploy_command_util.CreateAppYamlForAppDirectory( os.getcwd()) app_config = yaml_parsing.AppConfigSet([yaml_path]) else: app_config = yaml_parsing.AppConfigSet(args.deployables) services = app_config.Services() if not args.skip_image_url_validation: flags.ValidateImageUrl(args.image_url, services) # The new API client. api_client = appengine_api_client.GetApiClient() # pylint: disable=protected-access log.debug('API endpoint: [{endpoint}], API version: [{version}]'.format( endpoint=api_client.client.url, version=api_client.client._VERSION)) # The legacy admin console API client. # The Admin Console API existed long before the App Engine Admin API, and # isn't being improved. We're in the process of migrating all of the calls # over to the Admin API, but a few things (notably config deployments) haven't # been ported over yet. ac_client = appengine_client.AppengineClient(args.server, args.ignore_bad_certs) app = _PossiblyCreateApp(api_client, project, deploy_options.app_create) # Tell the user what is going to happen, and ask them to confirm. deployed_urls = output_helpers.DisplayProposedDeployment( app, project, app_config, version_id, deploy_options.promote) console_io.PromptContinue(cancel_on_no=True) if services: # Do generic app setup if deploying any services. # All deployment paths for a service involve uploading source to GCS. code_bucket_ref = args.bucket or flags.GetCodeBucket(app, project) metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET) log.debug( 'Using bucket [{b}].'.format(b=code_bucket_ref.ToBucketUrl())) # Prepare Flex if any service is going to deploy an image. if any([m.RequiresImage() for m in services.values()]): deploy_command_util.DoPrepareManagedVms(ac_client) all_services = dict([(s.id, s) for s in api_client.ListServices()]) else: code_bucket_ref = None all_services = {} new_versions = [] stager = staging.GetNoopStager( ) if args.skip_staging else staging.GetStager() deployer = ServiceDeployer(api_client, stager, deploy_options) for (name, service) in services.iteritems(): new_version = version_util.Version(project, name, version_id) deployer.Deploy(service, new_version, code_bucket_ref, args.image_url, all_services) new_versions.append(new_version) log.status.Print('Deployed service [{0}] to [{1}]'.format( name, deployed_urls[name])) # Deploy config files. for (name, config) in app_config.Configs().iteritems(): message = 'Updating config [{config}]'.format(config=name) with progress_tracker.ProgressTracker(message): ac_client.UpdateConfig(name, config.parsed) updated_configs = app_config.Configs().keys() PrintPostDeployHints(new_versions, updated_configs) # Return all the things that were deployed. return {'versions': new_versions, 'configs': updated_configs}
def Run(self, args): """Connect to a running flex instance. Args: args: argparse.Namespace, the args the command was invoked with. Raises: InvalidInstanceTypeError: The instance is not supported for SSH. MissingVersionError: The version specified does not exist. MissingInstanceError: The instance specified does not exist. UnattendedPromptError: Not running in a tty. OperationCancelledError: User cancelled the operation. ssh.CommandError: The SSH command exited with SSH exit code, which usually implies that a connection problem occurred. Returns: int, The exit code of the SSH command. """ api_client = appengine_api_client.GetApiClient() env = ssh.Environment.Current() env.RequireSSH() keys = ssh.Keys.FromFilename() keys.EnsureKeysExist(overwrite=False) try: version = api_client.GetVersionResource(service=args.service, version=args.version) except api_exceptions.NotFoundError: raise command_exceptions.MissingVersionError('{}/{}'.format( args.service, args.version)) version = version_util.Version.FromVersionResource(version, None) if version.environment is not util.Environment.FLEX: if version.environment is util.Environment.MANAGED_VMS: environment = 'Managed VMs' msg = 'Use `gcloud compute ssh` for Managed VMs instances.' else: environment = 'Standard' msg = None raise command_exceptions.InvalidInstanceTypeError(environment, msg) res = resources.REGISTRY.Parse( args.instance, params={ 'appsId': properties.VALUES.core.project.GetOrFail, 'versionsId': args.version, 'instancesId': args.instance, 'servicesId': args.service, }, collection='appengine.apps.services.versions.instances') rel_name = res.RelativeName() try: instance = api_client.GetInstanceResource(res) except api_exceptions.NotFoundError: raise command_exceptions.MissingInstanceError(rel_name) if not instance.vmDebugEnabled: log.warn(ENABLE_DEBUG_WARNING) console_io.PromptContinue(cancel_on_no=True, throw_if_unattended=True) user = ssh.GetDefaultSshUsername() remote = ssh.Remote(instance.vmIp, user=user) public_key = keys.GetPublicKey().ToEntry() ssh_key = '{user}:{key} {user}'.format(user=user, key=public_key) log.status.Print( 'Sending public key to instance [{}].'.format(rel_name)) api_client.DebugInstance(res, ssh_key) options = { 'IdentitiesOnly': 'yes', # No ssh-agent as of yet 'UserKnownHostsFile': ssh.KnownHosts.DEFAULT_PATH, 'CheckHostIP': 'no', 'HostKeyAlias': HOST_KEY_ALIAS.format(project=api_client.project, instance_id=args.instance) } cmd = ssh.SSHCommand(remote, identity_file=keys.key_file, options=options) if args.container: cmd.tty = True cmd.remote_command = ['container_exec', args.container, '/bin/sh'] return cmd.Run(env)
def Run(self, args): api_client = appengine_api_client.GetApiClient() with progress_tracker.ProgressTracker( 'Repairing the app [{0}]'.format(api_client.project)): api_client.RepairApplication()
def RunDeploy( args, enable_endpoints=False, use_beta_stager=False, runtime_builder_strategy=runtime_builders.RuntimeBuilderStrategy.NEVER, use_service_management=False, check_for_stopped=False): """Perform a deployment based on the given args. Args: args: argparse.Namespace, An object that contains the values for the arguments specified in the ArgsDeploy() function. enable_endpoints: Enable Cloud Endpoints for the deployed app. use_beta_stager: Use the stager registry defined for the beta track rather than the default stager registry. runtime_builder_strategy: runtime_builders.RuntimeBuilderStrategy, when to use the new CloudBuild-based runtime builders (alternative is old externalized runtimes). use_service_management: bool, whether to use servicemanagement API to enable the Appengine Flexible API for a Flexible deployment. check_for_stopped: bool, whether to check if the app is stopped before deploying. Returns: A dict on the form `{'versions': new_versions, 'configs': updated_configs}` where new_versions is a list of version_util.Version, and updated_configs is a list of config file identifiers, see yaml_parsing.ConfigYamlInfo. """ project = properties.VALUES.core.project.Get(required=True) deploy_options = DeployOptions.FromProperties( enable_endpoints, runtime_builder_strategy=runtime_builder_strategy) with files.TemporaryDirectory() as staging_area: if args.skip_staging: stager = staging.GetNoopStager(staging_area) elif use_beta_stager: stager = staging.GetBetaStager(staging_area) else: stager = staging.GetStager(staging_area) services, configs = deployables.GetDeployables( args.deployables, stager, deployables.GetPathMatchers()) service_infos = [d.service_info for d in services] if not args.skip_image_url_validation: flags.ValidateImageUrl(args.image_url, service_infos) # The new API client. api_client = appengine_api_client.GetApiClient() # pylint: disable=protected-access log.debug( 'API endpoint: [{endpoint}], API version: [{version}]'.format( endpoint=api_client.client.url, version=api_client.client._VERSION)) # The legacy admin console API client. # The Admin Console API existed long before the App Engine Admin API, and # isn't being improved. We're in the process of migrating all of the calls # over to the Admin API, but a few things (notably config deployments) # haven't been ported over yet. ac_client = appengine_client.AppengineClient(args.server, args.ignore_bad_certs) app = _PossiblyCreateApp(api_client, project) if check_for_stopped: _RaiseIfStopped(api_client, app) app = _PossiblyRepairApp(api_client, app) # Tell the user what is going to happen, and ask them to confirm. version_id = args.version or util.GenerateVersionId() deployed_urls = output_helpers.DisplayProposedDeployment( app, project, services, configs, version_id, deploy_options.promote) console_io.PromptContinue(cancel_on_no=True) if service_infos: # Do generic app setup if deploying any services. # All deployment paths for a service involve uploading source to GCS. metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET_START) code_bucket_ref = args.bucket or flags.GetCodeBucket(app, project) metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET) log.debug( 'Using bucket [{b}].'.format(b=code_bucket_ref.ToBucketUrl())) # Prepare Flex if any service is going to deploy an image. if any([s.RequiresImage() for s in service_infos]): if use_service_management: deploy_command_util.PossiblyEnableFlex(project) else: deploy_command_util.DoPrepareManagedVms(ac_client) all_services = dict([(s.id, s) for s in api_client.ListServices()]) else: code_bucket_ref = None all_services = {} new_versions = [] deployer = ServiceDeployer(api_client, deploy_options) # Track whether a service has been deployed yet, for metrics. service_deployed = False for service in services: if not service_deployed: metrics.CustomTimedEvent( metric_names.FIRST_SERVICE_DEPLOY_START) new_version = version_util.Version(project, service.service_id, version_id) deployer.Deploy(service, new_version, code_bucket_ref, args.image_url, all_services, app.gcrDomain) new_versions.append(new_version) log.status.Print('Deployed service [{0}] to [{1}]'.format( service.service_id, deployed_urls[service.service_id])) if not service_deployed: metrics.CustomTimedEvent(metric_names.FIRST_SERVICE_DEPLOY) service_deployed = True # Deploy config files. if configs: metrics.CustomTimedEvent(metric_names.UPDATE_CONFIG_START) for config in configs: message = 'Updating config [{config}]'.format(config=config.name) with progress_tracker.ProgressTracker(message): ac_client.UpdateConfig(config.name, config.parsed) metrics.CustomTimedEvent(metric_names.UPDATE_CONFIG) updated_configs = [c.name for c in configs] PrintPostDeployHints(new_versions, updated_configs) # Return all the things that were deployed. return {'versions': new_versions, 'configs': updated_configs}
def GetAppHostname(app_id, service=None, version=None, use_ssl=appinfo.SECURE_HTTP): """Returns the hostname of the given version of the deployed app. Args: app_id: str, project ID. service: str, the (optional) service being deployed version: str, the deployed version ID (omit to get the default version URL). use_ssl: bool, whether to construct an HTTPS URL. Returns: str. Constructed URL. Raises: googlecloudsdk.core.exceptions.Error: if an invalid app_id is supplied. """ if not app_id: msg = 'Must provide a valid app ID to construct a hostname.' raise exceptions.Error(msg) version = version or '' service = service or '' if service == DEFAULT_SERVICE: service = '' domain = DEFAULT_DOMAIN if ':' in app_id: api_client = appengine_api_client.GetApiClient() app = api_client.GetApplication() app_id, domain = app.defaultHostname.split('.', 1) if service == DEFAULT_SERVICE: service = '' # Normally, AppEngine URLs are of the form # 'http[s]://version.service.app.appspot.com'. However, the SSL certificate # for appspot.com is not valid for subdomains of subdomains of appspot.com # (e.g. 'https://app.appspot.com/' is okay; 'https://service.app.appspot.com/' # is not). To deal with this, AppEngine recognizes URLs like # 'http[s]://version-dot-service-dot-app.appspot.com/'. # # This works well as long as the domain name part constructed in this fashion # is less than 63 characters long, as per the DNS spec. If the domain name # part is longer than that, we are forced to use the URL with an invalid # certificate. # # We've tried to do the best possible thing in every case here. subdomain_parts = filter(bool, [version, service, app_id]) scheme = 'http' if use_ssl == appinfo.SECURE_HTTP: subdomain = '.'.join(subdomain_parts) scheme = 'http' else: subdomain = ALT_SEPARATOR.join(subdomain_parts) if len(subdomain) <= MAX_DNS_LABEL_LENGTH: scheme = 'https' else: subdomain = '.'.join(subdomain_parts) if use_ssl == appinfo.SECURE_HTTP_OR_HTTPS: scheme = 'http' elif use_ssl == appinfo.SECURE_HTTPS: msg = ('Most browsers will reject the SSL certificate for service {0}. ' 'Please verify that the certificate corresponds to the parent ' 'domain of your application when you connect.').format(service) log.warn(msg) scheme = 'https' return '{0}://{1}.{2}'.format(scheme, subdomain, domain)
def Run(self, args): if args.env_vars: log.warn( 'The env-vars flag is deprecated, and will soon be removed.') # Do this up-front to print applicable warnings early promote = deploy_command_util.GetPromoteFromArgs(args) project = properties.VALUES.core.project.Get(required=True) version = args.version or util.GenerateVersionId() use_cloud_build = properties.VALUES.app.use_cloud_build.GetBool() app_config = yaml_parsing.AppConfigSet(args.deployables) remote_build = True docker_build_property = properties.VALUES.app.docker_build.Get() if args.docker_build: remote_build = args.docker_build == 'remote' elif docker_build_property: remote_build = docker_build_property == 'remote' gae_client = appengine_client.AppengineClient(args.server) api_client = appengine_api_client.GetApiClient(self.Http(timeout=None)) log.debug( 'API endpoint: [{endpoint}], API version: [{version}]'.format( endpoint=api_client.client.url, version=api_client.api_version)) cloudbuild_client = cloudbuild_v1.CloudbuildV1(http=self.Http(), get_credentials=False) deployed_urls = _DisplayProposedDeployment(project, app_config, version, promote) if args.version or promote: # Prompt if there's a chance that you're overwriting something important: # If the version is set manually, you could be deploying over something. # If you're setting the new deployment to be the default version, you're # changing the target of the default URL. # Otherwise, all existing URLs will continue to work, so need to prompt. console_io.PromptContinue(default=True, throw_if_unattended=False, cancel_on_no=True) log.status.Print('Beginning deployment...') code_bucket = None if use_cloud_build: # If using Argo CloudBuild, we'll need to upload source to a GCS bucket. code_bucket = self._GetCodeBucket(api_client, args) modules = app_config.Modules() if args.image_url: if len(modules) != 1: raise exceptions.ToolException( 'No more than one module may be deployed when using the ' 'image-url flag') for registry in constants.ALL_SUPPORTED_REGISTRIES: if args.image_url.startswith(registry): break else: raise exceptions.ToolException( '%s is not in a supported registry. Supported registries are %s' % (args.image_url, constants.ALL_SUPPORTED_REGISTRIES)) module = modules.keys()[0] images = {module: args.image_url} else: images = deploy_command_util.BuildAndPushDockerImages( modules, version, gae_client, cloudbuild_client, code_bucket, self.cli, remote_build) deployment_manifests = {} if app_config.NonHermeticModules() and self.use_admin_api: # TODO(clouser): Consider doing this in parallel with # BuildAndPushDockerImage. code_bucket = self._GetCodeBucket(api_client, args) metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET) log.debug('Using bucket [{b}].'.format(b=code_bucket)) if not code_bucket: raise exceptions.ToolException( ('Could not retrieve the default Google ' 'Cloud Storage bucket for [{a}]. ' 'Please try again or use the [bucket] ' 'argument.').format(a=project)) deployment_manifests = deploy_app_command_util.CopyFilesToCodeBucket( app_config.NonHermeticModules().items(), code_bucket) metrics.CustomTimedEvent(metric_names.COPY_APP_FILES) # Now do deployment. for (module, info) in app_config.Modules().iteritems(): message = 'Updating module [{module}]'.format(module=module) with console_io.ProgressTracker(message): if args.force: gae_client.CancelDeployment(module=module, version=version) metrics.CustomTimedEvent(metric_names.CANCEL_DEPLOYMENT) if info.is_hermetic or self.use_admin_api: api_client.DeployModule(module, version, info, deployment_manifests.get(module), images.get(module)) metrics.CustomTimedEvent(metric_names.DEPLOY_API) else: gae_client.DeployModule(module, version, info.parsed, info.file) metrics.CustomTimedEvent(metric_names.DEPLOY_ADMIN_CONSOLE) if promote: if info.is_hermetic or self.use_admin_api: api_client.SetDefaultVersion(module, version) metrics.CustomTimedEvent( metric_names.SET_DEFAULT_VERSION_API) else: gae_client.SetDefaultVersion(modules=[module], version=version) metrics.CustomTimedEvent( metric_names.SET_DEFAULT_VERSION_ADMIN_CONSOLE) # Config files. for (c, info) in app_config.Configs().iteritems(): message = 'Updating config [{config}]'.format(config=c) with console_io.ProgressTracker(message): gae_client.UpdateConfig(c, info.parsed) return deployed_urls
def Run(self, args): api_client = appengine_api_client.GetApiClient() if args.pending: return api_client.ListOperations(op_filter='done:false') else: return api_client.ListOperations()
def Run(self, args): api_client = appengine_api_client.GetApiClient() return api_client.GetAllInstances(args.service, args.version)
def RunDeploy(args, enable_endpoints=False, app_create=False, use_beta_stager=False): """Perform a deployment based on the given args. Args: args: argparse.Namespace, An object that contains the values for the arguments specified in the ArgsDeploy() function. enable_endpoints: Enable Cloud Endpoints for the deployed app. app_create: Offer to create an app if current GCP project is appless. use_beta_stager: Use the stager registry defined for the beta track rather than the default stager registry. Returns: A dict on the form `{'versions': new_versions, 'configs': updated_configs}` where new_versions is a list of version_util.Version, and updated_configs is a list of config file identifiers, see yaml_parsing.ConfigYamlInfo. """ version_id = args.version or util.GenerateVersionId() flags.ValidateVersion(version_id) project = properties.VALUES.core.project.Get(required=True) deploy_options = DeployOptions.FromProperties(enable_endpoints, app_create) # Parse existing app.yamls or try to generate a new one if the directory is # empty. if not args.deployables: yaml_path = deploy_command_util.DEFAULT_DEPLOYABLE if not os.path.exists(deploy_command_util.DEFAULT_DEPLOYABLE): log.warning('Automatic app detection is currently in Beta') yaml_path = deploy_command_util.CreateAppYamlForAppDirectory( os.getcwd()) app_config = yaml_parsing.AppConfigSet([yaml_path]) else: app_config = yaml_parsing.AppConfigSet(args.deployables) # If applicable, sort services by order they were passed to the command. services = app_config.Services() if not args.skip_image_url_validation: flags.ValidateImageUrl(args.image_url, services) # The new API client. api_client = appengine_api_client.GetApiClient() # pylint: disable=protected-access log.debug('API endpoint: [{endpoint}], API version: [{version}]'.format( endpoint=api_client.client.url, version=api_client.client._VERSION)) # The legacy admin console API client. # The Admin Console API existed long before the App Engine Admin API, and # isn't being improved. We're in the process of migrating all of the calls # over to the Admin API, but a few things (notably config deployments) haven't # been ported over yet. ac_client = appengine_client.AppengineClient(args.server, args.ignore_bad_certs) app = _PossiblyCreateApp(api_client, project, deploy_options.app_create) if properties.VALUES.app.use_gsutil.GetBool(): log.warning( 'Your gcloud installation has a deprecated config property ' 'enabled: [app/use_gsutil], which will be removed in a ' 'future version. Run `gcloud config unset app/use_gsutil` to ' 'switch to the recommended approach. If you encounter any ' 'issues, please report using `gcloud feedback`. To revert ' 'temporarily, run `gcloud config set app/use_gsutil True`.\n') # Tell the user what is going to happen, and ask them to confirm. deployed_urls = output_helpers.DisplayProposedDeployment( app, project, app_config, version_id, deploy_options.promote) console_io.PromptContinue(cancel_on_no=True) if services: # Do generic app setup if deploying any services. # All deployment paths for a service involve uploading source to GCS. code_bucket_ref = args.bucket or flags.GetCodeBucket(app, project) metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET) log.debug( 'Using bucket [{b}].'.format(b=code_bucket_ref.ToBucketUrl())) # Prepare Flex if any service is going to deploy an image. if any([m.RequiresImage() for m in services.values()]): deploy_command_util.DoPrepareManagedVms(ac_client) all_services = dict([(s.id, s) for s in api_client.ListServices()]) else: code_bucket_ref = None all_services = {} new_versions = [] if args.skip_staging: stager = staging.GetNoopStager() elif use_beta_stager: stager = staging.GetBetaStager() else: stager = staging.GetStager() deployer = ServiceDeployer(api_client, stager, deploy_options) for name, service in services.iteritems(): new_version = version_util.Version(project, name, version_id) deployer.Deploy(service, new_version, code_bucket_ref, args.image_url, all_services) new_versions.append(new_version) log.status.Print('Deployed service [{0}] to [{1}]'.format( name, deployed_urls[name])) # Deploy config files. for (name, config) in app_config.Configs().iteritems(): message = 'Updating config [{config}]'.format(config=name) with progress_tracker.ProgressTracker(message): ac_client.UpdateConfig(name, config.parsed) updated_configs = app_config.Configs().keys() PrintPostDeployHints(new_versions, updated_configs) # Return all the things that were deployed. return {'versions': new_versions, 'configs': updated_configs}
def GetAppHostname(app=None, app_id=None, service=None, version=None, use_ssl=appinfo.SECURE_HTTP, deploy=True): """Returns the hostname of the given version of the deployed app. Args: app: Application resource. One of {app, app_id} must be given. app_id: str, project ID. One of {app, app_id} must be given. If both are provided, the hostname from app is preferred. service: str, the (optional) service being deployed version: str, the deployed version ID (omit to get the default version URL). use_ssl: bool, whether to construct an HTTPS URL. deploy: bool, if this is called during a deployment. Returns: str. Constructed URL. Raises: TypeError: if neither an app nor an app_id is provided """ if not app and not app_id: raise TypeError( 'Must provide an application resource or application ID.') version = version or '' service_name = service or '' if service == DEFAULT_SERVICE: service_name = '' domain = DEFAULT_DOMAIN if not app and ':' in app_id: api_client = appengine_api_client.GetApiClient() app = api_client.GetApplication() if app: app_id, domain = app.defaultHostname.split('.', 1) # Normally, AppEngine URLs are of the form # 'http[s]://version.service.app.appspot.com'. However, the SSL certificate # for appspot.com is not valid for subdomains of subdomains of appspot.com # (e.g. 'https://app.appspot.com/' is okay; 'https://service.app.appspot.com/' # is not). To deal with this, AppEngine recognizes URLs like # 'http[s]://version-dot-service-dot-app.appspot.com/'. # # This works well as long as the domain name part constructed in this fashion # is less than 63 characters long, as per the DNS spec. If the domain name # part is longer than that, we are forced to use the URL with an invalid # certificate. # # We've tried to do the best possible thing in every case here. subdomain_parts = filter(bool, [version, service_name, app_id]) scheme = 'http' if use_ssl == appinfo.SECURE_HTTP: subdomain = '.'.join(subdomain_parts) scheme = 'http' else: subdomain = ALT_SEPARATOR.join(subdomain_parts) if len(subdomain) <= MAX_DNS_LABEL_LENGTH: scheme = 'https' else: if deploy: format_parts = ['$VERSION_ID', '$SERVICE_ID', '$APP_ID'] subdomain_format = ALT_SEPARATOR.join([ j for (i, j) in zip([version, service_name, app_id], format_parts) if i ]) msg = ( 'This deployment will result in an invalid SSL certificate for ' 'service [{0}]. The total length of your subdomain in the ' 'format {1} should not exceed {2} characters. Please verify ' 'that the certificate corresponds to the parent domain of your ' 'application when you connect.').format( service, subdomain_format, MAX_DNS_LABEL_LENGTH) log.warn(msg) subdomain = '.'.join(subdomain_parts) if use_ssl == appinfo.SECURE_HTTP_OR_HTTPS: scheme = 'http' elif use_ssl == appinfo.SECURE_HTTPS: if not deploy: msg = ('Most browsers will reject the SSL certificate for ' 'service [{0}].').format(service) log.warn(msg) scheme = 'https' return '{0}://{1}.{2}'.format(scheme, subdomain, domain)
def Run(self, args): api_client = appengine_api_client.GetApiClient() return api_client.GetOperation(args.operation)
def Run(self, args): api_client = appengine_api_client.GetApiClient() return api_client.GetServiceResource(args.service)
def Run(self, args): api_client = appengine_api_client.GetApiClient() return sorted(api_client.ListRegions())
def Run(self, args): project = properties.VALUES.core.project.Get(required=True) version = args.version or util.GenerateVersionId() use_cloud_build = properties.VALUES.app.use_cloud_build.GetBool() config_cleanup = None if args.deployables: app_config = yaml_parsing.AppConfigSet(args.deployables) else: if not os.path.exists(DEFAULT_DEPLOYABLE): console_io.PromptContinue( 'Deployment to Google App Engine requires an app.yaml file. ' 'This command will run `gcloud preview app gen-config` to generate ' 'an app.yaml file for you in the current directory (if the current ' 'directory does not contain an App Engine module, please answer ' '"no").', cancel_on_no=True) # This generates the app.yaml AND the Dockerfile (and related files). params = ext_runtime.Params(deploy=True) configurator = fingerprinter.IdentifyDirectory(os.getcwd(), params=params) if configurator is None: raise NoAppIdentifiedError( 'Could not identify an app in the current directory.\n\n' 'Please prepare an app.yaml file for your application manually ' 'and deploy again.') config_cleanup = configurator.GenerateConfigs() log.status.Print( '\nCreated [{0}] in the current directory.\n'.format( DEFAULT_DEPLOYABLE)) app_config = yaml_parsing.AppConfigSet([DEFAULT_DEPLOYABLE]) # If the app has enabled Endpoints API Management features, pass # control to the cloud_endpoints handler. for _, module in app_config.Modules().items(): if module and module.parsed and module.parsed.beta_settings: bs = module.parsed.beta_settings use_endpoints = bs.get('use_endpoints_api_management', '').lower() if (use_endpoints in ('true', '1', 'yes') and bs.get('endpoints_swagger_spec_file')): cloud_endpoints.PushServiceConfig( bs.get('endpoints_swagger_spec_file'), project, apis.GetClientInstance('servicemanagement', 'v1', self.Http()), apis.GetMessagesModule('servicemanagement', 'v1')) remote_build = True docker_build_property = properties.VALUES.app.docker_build.Get() if args.docker_build: remote_build = args.docker_build == 'remote' elif docker_build_property: remote_build = docker_build_property == 'remote' clients = _AppEngineClients( appengine_client.AppengineClient(args.server, args.ignore_bad_certs), appengine_api_client.GetApiClient(self.Http(timeout=None))) log.debug( 'API endpoint: [{endpoint}], API version: [{version}]'.format( endpoint=clients.api.client.url, version=clients.api.api_version)) cloudbuild_client = apis.GetClientInstance('cloudbuild', 'v1', self.Http()) storage_client = apis.GetClientInstance('storage', 'v1', self.Http()) promote = properties.VALUES.app.promote_by_default.GetBool() deployed_urls = _DisplayProposedDeployment(project, app_config, version, promote) if args.version or promote: # Prompt if there's a chance that you're overwriting something important: # If the version is set manually, you could be deploying over something. # If you're setting the new deployment to be the default version, you're # changing the target of the default URL. # Otherwise, all existing URLs will continue to work, so need to prompt. console_io.PromptContinue(default=True, throw_if_unattended=False, cancel_on_no=True) log.status.Print('Beginning deployment...') source_contexts = [] if args.repo_info_file: if args.image_url: raise NoRepoInfoWithImageUrlError() try: with open(args.repo_info_file, 'r') as f: source_contexts = json.load(f) except (ValueError, IOError) as ex: raise RepoInfoLoadError(args.repo_info_file, ex) if isinstance(source_contexts, dict): # This is an old-style source-context.json file. Convert to a new- # style array of extended contexts. source_contexts = [ context_util.ExtendContextDict(source_contexts) ] code_bucket_ref = None if use_cloud_build or app_config.NonHermeticModules(): # If using Argo CloudBuild, we'll need to upload source to a GCS bucket. code_bucket_ref = self._GetCodeBucket(clients.api, args) metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET) log.debug('Using bucket [{b}].'.format(b=code_bucket_ref)) modules = app_config.Modules() if any([m.RequiresImage() for m in modules.values()]): deploy_command_util.DoPrepareManagedVms(clients.gae) if args.image_url: if len(modules) != 1: raise MultiDeployError() for registry in constants.ALL_SUPPORTED_REGISTRIES: if args.image_url.startswith(registry): break else: raise UnsupportedRegistryError(args.image_url) module = modules.keys()[0] images = {module: args.image_url} else: images = deploy_command_util.BuildAndPushDockerImages( modules, version, cloudbuild_client, storage_client, self.Http(), code_bucket_ref, self.cli, remote_build, source_contexts, config_cleanup) deployment_manifests = {} if app_config.NonHermeticModules(): if properties.VALUES.app.use_gsutil.GetBool(): copy_func = deploy_app_command_util.CopyFilesToCodeBucket metric_name = metric_names.COPY_APP_FILES else: copy_func = deploy_app_command_util.CopyFilesToCodeBucketNoGsUtil metric_name = metric_names.COPY_APP_FILES_NO_GSUTIL deployment_manifests = copy_func( app_config.NonHermeticModules().items(), code_bucket_ref, source_contexts, storage_client) metrics.CustomTimedEvent(metric_name) all_services = clients.api.ListServices() # Now do deployment. for (module, info) in app_config.Modules().iteritems(): message = 'Updating module [{module}]'.format(module=module) with console_io.ProgressTracker(message): if args.force: log.warning( 'The --force argument is deprecated and no longer ' 'required. It will be removed in a future release.') clients.api.DeployModule(module, version, info, deployment_manifests.get(module), images.get(module)) metrics.CustomTimedEvent(metric_names.DEPLOY_API) stop_previous_version = ( deploy_command_util.GetStopPreviousVersionFromArgs(args)) if promote: new_version = version_util.Version(project, module, version) _Promote(all_services, new_version, clients, stop_previous_version) elif stop_previous_version: log.info( 'Not stopping previous version because new version was not ' 'promoted.') # Config files. for (c, info) in app_config.Configs().iteritems(): message = 'Updating config [{config}]'.format(config=c) with console_io.ProgressTracker(message): clients.gae.UpdateConfig(c, info.parsed) return deployed_urls
def Run(self, args): api_client = appengine_api_client.GetApiClient() return api_client.GetApplication()