feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The gcloud app instances group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Instances(base.Group):
"""View and manage your App Engine instances.
This set of commands can be used to view and manage your existing App Engine
instances.
For more information on App Engine instances, see:
https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To list your App Engine instances, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*- #
# Copyright 2016 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Deletes a specific instance."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import instances_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import console_io
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Delete(base.DeleteCommand):
"""Delete a specified instance."""
detailed_help = {
'EXAMPLES': """\
To delete instance i1 of service s1 and version v1, run:
$ {command} i1 --service=s1 --version=v1
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'instance', help='The instance ID.')
parser.add_argument(
'--version', '-v', required=True, help='The version ID.')
parser.add_argument(
'--service', '-s', required=True, help='The service ID.')
def Run(self, args):
client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
instance = instances_util.Instance(args.service,
args.version, args.instance)
log.status.Print('Deleting the instance [{0}].'.format(instance))
console_io.PromptContinue(cancel_on_no=True)
res = resources.REGISTRY.Parse(
args.instance,
params={
'appsId': properties.VALUES.core.project.GetOrFail,
'servicesId': args.service,
'versionsId': args.version,
'instancesId': args.instance,
},
collection='appengine.apps.services.versions.instances')
client.DeleteInstance(res)
log.DeletedResource(res)

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `app instances describe` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Describe(base.Command):
"""Display all data about an existing instance."""
detailed_help = {
'EXAMPLES': """\
To show all data about instance i1 for service s1 and version v1, run:
$ {command} --service=s1 --version=v1 i1
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'instance',
help='The instance ID.')
parser.add_argument(
'--service', '-s', required=True,
help='The service ID.')
parser.add_argument(
'--version', '-v', required=True,
help='The version ID.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
params = {'appsId': properties.VALUES.core.project.GetOrFail,
'servicesId': args.service,
'versionsId': args.version}
res = resources.REGISTRY.Parse(args.instance,
params=params,
collection='appengine.apps.services.'
'versions.instances')
return api_client.GetInstanceResource(res)

View File

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `app instances disable-debug` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import env
from googlecloudsdk.api_lib.app import instances_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.console import progress_tracker
class DisableDebug(base.Command):
"""Disable debug mode for an instance.
When not in debug mode, SSH will be disabled on the VMs. They will be included
in the health checking pools.
Note that any local changes to an instance will be *lost* if debug mode is
disabled on the instance. New instance(s) may spawn depending on the app's
scaling settings.
"""
detailed_help = {
'EXAMPLES': """\
To disable debug mode for a particular instance, run:
$ {command} --service=s1 --version=v1 i1
To disable debug mode for an instance chosen interactively, run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'instance', nargs='?',
help="""\
The instance ID to disable debug mode on. If not specified,
select instance interactively. Must uniquely specify (with other
flags) exactly one instance""")
parser.add_argument(
'--service', '-s',
help="""\
If specified, only match instances belonging to the given service.
This affects both interactive and non-interactive selection.""")
parser.add_argument(
'--version', '-v',
help="""\
If specified, only match instances belonging to the given version.
This affects both interactive and non-interactive selection.""")
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
all_instances = list(api_client.GetAllInstances(
args.service, args.version,
version_filter=lambda v: v.environment in [env.FLEX, env.MANAGED_VMS]))
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)

View File

