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()))
Beispiel #2
0
    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)
Beispiel #3
0
  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)
Beispiel #4
0
    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.')
Beispiel #5
0
    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)
Beispiel #6
0
    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()))
Beispiel #7
0
    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)
Beispiel #8
0
    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()))
Beispiel #9
0
    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)
Beispiel #10
0
 def Run(self, args):
   api_client = appengine_api_client.GetApiClient()
   return api_client.GetVersionResource(service=args.service,
                                        version=args.version)
Beispiel #11
0
 def Run(self, args):
     api_client = appengine_api_client.GetApiClient()
     return api_client.GetInstanceResource(service=args.service,
                                           version=args.version,
                                           instance=args.instance)
Beispiel #12
0
    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}
Beispiel #14
0
    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)
Beispiel #15
0
  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()
Beispiel #16
0
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}
Beispiel #17
0
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)
Beispiel #18
0
    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
Beispiel #19
0
 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()
Beispiel #20
0
 def Run(self, args):
     api_client = appengine_api_client.GetApiClient()
     return api_client.GetAllInstances(args.service, args.version)
Beispiel #21
0
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)
Beispiel #23
0
 def Run(self, args):
     api_client = appengine_api_client.GetApiClient()
     return api_client.GetOperation(args.operation)
Beispiel #24
0
 def Run(self, args):
     api_client = appengine_api_client.GetApiClient()
     return api_client.GetServiceResource(args.service)
Beispiel #25
0
 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
Beispiel #27
0
 def Run(self, args):
     api_client = appengine_api_client.GetApiClient()
     return api_client.GetApplication()