@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `app instances enable-debug` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import env
from googlecloudsdk.api_lib.app import instances_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.console import progress_tracker
class EnableDebug(base.Command):
"""Enable debug mode for an instance (only works on the flexible environment).
When in debug mode, SSH will be enabled on the VMs, and you can use
`gcloud compute ssh` to login to them. They will be removed from the health
checking pools, but they still receive requests.
Note that any local changes to an instance will be *lost* if debug mode is
disabled on the instance. New instance(s) may spawn depending on the app's
scaling settings.
Additionally, debug mode doesn't work for applications using the
App Engine standard environment.
"""
detailed_help = {
'EXAMPLES': """\
To enable debug mode for a particular instance, run:
$ {command} --service=s1 --version=v1 i1
To enable debug mode for an instance chosen interactively, run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'instance', nargs='?',
help="""\
Instance ID to enable debug mode on. If not specified,
select instance interactively. Must uniquely specify (with other
flags) exactly one instance""")
parser.add_argument(
'--service', '-s',
help="""\
If specified, only match instances belonging to the given service.
This affects both interactive and non-interactive selection.""")
parser.add_argument(
'--version', '-v',
help="""\
If specified, only match instances belonging to the given version.
This affects both interactive and non-interactive selection.""")
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
all_instances = list(api_client.GetAllInstances(
args.service, args.version,
version_filter=lambda v: v.environment in [env.FLEX, env.MANAGED_VMS]))
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)

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `app instances list` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.calliope import base
APPENGINE_PATH_START = 'https://appengine.googleapis.com/{0}/'.format(
appengine_api_client.AppengineApiClient.ApiVersion())
def _GetUri(resource):
# TODO(b/29539463): Use parser when instances collection adds simple URIs
# and a Get method
try:
return APPENGINE_PATH_START + resource.instance.name
except AttributeError:
return APPENGINE_PATH_START + resource['instance']['name']
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List the instances affiliated with the current App Engine project."""
detailed_help = {
'EXAMPLES': """\
To list all App Engine instances, run:
$ {command}
To list all App Engine instances for a given service, run:
$ {command} -s myservice
To list all App Engine instances for a given version, run:
$ {command} -v v1
""",
}
@staticmethod
def Args(parser):
parser.add_argument('--service', '-s',
help=('If specified, only list instances belonging to '
'the given service.'))
parser.add_argument('--version', '-v',
help=('If specified, only list instances belonging to '
'the given version.'))
parser.display_info.AddFormat("""
table(
service:sort=1,
version:sort=2,
id:sort=3,
instance.vmStatus.yesno(no="N/A"),
instance.vmLiveness,
instance.vmDebugEnabled.yesno(yes="YES", no=""):label=DEBUG_MODE
)
""")
parser.display_info.AddUriFunc(_GetUri)
# TODO(b/29539463) Resources of this API are not parsable.
parser.display_info.AddCacheUpdater(None)
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return api_client.GetAllInstances(args.service, args.version)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class ListBeta(List):
"""List the instances affiliated with the current App Engine project."""
@staticmethod
def Args(parser):
parser.add_argument(
'--service',
'-s',
help=('If specified, only list instances belonging to '
'the given service.'))
parser.add_argument(
'--version',
'-v',
help=('If specified, only list instances belonging to '
'the given version.'))
parser.display_info.AddFormat("""
table(
service:sort=1,
version:sort=2,
id:sort=3,
instance.vmStatus.yesno(no="N/A"),
instance.vmLiveness,
instance.vmDebugEnabled.yesno(yes="YES", no=""):label=DEBUG_MODE
)
""")
parser.display_info.AddUriFunc(_GetUri)
parser.display_info.AddCacheUpdater(None)
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return api_client.GetAllInstances(args.service, args.version)

View File

@@ -0,0 +1,368 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `app instances ssh` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.compute import base_classes as compute_base_classes
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import exceptions as command_exceptions
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.command_lib.app import iap_tunnel
from googlecloudsdk.command_lib.app import ssh_common
from googlecloudsdk.command_lib.util.ssh import ssh
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class ScpGa(base.Command):
"""SCP from or to the VM of an App Engine Flexible instance."""
detailed_help = {
'DESCRIPTION': textwrap.dedent("""\
*{command}* lets you remotely copy files to or from an App Engine
Flexible instance.""") + ssh_common.DETAILED_HELP,
'EXAMPLES': """\
To copy one file from a remote instance to the local machine, run:
$ {command} --service=s1 --version=v1 i1:remote_file local_file
To copy several local files to a remote instance, run:
$ {command} --service=s1 --version=v1 local_1 local_1 i1:remote_dir
To use recursive copy, run:
$ {command} --service=s1 --version=v1 --recurse local_dir i1:remote_dir
""",
}
@staticmethod
def Args(parser):
flags.AddServiceVersionSelectArgs(parser)
iap_tunnel.AddSshTunnelArgs(parser)
parser.add_argument(
'--recurse',
action='store_true',
help='Upload directories recursively.')
parser.add_argument(
'--compress',
action='store_true',
help='Enable compression.')
parser.add_argument(
'sources',
help='Specifies the files to copy.',
metavar='[INSTANCE:]SRC',
nargs='+')
parser.add_argument(
'destination',
help='Specifies a destination for the source files.',
metavar='[INSTANCE:]DEST')
def Run(self, args):
"""Securily copy files from/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 SCP command exited with SCP exit code, which
usually implies that a connection problem occurred.
Returns:
int, The exit code of the SCP command.
"""
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
env = ssh.Environment.Current()
env.RequireSSH()
keys = ssh.Keys.FromFilename()
keys.EnsureKeysExist(overwrite=False)
# Make sure we have a unique remote
dst = ssh.FileReference.FromPath(args.destination)
srcs = [ssh.FileReference.FromPath(source) for source in args.sources]
ssh.SCPCommand.Verify(srcs, dst, single_remote=True)
remote = dst.remote or srcs[0].remote
instance = remote.host
if not dst.remote: # Make sure all remotes point to the same ref
for src in srcs:
src.remote = remote
try:
version_resource = api_client.GetVersionResource(args.service,
args.version)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingVersionError('{}/{}'.format(
args.service, args.version))
project = properties.VALUES.core.project.GetOrFail()
res = resources.REGISTRY.Parse(
instance,
params={
'appsId': project,
'versionsId': args.version,
'instancesId': instance,
'servicesId': args.service,
},
collection='appengine.apps.services.versions.instances')
instance_name = res.RelativeName()
try:
instance_resource = api_client.GetInstanceResource(res)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingInstanceError(instance_name)
iap_tunnel_args = iap_tunnel.CreateSshTunnelArgs(
args=args,
api_client=api_client,
track=self.ReleaseTrack(),
project=project,
version=version_resource,
instance=instance_resource)
region = '-'.join(instance_resource.vmZoneName.split('-')[:-1])
user = ssh.GetDefaultSshUsername()
project = ssh_common.GetComputeProject(self.ReleaseTrack())
oslogin_state = ssh.GetOsloginState(
None,
project,
user,
keys.GetPublicKey().ToEntry(),
None,
self.ReleaseTrack(),
app_engine_params={
'appsId': project.name,
'servicesId': res.servicesId,
'versionsId': res.versionsId,
'instancesId': instance,
'serviceAccount': (
version_resource.serviceAccount
if version_resource.serviceAccount
else ''
),
'region': region
},
messages=compute_base_classes.ComputeApiHolder(
self.ReleaseTrack()
).client.messages,
)
cert_file = None
if oslogin_state.third_party_user or oslogin_state.require_certificates:
cert_file = ssh.CertFileFromAppEngineInstance(
project.name, res.servicesId, res.versionsId, instance
)
connection_details = ssh_common.PopulatePublicKey(
api_client, args.service, args.version, remote.host,
keys.GetPublicKey(), oslogin_state.user, oslogin_state.oslogin_enabled)
# Update all remote references
remote.host = connection_details.remote.host
remote.user = connection_details.remote.user
return ssh.SCPCommand(
srcs,
dst,
identity_file=keys.key_file,
cert_file=cert_file,
compress=args.compress,
recursive=args.recurse,
options=connection_details.options,
iap_tunnel_args=iap_tunnel_args).Run(env)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
@base.DefaultUniverseOnly
class ScpBeta(base.Command):
"""SCP from or to the VM of an App Engine Flexible environment instance."""
detailed_help = {
'DESCRIPTION': textwrap.dedent("""\
*{command}* lets you remotely copy files to or from an App Engine
Flexible environment instance.""") + ssh_common.DETAILED_HELP,
'EXAMPLES': """\
To copy one file from a remote instance to the local machine, run:
$ {command} --service=s1 --version=v1 i1:remote_file local_file
To copy several local files to a remote instance, run:
$ {command} --service=s1 --version=v1 local_1 local_1 i1:remote_dir
To use recursive copy, run:
$ {command} --service=s1 --version=v1 --recurse local_dir i1:remote_dir
""",
}
@staticmethod
def Args(parser):
flags.AddServiceVersionSelectArgs(parser)
iap_tunnel.AddSshTunnelArgs(parser)
parser.add_argument(
'--recurse',
action='store_true',
help='Upload directories recursively.')
parser.add_argument(
'--compress',
action='store_true',
help='Enable compression.')
parser.add_argument(
'sources',
help='Specifies the files to copy.',
metavar='[INSTANCE:]SRC',
nargs='+')
parser.add_argument(
'destination',
help='Specifies a destination for the source files.',
metavar='[INSTANCE:]DEST')
def Run(self, args):
"""Securily copy files from/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 SCP command exited with SCP exit code, which
usually implies that a connection problem occurred.
Returns:
int, The exit code of the SCP command.
"""
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
env = ssh.Environment.Current()
env.RequireSSH()
keys = ssh.Keys.FromFilename()
keys.EnsureKeysExist(overwrite=False)
# Make sure we have a unique remote
dst = ssh.FileReference.FromPath(args.destination)
srcs = [ssh.FileReference.FromPath(source) for source in args.sources]
ssh.SCPCommand.Verify(srcs, dst, single_remote=True)
remote = dst.remote or srcs[0].remote
instance = remote.host
if not dst.remote: # Make sure all remotes point to the same ref
for src in srcs:
src.remote = remote
try:
version_resource = api_client.GetVersionResource(args.service,
args.version)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingVersionError('{}/{}'.format(
args.service, args.version))
project = properties.VALUES.core.project.GetOrFail()
res = resources.REGISTRY.Parse(
instance,
params={
'appsId': project,
'versionsId': args.version,
'instancesId': instance,
'servicesId': args.service,
},
collection='appengine.apps.services.versions.instances')
instance_name = res.RelativeName()
try:
instance_resource = api_client.GetInstanceResource(res)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingInstanceError(instance_name)
iap_tunnel_args = iap_tunnel.CreateSshTunnelArgs(args, api_client,
self.ReleaseTrack(),
project, version_resource,
instance_resource)
region = '-'.join(instance_resource.vmZoneName.split('-')[:-1])
user = ssh.GetDefaultSshUsername()
project = ssh_common.GetComputeProject(self.ReleaseTrack())
oslogin_state = ssh.GetOsloginState(
None,
project,
user,
keys.GetPublicKey().ToEntry(),
None,
self.ReleaseTrack(),
app_engine_params={
'appsId': project.name,
'servicesId': res.servicesId,
'versionsId': res.versionsId,
'instancesId': instance,
'serviceAccount': (
version_resource.serviceAccount
if version_resource.serviceAccount
else ''
),
'region': region
},
messages=compute_base_classes.ComputeApiHolder(
self.ReleaseTrack()
).client.messages,
)
cert_file = None
if oslogin_state.third_party_user or oslogin_state.require_certificates:
cert_file = ssh.CertFileFromAppEngineInstance(
project.name, res.servicesId, res.versionsId, instance
)
connection_details = ssh_common.PopulatePublicKey(
api_client, args.service, args.version, remote.host,
keys.GetPublicKey(), oslogin_state.user, oslogin_state.oslogin_enabled)
# Update all remote references
remote.host = connection_details.remote.host
remote.user = connection_details.remote.user
cmd = ssh.SCPCommand(
srcs,
dst,
identity_file=keys.key_file,
cert_file=cert_file,
compress=args.compress,
recursive=args.recurse,
options=connection_details.options,
iap_tunnel_args=iap_tunnel_args)
return cmd.Run(env)

View File

@@ -0,0 +1,357 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `app instances ssh` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import argparse
import textwrap
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.compute import base_classes as compute_base_classes
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import exceptions as command_exceptions
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.command_lib.app import iap_tunnel
from googlecloudsdk.command_lib.app import ssh_common
from googlecloudsdk.command_lib.util.ssh import containers
from googlecloudsdk.command_lib.util.ssh import ssh
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
def _ArgsCommon(parser):
parser.add_argument(
'instance',
help='The instance ID.')
parser.add_argument(
'--container',
help='Name of the container within the VM to connect to.')
parser.add_argument(
'command',
nargs=argparse.REMAINDER,
help='Remote command to execute on the VM.')
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class SshGa(base.Command):
"""SSH into the VM of an App Engine Flexible instance."""
detailed_help = {
'DESCRIPTION':
textwrap.dedent("""\
*{command}* lets you remotely log in to your running App Engine Flexible
instances under two conditions:
* The instance is running.
* The instance has an external IP address. To check from the Cloud
Console, go to the Instances page and confirm that there is an IP
address listed in the VM IP column. To check from your app.yaml, open
your app.yaml and look at the network settings. The *instance_ip_mode*
field must be either not listed or set to ``external''.""") +
ssh_common.DETAILED_HELP,
'EXAMPLES':
"""\
To SSH into an App Engine Flexible instance, run:
$ {command} --service=s1 --version=v1 i1
To SSH into the app container within an instance, run:
$ {command} --service=s1 --version=v1 i1 --container=gaeapp
To SSH into the app container and run a remote command, run:
$ {command} --service=s1 --version=v1 i1 --container=gaeapp -- echo hello
""",
}
@staticmethod
def Args(parser):
flags.AddServiceVersionSelectArgs(parser)
_ArgsCommon(parser)
iap_tunnel.AddSshTunnelArgs(parser)
def Run(self, args):
"""Connect to a running App Engine flexible 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.GetApiClientForTrack(self.ReleaseTrack())
try:
res = resources.REGISTRY.Parse(
args.instance,
collection='appengine.apps.services.versions.instances')
service = res.servicesId
version = res.versionsId
instance = res.instancesId
except resources.RequiredFieldOmittedException:
service = args.service
version = args.version
instance = args.instance
env = ssh.Environment.Current()
env.RequireSSH()
keys = ssh.Keys.FromFilename()
keys.EnsureKeysExist(overwrite=False)
try:
version_resource = api_client.GetVersionResource(service, version)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingVersionError('{}/{}'.format(
service, version))
project = properties.VALUES.core.project.GetOrFail()
res = resources.REGISTRY.Parse(
instance,
params={
'appsId': project,
'versionsId': version,
'instancesId': instance,
'servicesId': service,
},
collection='appengine.apps.services.versions.instances',
)
try:
instance_resource = api_client.GetInstanceResource(res)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingInstanceError(res.RelativeName())
iap_tunnel_args = iap_tunnel.CreateSshTunnelArgs(
args,
api_client,
self.ReleaseTrack(),
project,
version_resource,
instance_resource,
)
region = '-'.join(instance_resource.vmZoneName.split('-')[:-1])
user = ssh.GetDefaultSshUsername()
project = ssh_common.GetComputeProject(self.ReleaseTrack())
oslogin_state = ssh.GetOsloginState(
None,
project,
user,
keys.GetPublicKey().ToEntry(),
None,
self.ReleaseTrack(),
app_engine_params={
'appsId': project.name,
'servicesId': service,
'versionsId': version,
'instancesId': instance,
'serviceAccount': (
version_resource.serviceAccount
if version_resource.serviceAccount
else ''
),
'region': region
},
messages=compute_base_classes.ComputeApiHolder(
self.ReleaseTrack()
).client.messages,
)
cert_file = None
if oslogin_state.third_party_user or oslogin_state.require_certificates:
cert_file = ssh.CertFileFromAppEngineInstance(
project.name, service, version, instance
)
connection_details = ssh_common.PopulatePublicKey(
api_client,
service,
version,
instance,
keys.GetPublicKey(),
oslogin_state.user,
oslogin_state.oslogin_enabled,
)
remote_command = containers.GetRemoteCommand(args.container, args.command)
tty = containers.GetTty(args.container, args.command)
try:
filtered_firewall_rules = ssh_common.FilterFirewallRules(
ssh_common.FetchFirewallRules()
)
if not filtered_firewall_rules:
log.warning(
'No ingress firewall rule that allows ingress to port 22. '
'User should add a firewall rule that allows ingress to port 22.'
)
except apitools_exceptions.NotFoundError:
raise log.exception('Unable to fetch firewall rules')
return ssh.SSHCommand(
connection_details.remote,
identity_file=keys.key_file,
cert_file=cert_file,
tty=tty,
remote_command=remote_command,
options=connection_details.options,
iap_tunnel_args=iap_tunnel_args).Run(env)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
@base.DefaultUniverseOnly
class SshBeta(SshGa):
"""SSH into the VM of an App Engine Flexible instance."""
@staticmethod
def Args(parser):
flags.AddServiceVersionSelectArgs(parser, short_flags=True)
_ArgsCommon(parser)
iap_tunnel.AddSshTunnelArgs(parser)
def Run(self, args):
"""Connect to a running App Engine flexible 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.
"""
log.warning(
'For `gcloud beta app instances ssh`, the short flags `-s` and `-v` '
'are deprecated and will be removed 2017-09-27. For the GA command, '
'they are not available. Please use `--service` and `--version` '
'instead.')
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
try:
res = resources.REGISTRY.Parse(
args.instance,
collection='appengine.apps.services.versions.instances')
service = res.servicesId
version = res.versionsId
instance = res.instancesId
except resources.RequiredFieldOmittedException:
service = args.service
version = args.version
instance = args.instance
env = ssh.Environment.Current()
env.RequireSSH()
keys = ssh.Keys.FromFilename()
keys.EnsureKeysExist(overwrite=False)
try:
version_resource = api_client.GetVersionResource(service, version)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingVersionError('{}/{}'.format(
service, version))
project = properties.VALUES.core.project.GetOrFail()
res = resources.REGISTRY.Parse(
instance,
params={
'appsId': project,
'versionsId': version,
'instancesId': instance,
'servicesId': service,
},
collection='appengine.apps.services.versions.instances'
)
try:
instance_resource = api_client.GetInstanceResource(res)
except apitools_exceptions.HttpNotFoundError:
raise command_exceptions.MissingInstanceError(res.RelativeName())
iap_tunnel_args = iap_tunnel.CreateSshTunnelArgs(args, api_client,
self.ReleaseTrack(),
project, version_resource,
instance_resource)
region = '-'.join(instance_resource.vmZoneName.split('-')[:-1])
user = ssh.GetDefaultSshUsername()
project = ssh_common.GetComputeProject(self.ReleaseTrack())
oslogin_state = ssh.GetOsloginState(
None,
project,
user,
keys.GetPublicKey().ToEntry(),
None,
self.ReleaseTrack(),
app_engine_params={
'appsId': project.name,
'servicesId': service,
'versionsId': version,
'instancesId': instance,
'serviceAccount': (
version_resource.serviceAccount
if version_resource.serviceAccount
else ''
),
'region': region
},
messages=compute_base_classes.ComputeApiHolder(
self.ReleaseTrack()
).client.messages,
)
cert_file = None
if oslogin_state.third_party_user or oslogin_state.require_certificates:
cert_file = ssh.CertFileFromAppEngineInstance(
project.name, service, version, instance
)
connection_details = ssh_common.PopulatePublicKey(
api_client,
service,
version,
instance,
keys.GetPublicKey(),
oslogin_state.user,
oslogin_state.oslogin_enabled,
)
remote_command = containers.GetRemoteCommand(args.container, args.command)
tty = containers.GetTty(args.container, args.command)
return ssh.SSHCommand(
connection_details.remote,
identity_file=keys.key_file,
cert_file=cert_file,
tty=tty,
remote_command=remote_command,
options=connection_details.options,
iap_tunnel_args=iap_tunnel_args).Run(env)