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,80 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 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 group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
DETAILED_HELP = {
'brief': 'Manage your App Engine deployments.',
'DESCRIPTION': """
The gcloud app command group lets you deploy and manage your Google App
Engine apps. These commands replace their equivalents in the appcfg
tool.
App Engine is a platform for building scalable web applications
and mobile backends. App Engine provides you with built-in services and
APIs such as NoSQL datastores, memcache, and a user authentication API,
common to most applications.
More information on App Engine can be found here:
https://cloud.google.com/appengine and detailed documentation can be
found here: https://cloud.google.com/appengine/docs/
""",
'EXAMPLES': """\
To run your app locally in the development application server
to simulate your application running in production App Engine with
sandbox restrictions and services provided by App Engine SDK libraries,
use the `dev_appserver.py` command and your app's `app.yaml`
configuration file to run:
$ dev_appserver.py ~/my_app/app.yaml
For an in-depth look into using the local development server, follow
this guide : https://cloud.google.com/appengine/docs/standard/python/tools/using-local-server
To deploy the code and configuration of your app to the App Engine
server, run:
$ {command} deploy ~/my_app/app.yaml
To list all versions of all services of your existing deployments, run:
$ {command} versions list
"""
}
@base.ReleaseTracks(base.ReleaseTrack.ALPHA,
base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class AppengineGA(base.Group):
"""Manage your App Engine deployments."""
category = base.COMPUTE_CATEGORY
def Filter(self, context, args):
# TODO(b/190524958): Determine if command group works with project number
base.RequireProjectID(args)
del context, args
base.DisableUserProjectQuota()
AppengineGA.detailed_help = DETAILED_HELP

View File

@@ -0,0 +1,80 @@
# -*- 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 Browse command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import browser_dispatcher
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Browse(base.Command):
"""Open the current app in a web browser.
"""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To open the default service, run:
$ {command}
To open a specific service, run:
$ {command} --service="myService"
To open a specific version, run:
$ {command} --service="myService" --version="v1"
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat('value(url)')
flags.LAUNCH_BROWSER.AddToParser(parser)
parser.add_argument(
'--version',
'-v',
help=('The version of the app that should be opened. If not specified, '
"choose a version based on the service's traffic split."))
parser.add_argument(
'--service',
'-s',
help=('The service that should be opened. If not specified, use the '
'default service. May be used in conjunction with `--version`.'))
def Run(self, args):
"""Launch a browser, or return a URL to print."""
project = properties.VALUES.core.project.Get(required=True)
url_format = browser_dispatcher.BrowseApp(project,
args.service,
args.version,
args.launch_browser)
if url_format:
if args.launch_browser:
log.status.Print(
'Did not detect your browser. Go to this link to view your app:')
return url_format
return None

View File

@@ -0,0 +1,130 @@
# -*- 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.
"""The app create 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.command_lib.app import create_util
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
_DETAILED_HELP = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To create an app with region chosen interactively, run:
$ {command}
To create an app in the us-central region, run:
$ {command} --region=us-central
To create an app that with a user-managed service account, run:
$ {command} --service-account=SERVICE_ACCOUNT
To create an app with minimum SSL policy allowing TLS 1.2 and above, run:
$ {command} --ssl-policy=TLS_VERSION_1_2
""",
}
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
"""Create an App Engine app within the current Google Cloud Project."""
detailed_help = _DETAILED_HELP
@staticmethod
def Args(parser):
create_util.AddAppCreateFlags(parser)
def Run(self, args):
project = properties.VALUES.core.project.Get(required=True)
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
if args.region:
create_util.CreateApp(
api_client,
project,
args.region,
service_account=args.service_account,
ssl_policy=args.ssl_policy,
)
elif console_io.CanPrompt():
create_util.CheckAppNotExists(api_client, project)
create_util.CreateAppInteractively(
api_client,
project,
service_account=args.service_account,
ssl_policy=args.ssl_policy,
)
else:
raise create_util.UnspecifiedRegionError(
'Prompts are disabled. Region must be specified either by the '
'`--region` flag or interactively. Use `gcloud app regions '
'list` to list available regions.')
log.status.Print('Success! The app is now created. Please use '
'`gcloud app deploy` to deploy your first app.')
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class CreateBeta(base.CreateCommand):
"""Create an App Engine app within the current Google Cloud Project."""
detailed_help = _DETAILED_HELP
@staticmethod
def Args(parser):
create_util.AddAppCreateFlags(parser)
def Run(self, args):
project = properties.VALUES.core.project.Get(required=True)
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
if args.region:
create_util.CreateApp(
api_client,
project,
args.region,
service_account=args.service_account,
ssl_policy=args.ssl_policy,
)
elif console_io.CanPrompt():
create_util.CheckAppNotExists(api_client, project)
create_util.CreateAppInteractively(
api_client,
project,
service_account=args.service_account,
ssl_policy=args.ssl_policy,
)
else:
raise create_util.UnspecifiedRegionError(
'Prompts are disabled. Region must be specified either by the '
'`--region` flag or interactively. Use `gcloud app regions '
'list` to list available regions.'
)
log.status.Print(
'Success! The app is now created. Please use '
'`gcloud app deploy` to deploy your first app.'
)

View File

@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*- #
# Copyright 2013 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 deploy command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import hashlib
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import runtime_builders
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import deploy_util
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
_DETAILED_HELP = {
'brief': ('Deploy the local code and/or configuration of your app to App '
'Engine.'),
'DESCRIPTION':
"""\
This command is used to deploy both code and configuration to the App
Engine server. As an input it takes one or more DEPLOYABLES that should be
uploaded. A DEPLOYABLE can be a service's .yaml file or a configuration's
.yaml file (for more information about configuration files specific to your
App Engine environment, refer to
https://cloud.google.com/appengine/docs/standard/configuration-files
or
https://cloud.google.com/appengine/docs/flexible/configuration-files).
Note, for Java 8 Standard apps or Java 11/17/21 Standard apps using bundled
services, you must add the path to the appengine-web.xml file inside the
WEB-INF directory. gcloud app deploy skips files specified in the
.gcloudignore file (see gcloud topic gcloudignore for more information).
For Java 11 Standard, you can either use the yaml file, a Maven pom.xml, or
a Gradle build.gradle. Alternatively, if the application is a single
self-contained jar, you can give the path to the jar and a simple service
configuration will be generated. You can deploy Java 11 Maven source
projects by specifying the location of your project's pom.xml file, and it
will be built and deployed using App Engine Buildpacks.
""",
'EXAMPLES':
"""\
To deploy a single service, run:
$ {command} ~/my_app/app.yaml
To deploy an App Engine Standard Java8 service or a Java11 service using bundled services, run:
$ {command} ~/my_app/WEB-INF/appengine-web.xml
To deploy an App Engine Standard Java11 single jar, run:
$ {command} ~/my_app/my_jar.jar
To deploy an App Engine Standard Java11 Maven source project, run:
$ {command} ~/my_app/pom.xml
To deploy an App Engine Standard Java11 Gradle source project, run:
$ {command} ~/my_app/build.gradle
By default, the service is deployed to the current project configured
via:
$ gcloud config set core/project PROJECT
To override this value for a single deployment, use the ``--project''
flag:
$ {command} ~/my_app/app.yaml --project=PROJECT
To deploy multiple services, run:
$ {command} ~/my_app/app.yaml ~/my_app/another_service.yaml
To change the default --promote behavior for your current
environment, run:
$ gcloud config set app/promote_by_default false
To deploy a service that will run as a service account, run:
$ {command} ~/my_app/app.yaml --service-account=SERVICE_ACCOUNT
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class DeployGA(base.SilentCommand):
"""Deploy the local code and/or configuration of your app to App Engine."""
@staticmethod
def Args(parser):
"""Get arguments for this command."""
deploy_util.ArgsDeploy(parser)
def Run(self, args):
runtime_builder_strategy = deploy_util.GetRuntimeBuilderStrategy(
base.ReleaseTrack.GA)
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
if (runtime_builder_strategy !=
runtime_builders.RuntimeBuilderStrategy.NEVER and
self._ServerSideExperimentEnabled()):
flex_image_build_option_default = deploy_util.FlexImageBuildOptions.ON_SERVER
else:
flex_image_build_option_default = deploy_util.FlexImageBuildOptions.ON_CLIENT
return deploy_util.RunDeploy(
args,
api_client,
runtime_builder_strategy=runtime_builder_strategy,
parallel_build=False,
flex_image_build_option=deploy_util.GetFlexImageBuildOption(
default_strategy=flex_image_build_option_default
),
)
def _ServerSideExperimentEnabled(self):
"""Evaluates whether the build on server-side experiment is enabled for the project.
The experiment is enabled for a project if the sha256 hash of the
projectID mod 100 is smaller than the current experiment rollout percent.
Returns:
false if the experiment is not enabled for this project or the
experiment config cannot be read due to an error
"""
runtimes_builder_root = properties.VALUES.app.runtime_builders_root.Get(
required=True)
try:
experiment_config = runtime_builders.Experiments.LoadFromURI(
runtimes_builder_root)
experiment_percent = experiment_config.GetExperimentPercentWithDefault(
runtime_builders.Experiments.TRIGGER_BUILD_SERVER_SIDE, 0)
project_hash = int(
hashlib.sha256(
properties.VALUES.core.project.Get().encode('utf-8')).hexdigest(),
16) % 100
return project_hash < experiment_percent
except runtime_builders.ExperimentsError as e:
log.debug(
'Experiment config file could not be read. This error is '
'informational, and does not cause a deployment to fail. '
'Reason: %s' % e, exc_info=True)
return False
@base.ReleaseTracks(base.ReleaseTrack.BETA)
@base.DefaultUniverseOnly
class DeployBeta(base.SilentCommand):
"""Deploy the local code and/or configuration of your app to App Engine."""
@staticmethod
def Args(parser):
"""Get arguments for this command."""
deploy_util.ArgsDeploy(parser)
def Run(self, args):
runtime_builder_strategy = deploy_util.GetRuntimeBuilderStrategy(
base.ReleaseTrack.BETA)
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return deploy_util.RunDeploy(
args,
api_client,
use_beta_stager=True,
runtime_builder_strategy=runtime_builder_strategy,
parallel_build=True,
flex_image_build_option=deploy_util.GetFlexImageBuildOption(
default_strategy=deploy_util.FlexImageBuildOptions.ON_SERVER))
DeployGA.detailed_help = _DETAILED_HELP
DeployBeta.detailed_help = _DETAILED_HELP

View File

@@ -0,0 +1,66 @@
# -*- 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.
"""The `app services describe` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import exceptions
from googlecloudsdk.core import log
_DETAILED_HELP = {
'EXAMPLES': """\
To show all the data about the current application, run
$ {command}
""",
}
def Describe(api_client):
try:
return api_client.GetApplication()
except apitools_exceptions.HttpNotFoundError:
log.debug('No app found:', exc_info=True)
project = api_client.project
raise exceptions.MissingApplicationError(project)
@base.ReleaseTracks(base.ReleaseTrack.GA)
class DescribeGA(base.Command):
"""Display all data about an existing service."""
def Run(self, args):
return Describe(appengine_api_client.GetApiClientForTrack(
self.ReleaseTrack()))
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class DescribeBeta(base.Command):
"""Display all data about an existing service using the beta API."""
def Run(self, args):
return Describe(appengine_api_client.GetApiClientForTrack(
self.ReleaseTrack()))
DescribeGA.detailed_help = _DETAILED_HELP
DescribeBeta.detailed_help = _DETAILED_HELP

View File

@@ -0,0 +1,59 @@
# -*- 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 gcloud app domain-mappings 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,
base.ReleaseTrack.ALPHA)
class DomainMappings(base.Group):
"""View and manage your App Engine domain mappings.
This set of commands can be used to view and manage your app's
domain mappings.
App Engine Domain Mappings allow an application to be served via one or many
custom domains, such as `example.com`, instead of the default
`https://<PROJECT-ID>.<REGION-ID>.r.appspot.com` address. You can use a
custom domain with or without SSL.
Use the AUTOMATIC management type to automatically provision an SSL
certificate for your domain. Use the MANUAL management type to provide
your own certificate or omit SSL.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list your App Engine domains, run:
$ {command} list
To create a domain with an automatically managed certificate, run:
$ {command} create 'example.com' --certificate-management=AUTOMATIC
To create a domain with a manual certificate, run:
$ {command} create 'example.com' --certificate-management=manual --certificate-id=1234
""",
}

View File

@@ -0,0 +1,86 @@
# -*- 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.
"""Surface for creating an App Engine domain mapping."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_domains_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import domains_util
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
class Create(base.CreateCommand):
"""Creates a domain mapping."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To create a new App Engine domain mapping with an automatically
managed certificate, run:
$ {command} 'example.com'
Note: managed certificates do not support wildcard domain mappings.
To create a domain with a manual certificate, run:
$ {command} '*.example.com' --certificate-management=manual --certificate-id=1234
To create a domain with no associated certificate, run:
$ {command} '*.example.com' --certificate-management=manual
""",
}
@staticmethod
def Args(parser):
flags.DOMAIN_FLAG.AddToParser(parser)
flags.AddCertificateIdFlag(parser, include_no_cert=False)
parser.display_info.AddFormat('default(id, resourceRecords)')
flags.AddCertificateManagementFlag(parser)
def Run(self, args):
return self.Create(args)
def Create(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
domains_util.ValidateCertificateArgs(args.certificate_id,
args.certificate_management)
if not args.certificate_management:
if not args.certificate_id:
args.certificate_management = 'automatic'
else:
args.certificate_management = 'manual'
management_type = domains_util.ParseCertificateManagement(
client.messages, args.certificate_management)
mapping = client.CreateDomainMapping(args.domain, args.certificate_id,
management_type)
log.CreatedResource(args.domain)
log.status.Print(
'Please add the following entries to your domain registrar.'
' DNS changes can require up to 24 hours to take effect.')
return mapping

View File

@@ -0,0 +1,54 @@
# -*- 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.
"""Surface for deleting an App Engine domain mapping."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_domains_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
class Delete(base.DeleteCommand):
"""Deletes a specified domain mapping."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To delete an App Engine domain mapping, run:
$ {command} '*.example.com'
""",
}
@staticmethod
def Args(parser):
flags.DOMAIN_FLAG.AddToParser(parser)
def Run(self, args):
console_io.PromptContinue(
prompt_string=('Deleting mapping [{0}]. This will stop your app from'
' serving from this domain.'.format(args.domain)),
cancel_on_no=True)
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
client.DeleteDomainMapping(args.domain)
log.DeletedResource(args.domain)

View File

@@ -0,0 +1,46 @@
# -*- 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.
"""Surface for retrieving a single domain mapping."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_domains_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
class Describe(base.DescribeCommand):
"""Describes a specified domain mapping."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To describe an App Engine domain mapping, run:
$ {command} '*.example.com'
""",
}
@staticmethod
def Args(parser):
flags.DOMAIN_FLAG.AddToParser(parser)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
return client.GetDomainMapping(args.domain)

View File

@@ -0,0 +1,51 @@
# -*- 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.
"""Surface for listing all domain mapping for an app."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_domains_api_client as api_client
from googlecloudsdk.calliope import base
class List(base.ListCommand):
"""Lists domain mappings."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list all App Engine domain mappings, run:
$ {command}
""",
}
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
return client.ListDomainMappings()
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
id:sort=1,
ssl_settings.certificate_id:label=SSL_CERTIFICATE_ID,
ssl_settings.sslManagementType.yesno(no='MANUAL'):label=SSL_MANAGEMENT_TYPE,
ssl_settings.pending_managed_certificate_id:label=PENDING_AUTO_CERT)
""")

View File

@@ -0,0 +1,73 @@
# -*- 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.
"""Surface for updating an App Engine domain mapping."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_domains_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import domains_util
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
class Update(base.UpdateCommand):
"""Updates a domain mapping."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To update an App Engine domain mapping, run:
$ {command} '*.example.com' --certificate-id=1234
To remove a certificate from a domain:
$ {command} '*.example.com' --no-certificate-id
""",
}
@staticmethod
def Args(parser):
flags.DOMAIN_FLAG.AddToParser(parser)
flags.AddCertificateIdFlag(parser, include_no_cert=True)
flags.AddCertificateManagementFlag(parser)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
domains_util.ValidateCertificateArgsForUpdate(args.certificate_id,
args.no_certificate_id,
args.certificate_management)
if (not args.certificate_management and
(args.certificate_id or args.no_certificate_id)):
args.certificate_management = 'manual'
if (args.certificate_management and
args.certificate_management.lower() == 'manual' and
not args.certificate_id and not args.no_certificate_id):
args.no_certificate_id = True
management_type = domains_util.ParseCertificateManagement(
client.messages, args.certificate_management)
mapping = client.UpdateDomainMapping(args.domain, args.certificate_id,
args.no_certificate_id,
management_type)
log.UpdatedResource(args.domain)
return mapping

View File

@@ -0,0 +1,40 @@
# -*- 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 gcloud app firewall-rules group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class FirewallRules(base.Group):
"""View and manage your App Engine firewall rules.
This set of commands can be used to view and manage your app's firewall rules.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list your App Engine firewall rules, run:
$ {command} list""",
}

View File

@@ -0,0 +1,63 @@
# -*- 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.
"""Surface for creating a firewall rule."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_firewall_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.app import firewall_rules_util
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
class Create(base.CreateCommand):
"""Creates a firewall rule."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To create a new App Engine firewall rule, run:
$ {command} 1234
--source-range='2001:db8::/32'
--action=deny
--description='block traffic from the example range.'
""",
}
@staticmethod
def Args(parser):
flags.FIREWALL_PRIORITY_FLAG.AddToParser(parser)
flags.AddFirewallRulesFlags(parser, required=True)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
priority = firewall_rules_util.ParsePriority(args.priority)
if priority == firewall_rules_util.DEFAULT_RULE_PRIORITY:
raise exceptions.InvalidArgumentException(
'priority', 'The `default` can not be created, only updated.')
action = firewall_rules_util.ParseAction(client.messages, args.action)
rule = client.Create(priority, args.source_range, action, args.description)
log.CreatedResource(args.priority)
return rule

View File

@@ -0,0 +1,62 @@
# -*- 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.
"""Surface for deleting a firewall rule."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_firewall_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.app import firewall_rules_util
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
class Delete(base.DeleteCommand):
"""Deletes a specified firewall rule."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To delete an App Engine firewall rule, run:
$ {command} 1234
""",
}
@staticmethod
def Args(parser):
flags.FIREWALL_PRIORITY_FLAG.AddToParser(parser)
def Run(self, args):
priority = firewall_rules_util.ParsePriority(args.priority)
if priority == firewall_rules_util.DEFAULT_RULE_PRIORITY:
raise exceptions.InvalidArgumentException(
'priority', 'The `default` can not be deleted, only updated.')
console_io.PromptContinue(
prompt_string='You are about to delete rule [{0}].'.format(priority),
cancel_on_no=True)
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
resource = firewall_rules_util.ParseFirewallRule(client, priority)
client.Delete(resource)
log.DeletedResource(priority)

View File

@@ -0,0 +1,49 @@
# -*- 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.
"""Surface for retrieving a single firewall rule."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_firewall_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import firewall_rules_util
from googlecloudsdk.command_lib.app import flags
class Describe(base.DescribeCommand):
"""Prints the fields of a specified firewall rule."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To describe an App Engine firewall rule, run:
$ {command} 1234
""",
}
@staticmethod
def Args(parser):
flags.FIREWALL_PRIORITY_FLAG.AddToParser(parser)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
resource = firewall_rules_util.ParseFirewallRule(client, args.priority)
return client.Get(resource)

View File

@@ -0,0 +1,46 @@
# -*- 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.
"""Surface for listing all firewall rules."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_firewall_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import firewall_rules_util
class List(base.ListCommand):
"""Lists the firewall rules."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list all App Engine firewall rules, run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(firewall_rules_util.LIST_FORMAT)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
return client.List()

View File

@@ -0,0 +1,59 @@
# -*- 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.
"""Surface to test an ip address against firewall rules."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_firewall_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import firewall_rules_util
from googlecloudsdk.core import log
class TestIp(base.Command):
"""Display firewall rules that match a given IP."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To test an IP address against the firewall rule set, run:
$ {command} 127.1.2.3
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(firewall_rules_util.LIST_FORMAT)
parser.add_argument(
'ip', help='An IPv4 or IPv6 address to test against the firewall.')
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
response = client.List(args.ip)
rules = list(response)
if rules:
log.status.Print('The action `{0}` will apply to the IP address.\n'.
format(rules[0].action))
log.status.Print('Matching Rules')
else:
log.status.Print('No rules match the IP address.')
return rules

View File

@@ -0,0 +1,64 @@
# -*- 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.
"""Surface for updating a firewall rule."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import util as util
from googlecloudsdk.api_lib.app.api import appengine_firewall_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import firewall_rules_util
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
class Update(base.UpdateCommand):
"""Updates a firewall rule."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To update an App Engine firewall rule, run:
$ {command} 1234
--source-range='2001:db8::/32'
--action=allow
--description='This is an example rule.'
""",
}
@staticmethod
def Args(parser):
flags.FIREWALL_PRIORITY_FLAG.AddToParser(parser)
flags.AddFirewallRulesFlags(parser, required=False)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
priority = firewall_rules_util.ParsePriority(args.priority)
resource = firewall_rules_util.ParseFirewallRule(client, priority)
action = firewall_rules_util.ParseAction(client.messages, args.action)
try:
rule = client.Update(resource, priority, args.source_range, action,
args.description)
except util.NoFieldsSpecifiedError:
firewall_rules_util.RaiseMinArgument()
log.UpdatedResource(priority)
return rule

View File

@@ -0,0 +1,187 @@
# -*- 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 gen-config command."""
# TODO(b/172812887) - Delete this command entirely; it's PY2 only.
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import io
import os
import shutil
import tempfile
from gae_ext_runtime import ext_runtime
from googlecloudsdk.api_lib.app import yaml_parsing
from googlecloudsdk.api_lib.app.runtimes import fingerprinter
from googlecloudsdk.appengine.api import appinfo
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import deployables
from googlecloudsdk.command_lib.app import exceptions
from googlecloudsdk.command_lib.app import output_helpers
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
from ruamel import yaml
import six
_DEPRECATION_MSG = """\
This command is deprecated and will soon be removed.
{fingerprinting}
To create a custom runtime, please follow the instructions at
https://cloud.google.com/appengine/docs/flexible/custom-runtimes/
""".format(fingerprinting=deployables.FINGERPRINTING_WARNING)
# TODO(b/28509217): Move back into command class when `preview` is gone.
def _Args(parser):
"""Add arguments for `gcloud app gen-config`."""
parser.add_argument(
'source_dir',
nargs='?',
help='The source directory to fingerprint.',
default=files.GetCWD())
parser.add_argument(
'--config',
default=None,
help=('The yaml file defining the service configuration. This is '
'normally one of the generated files, but when generating a '
'custom runtime there can be an app.yaml containing parameters.'))
rt_list = [r for r in appinfo.GetAllRuntimes() if r not in ['vm', 'custom']]
parser.add_argument(
'--runtime',
default=None,
help=('Generate config files for a given runtime. Can be used in '
'conjunction with --custom. Allowed runtimes are: ' +
', '.join(rt_list) + '.'))
parser.add_argument(
'--custom',
action='store_true',
default=False,
help=('If true, generate config files for a custom runtime. This '
'will produce a Dockerfile, a .dockerignore file and an app.yaml '
'(possibly other files as well, depending on the runtime).'))
def _Run(args):
"""Run the `gcloud app gen-config` command."""
if args.config:
# If the user has specified an config file, use that.
config_filename = args.config
else:
# Otherwise, check for an app.yaml in the source directory.
config_filename = os.path.join(args.source_dir, 'app.yaml')
if not os.path.exists(config_filename):
config_filename = None
# If there was an config file either specified by the user or in the source
# directory, load it.
if config_filename:
try:
myi = yaml_parsing.ServiceYamlInfo.FromFile(config_filename)
config = myi.parsed
except IOError as ex:
log.error('Unable to open %s: %s', config_filename, ex)
return
else:
config = None
fingerprinter.GenerateConfigs(
args.source_dir,
ext_runtime.Params(appinfo=config, custom=args.custom,
runtime=args.runtime),
config_filename)
# If the user has a config file, make sure that they're using a custom
# runtime.
if config and args.custom and config.GetEffectiveRuntime() != 'custom':
alter = console_io.PromptContinue(
default=False,
message=output_helpers.RUNTIME_MISMATCH_MSG.format(config_filename),
prompt_string='Would you like to update it now?')
if alter:
_AlterRuntime(config_filename, 'custom')
log.status.Print('[{0}] has been updated.'.format(config_filename))
else:
log.status.Print('Please update [{0}] manually by changing the runtime '
'field to custom.'.format(config_filename))
@base.Deprecate(is_removed=False, warning=_DEPRECATION_MSG)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class GenConfig(base.Command):
"""Generate missing configuration files for a source directory."""
@staticmethod
def Args(parser):
_Args(parser)
def Run(self, args):
if six.PY3:
raise exceptions.NotSupportedPy3Exception(
'This command does not support python3.')
_Run(args)
def _AlterRuntime(config_filename, runtime):
try:
# 0. Take backup
with tempfile.NamedTemporaryFile(prefix='app.yaml.') as f:
backup_fname = f.name
log.status.Print(
'Copying original config [{0}] to backup location [{1}].'.format(
config_filename, backup_fname))
shutil.copyfile(config_filename, backup_fname)
# 1. Open and parse file using ruamel
with files.FileReader(config_filename) as yaml_file:
encoding = yaml_file.encoding
config = yaml.load(yaml_file, yaml.RoundTripLoader)
# 2. Alter the ruamel in-memory object representing the yaml file
config['runtime'] = runtime
# 3. Create an in-memory file buffer and write yaml file to it
raw_buf = io.BytesIO()
tmp_yaml_buf = io.TextIOWrapper(raw_buf, encoding)
yaml.dump(config, tmp_yaml_buf, Dumper=yaml.RoundTripDumper)
# 4. Overwrite the original app.yaml
with files.BinaryFileWriter(config_filename) as yaml_file:
tmp_yaml_buf.seek(0)
yaml_file.write(raw_buf.getvalue())
except Exception as e:
raise fingerprinter.AlterConfigFileError(e)
_DETAILED_HELP = {
'DESCRIPTION': """\
This command generates all relevant config files (app.yaml, Dockerfile and a
build Dockerfile) for your application in the current directory or emits an
error message if the source directory contents are not recognized.
""",
'EXAMPLES': """\
To generate configs for the current directory:
$ {command}
To generate configs for ~/my_app:
$ {command} ~/my_app
"""
}
GenConfig.detailed_help = _DETAILED_HELP

View File

@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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 gen_repo_info_file command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
import os
from googlecloudsdk.appengine.tools import context_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
import six
@base.Hidden
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class GenRepoInfoFile(base.Command):
"""[DEPRECATED] Saves repository information in a file.
DEPRECATED, use `gcloud beta debug source gen-repo-info-file` instead. The
generated file is an opaque blob representing which source revision the
application was built at, and which Google-hosted repository this revision
will be pushed to.
"""
detailed_help = {
'DESCRIPTION': """\
DEPRECATED, use `gcloud beta debug source gen-repo-info-file`
instead.
This command generates a {name} file, containing information on the
source revision and remote repository associated with the given
source directory.
{name} contains information on the remote repository: the associated
Cloud Repository if there is one, or the remote Git repository if
there is no Cloud Repository.
""".format(name=context_util.CONTEXT_FILENAME),
'EXAMPLES': """\
To generate repository information file for your app,
from your source directory run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'--source-directory',
default='.',
help='The path to directory containing the source code for the build.')
# TODO(b/25215149) Remove this option.
parser.add_argument(
'--output-file',
help=(
'(Deprecated; use --output-directory instead.) '
'Specifies the full name of the output file to contain a single '
'source context. The file name must be "{name}" in '
'order to work with cloud diagnostic tools.').format(
name=context_util.CONTEXT_FILENAME))
parser.add_argument(
'--output-directory',
default='',
help=(
'The directory in which to create the source context file. '
'Defaults to the current directory, or the directory containing '
'--output-file if that option is provided with a file name that '
'includes a directory path.'))
def Run(self, args):
if six.PY3:
raise exceptions.NotSupportedPy3Exception(
'This command does not support python3.')
log.warning('This command is deprecated. Please use '
'`gcloud beta source debug gen-repo-info-file` instead.')
contexts = context_util.CalculateExtendedSourceContexts(
args.source_directory)
# First create the old-style source-context.json file
if args.output_file:
log.warning(
'The --output-file option is deprecated and will soon be removed.')
output_directory = os.path.dirname(args.output_file)
output_file = args.output_file
else:
output_directory = ''
output_file = context_util.CONTEXT_FILENAME
if not output_directory:
if args.output_directory:
output_directory = args.output_directory
output_file = os.path.join(output_directory, output_file)
else:
output_directory = '.'
best_context = context_util.BestSourceContext(contexts)
files.MakeDir(output_directory)
files.WriteFileContents(
output_file, json.dumps(best_context, indent=2, sort_keys=True))

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)

View File

@@ -0,0 +1,37 @@
# -*- 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 logs 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 Logs(base.Group):
"""Manage your App Engine logs.
This set of commands can be used to view your existing App Engine logs.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To read the logs for the current App Engine project, run:
$ {command} read
""",
}

View File

@@ -0,0 +1,101 @@
# -*- 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.
"""app logs read command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import logs_util
from googlecloudsdk.api_lib.logging import common
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
class Read(base.Command):
"""Reads log entries for the current App Engine app."""
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.SERVICE.AddToParser(parser)
flags.VERSION.AddToParser(parser)
flags.LEVEL.AddToParser(parser)
flags.LOGS.AddToParser(parser)
parser.add_argument('--limit', required=False, type=int,
default=200, help='Number of log entries to show.')
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
The list of log entries.
"""
printer = logs_util.LogPrinter()
printer.RegisterFormatter(logs_util.FormatRequestLogEntry)
printer.RegisterFormatter(logs_util.FormatNginxLogEntry)
printer.RegisterFormatter(logs_util.FormatAppEntry)
project = properties.VALUES.core.project.Get(required=True)
filters = logs_util.GetFilters(project, args.logs, args.service,
args.version, args.level)
lines = []
# pylint: disable=g-builtin-op, For the .keys() method
for entry in common.FetchLogs(log_filter=' AND '.join(filters),
order_by='DESC',
limit=args.limit):
lines.append(printer.Format(entry))
for line in reversed(lines):
log.out.Print(line)
Read.detailed_help = {
'DESCRIPTION': """\
Display the latest log entries from stdout, stderr and crash log for the
current Google App Engine app in a human readable format. This command
requires that the caller have the logging.logEntries.list
permission.
""",
'EXAMPLES': """\
To display the latest entries for the current app, run:
$ {command}
To show only the entries with severity at `warning` or higher, run:
$ {command} --level=warning
To show only the entries with a specific version, run:
$ {command} --version=v1
To show only the 10 latest log entries for the default service, run:
$ {command} --limit=10 --service=default
To show only the logs from the request log for standard apps, run:
$ {command} --logs=request_log
To show only the logs from the request log for Flex apps, run:
$ {command} --logs=nginx.request
""",
}

View File

@@ -0,0 +1,75 @@
# -*- 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.
"""app logs tail command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app import logs_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.command_lib.logs import stream
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Tail(base.Command):
"""Streams logs for App Engine apps."""
detailed_help = {
'EXAMPLES': """\
To stream logs from a serving app, run:
$ {command}
To show only logs with a specific service, version, and level, run:
$ {command} --service=s1 --version=v1 --level=warning
To show only the logs from the request log for Standard apps, run:
$ {command} --logs=request_log
To show only the logs from the request log for Flex apps, run:
$ {command} --logs=nginx.request
"""
}
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.SERVICE.AddToParser(parser)
flags.VERSION.AddToParser(parser)
flags.LEVEL.AddToParser(parser)
flags.LOGS.AddToParser(parser)
def Run(self, args):
printer = logs_util.LogPrinter()
printer.RegisterFormatter(logs_util.FormatRequestLogEntry)
printer.RegisterFormatter(logs_util.FormatNginxLogEntry)
printer.RegisterFormatter(logs_util.FormatAppEntry)
project = properties.VALUES.core.project.Get(required=True)
filters = logs_util.GetFilters(project, args.logs, args.service,
args.version, args.level)
log.status.Print('Waiting for new log entries...')
log_fetcher = stream.LogFetcher(filters=filters,
polling_interval=1,
num_prev_entries=100)
for log_entry in log_fetcher.YieldLogs():
log.out.Print(printer.Format(log_entry))

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.migrate group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.Hidden
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Migrate(base.Group):
"""Converts first-generation Python App Engine applications to second-generation."""
category = base.APP_ENGINE_CATEGORY

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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 migrate gen1-to-gen2 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.command_lib.app import migration_util
@base.DefaultUniverseOnly
class Gen1ToGen2(base.Command):
"""Migrate the first-generation App Engine code to be compatible with second-generation runtimes."""
@staticmethod
def Args(parser):
parser.add_argument(
'--appyaml',
help=(
'YAML file for the first-generation App Engine version to be'
' migrated.'
),
)
parser.add_argument(
'--output-dir',
required=True,
help=(
'The directory where the migrated code for the second-generation'
' application will be stored.'
),
)
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
migration_util.Gen1toGen2Migration(api_client, args).StartMigration()

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.migrate_config group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class MigrateConfig(base.Group):
"""Convert configuration files from one format to another.
Automated one-time migration tooling for helping with transition of
configuration from one state to another. Currently exclusively
provides commands for converting datastore-indexes.xml, queue.xml, cron.xml
and dispatch.xml to their yaml counterparts.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To convert a cron.xml to cron.yaml, run:
$ {command} cron-xml-to-yaml my/app/WEB-INF/cron.xml
""",
}

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 migrate-config cron-xml-to-yaml command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import migrate_config
class CronXmlToYaml(base.Command):
"""Convert a cron.xml file to cron.yaml."""
@staticmethod
def Args(parser):
parser.add_argument(
'xml_file',
help='Path to the cron.xml file.')
def Run(self, args):
src = os.path.abspath(args.xml_file)
dst = os.path.join(os.path.dirname(src), 'cron.yaml')
entry = migrate_config.REGISTRY['cron-xml-to-yaml']
migrate_config.Run(entry, src=src, dst=dst)
CronXmlToYaml.detailed_help = {
'brief': migrate_config.REGISTRY['cron-xml-to-yaml'].description
}

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 migrate-config datastore-indexes-xml-to-yaml command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import migrate_config
class DatastoreIndexesXmlToYaml(base.Command):
"""Convert a datastore-indexes.xml file to index.yaml."""
@staticmethod
def Args(parser):
parser.add_argument(
'xml_file',
help='Path to the datastore-indexes.xml file.')
parser.add_argument(
'--generated-indexes-file',
help=('If specified, include the auto-generated xml file too, and '
'merge the resulting entries appropriately. Note that this file '
'is usually named '
'`WEB-INF/appengine-generated/datastore-indexes-auto.xml`.'))
def Run(self, args):
src = os.path.abspath(args.xml_file)
dst = os.path.join(os.path.dirname(src), 'index.yaml')
auto_src = None
if args.generated_indexes_file:
auto_src = os.path.abspath(args.generated_indexes_file)
entry = migrate_config.REGISTRY['datastore-indexes-xml-to-yaml']
migrate_config.Run(entry, src=src, dst=dst, auto_src=auto_src)
DatastoreIndexesXmlToYaml.detailed_help = {
'brief': (migrate_config.REGISTRY['datastore-indexes-xml-to-yaml']
.description)
}

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 migrate-config dispatch-xml-to-yaml command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import migrate_config
class DispatchXmlToYaml(base.Command):
"""Convert a dispatch.xml file to dispatch.yaml."""
@staticmethod
def Args(parser):
parser.add_argument(
'xml_file',
help='Path to the dispatch.xml file.')
def Run(self, args):
src = os.path.abspath(args.xml_file)
dst = os.path.join(os.path.dirname(src), 'dispatch.yaml')
entry = migrate_config.REGISTRY['dispatch-xml-to-yaml']
migrate_config.Run(entry, src=src, dst=dst)
DispatchXmlToYaml.detailed_help = {
'brief': migrate_config.REGISTRY['dispatch-xml-to-yaml'].description
}

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 migrate-config queue-xml-to-yaml command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import migrate_config
class QueueXmlToYaml(base.Command):
"""Convert a queue.xml file to queue.yaml."""
@staticmethod
def Args(parser):
parser.add_argument(
'xml_file',
help='Path to the queue.xml file.')
def Run(self, args):
src = os.path.abspath(args.xml_file)
dst = os.path.join(os.path.dirname(src), 'queue.yaml')
entry = migrate_config.REGISTRY['queue-xml-to-yaml']
migrate_config.Run(entry, src=src, dst=dst)
QueueXmlToYaml.detailed_help = {
'brief': migrate_config.REGISTRY['queue-xml-to-yaml'].description
}

View File

@@ -0,0 +1,239 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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 migrate-to-run command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import collections
import re
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.run import k8s_object
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import gae_to_cr_migration_util
from googlecloudsdk.command_lib.app.gae_to_cr_migration_util import list_incompatible_features
from googlecloudsdk.command_lib.app.gae_to_cr_migration_util import translate
from googlecloudsdk.command_lib.run import config_changes
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from surface.run import deploy
@base.Hidden
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AppEngineToCloudRun(deploy.AlphaDeploy):
"""Migrate a second-generation App Engine app to Cloud Run."""
detailed_help = {
'DESCRIPTION': """\
Migrates the second-generation App Engine app to Cloud Run.
""",
'EXAMPLES': """\
To migrate an App Engine app to Cloud Run:\n
through app.yaml\n
gcloud app migrate-to-run --appyaml=path/to/app.yaml --entrypoint=main\n
OR\n
through service and version\n
gcloud app migrate-to-run --service=default --version=v1 --entrypoint=main\n
""",
}
@classmethod
def Args(cls, parser):
super().Args(parser)
parser.add_argument(
'--appyaml',
help=(
'YAML file for the second generation App Engine version to be'
' migrated.'
),
)
parser.add_argument(
'--service',
help='service name that is deployed in App Engine',
)
parser.add_argument(
'--version',
help='version name that is deployed in App Engine',
)
parser.add_argument(
'--entrypoint',
help='entrypoint required for some runtimes',
)
def Run(self, args):
"""Overrides the Deploy.Run method, applying the wrapper logic for FlagIsExplicitlySet."""
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
gae_to_cr_migration_util.GAEToCRMigrationUtil(api_client, args)
self.release_track = self.ReleaseTrack()
original_flag_is_explicitly_set = flags.FlagIsExplicitlySet
try:
flags.FlagIsExplicitlySet = self._flag_is_explicitly_set_wrapper
self.StartMigration(args)
# Execute the gcloud run deploy command using the arguments prepared in
# StartMigration.
super().Run(args)
self.PrintMigrationSummary(args)
finally:
flags.FlagIsExplicitlySet = original_flag_is_explicitly_set
def _flag_is_explicitly_set_wrapper(self, args, flag):
"""Wrapper function to check if a flag is explicitly set.
This wrapper checks for flags added during the migration process,
in addition to the original flags.FlagIsExplicitlySet check.
Args:
args: The arguments to check.
flag: The flag to check.
Returns:
bool: True if the flag is explicitly set, False otherwise.
"""
return hasattr(self, '_migration_flags') and flag in self._migration_flags
def _GetBaseChanges(self, args):
"""Returns the service config changes with some default settings."""
changes = flags.GetServiceConfigurationChanges(args, self.ReleaseTrack())
changes.insert(
0,
config_changes.DeleteAnnotationChange(
k8s_object.BINAUTHZ_BREAKGLASS_ANNOTATION
),
)
changes.append(
config_changes.SetLaunchStageAnnotationChange(base.ReleaseTrack.ALPHA)
)
return changes
def StartMigration(self, args) -> None:
"""Starts the migration process."""
# List incompatible features.
list_incompatible_features.list_incompatible_features(
args.appyaml, args.service, args.version
)
# Translate app.yaml to gcloud run deploy flags.
cloud_run_deploy_command = translate.translate(
args.appyaml, args.service, args.version, args.entrypoint
)
print_deploy_command = ''
for command_str in cloud_run_deploy_command:
if command_str.startswith('--labels'):
command_str = '--labels=migrated-from=app-engine,migration-tool=gcloud-app-migrate-standard-v1'
print_deploy_command += command_str + ' '
if args.entrypoint:
setattr(
args,
'set-build-env-vars',
{'GOOGLE_ENTRYPOINT': args.entrypoint},
)
print_deploy_command += (
' --set-build-env-vars GOOGLE_ENTRYPOINT=' + args.entrypoint
)
# Update args with the translated gcloud run deploy flags
log.status.Print('Command to run:', print_deploy_command, '\n')
setattr(args, 'SERVICE', cloud_run_deploy_command[3])
self._migration_flags = []
for command_str in cloud_run_deploy_command:
if command_str.startswith('--'):
command_str = command_str.replace('--', '')
# TODO: b/445905035 - Use ArgDict type for args to simplify the parsing
# logic
command_args = command_str.split('=')
command_args[0] = command_args[0].replace('-', '_')
self._migration_flags.append(command_args[0])
if command_args[0] == 'labels':
args.__setattr__(
command_args[0],
{
'migrated-from': 'app-engine',
'migration-tool': 'gcloud-app-migrate-standard-v1',
},
)
continue
if command_args[0] == 'set_env_vars':
args.__setattr__(command_args[0], self.ParseSetEnvVars(command_str))
continue
if command_args[0] == 'timeout':
if command_args[1] == '600':
args.__setattr__(command_args[0], 600)
elif command_args[1] == '3600':
args.__setattr__(command_args[0], 3600)
continue
if command_args[0] == 'min_instances':
args.__setattr__(command_args[0], flags.ScaleValue(command_args[1]))
continue
if command_args[0] == 'max_instances':
args.__setattr__(command_args[0], flags.ScaleValue(command_args[1]))
continue
if len(command_args) > 1:
args.__setattr__(command_args[0], command_args[1])
else:
args.__setattr__(command_args[0], True)
return
def PrintMigrationSummary(self, args):
"""Prints the migration summary."""
log.status.Print(
'\n'
'The code and configuration of your App Engine service has been copied'
' to Cloud Run.'
'\n'
)
region = properties.VALUES.run.region.Get()
service = args.SERVICE or 'default'
project = properties.VALUES.core.project.Get()
log.status.Print(
'View and edit in Cloud Run console:'
f' https://console.cloud.google.com/run/detail/{region}/{service}/metrics?project={project}\nDeploy'
' new versions of your code with the same configuration using "gcloud'
f' run deploy {service} --source=.'
f' --region={region} --project={project}"\n'
)
def ParseSetEnvVars(
self, input_str: str
) -> collections.OrderedDict[str, str]:
"""Parses a 'set-env-vars' string and converts it into an OrderedDict.
Args:
input_str: A string in the format of
'set-env-vars="KEY1=VALUE1,KEY2=VALUE2"'.
Returns:
An OrderedDict containing the environment variables.
"""
match = re.search(r'="([^"]*)"', input_str)
if not match:
return collections.OrderedDict()
vars_string = match.group(1)
if not vars_string:
return collections.OrderedDict()
env_vars = collections.OrderedDict(
pair.split('=', 1) for pair in vars_string.split(',')
)
return env_vars

View File

@@ -0,0 +1,102 @@
# -*- 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 Open Console command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import browser_dispatcher
from googlecloudsdk.core import properties
from six.moves import urllib
CONSOLE_URL = 'https://console.developers.google.com/appengine?{query}'
LOGS_URL = 'https://console.developers.google.com/logs?{query}'
def _CreateDevConsoleURL(project, service='default', version=None, logs=False):
"""Creates a URL to a page in the Developer Console according to the params.
Args:
project: The app engine project id
service: A service belonging to the project
version: A version belonging to the service, or all versions if omitted
logs: If true, go to the log section instead of the dashboard
Returns:
The URL to the respective page in the Developer Console
"""
if service is None:
service = 'default'
query = [('project', project), ('serviceId', service)]
if version:
query.append(('versionId', version))
query_string = urllib.parse.urlencode(query)
return (LOGS_URL if logs else CONSOLE_URL).format(query=query_string)
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class OpenConsole(base.Command):
"""Open the App Engine dashboard, or log viewer, in a web browser.
"""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
Open the App Engine dashboard for the default service:
$ {command}
Open the service specific dashboard view:
$ {command} --service="myService"
Open the version specific dashboard view:
$ {command} --service="myService" --version="v1"
Open the log viewer for the default service:
$ {command} --logs
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'--service',
'-s',
help=('The service to consider. If not specified, use the '
'default service.'))
parser.add_argument(
'--version',
'-v',
help=('The version to consider. If not specified, '
'all versions for the given service are considered.'))
parser.add_argument(
'--logs',
'-l',
action='store_true',
default=False,
help='Open the log viewer instead of the App Engine dashboard.')
def Run(self, args):
project = properties.VALUES.core.project.Get(required=True)
url = _CreateDevConsoleURL(project, args.service, args.version, args.logs)
browser_dispatcher.OpenURL(url)

View File

@@ -0,0 +1,39 @@
# -*- 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.
"""The gcloud app operations 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 Operations(base.Group):
"""View and manage your App Engine Operations.
This set of commands can be used to view and manage your existing App Engine
operations.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To list your App Engine operations, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,43 @@
# -*- 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.
"""The `app operations 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
class Describe(base.DescribeCommand):
"""Describes the operation."""
detailed_help = {
'EXAMPLES': """\
To describe an App Engine operation called o1, run:
$ {command} o1
""",
}
@staticmethod
def Args(parser):
parser.add_argument('operation', help='ID of operation.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return api_client.GetOperation(args.operation)

View File

@@ -0,0 +1,58 @@
# -*- 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.
"""The `app operations 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
class List(base.ListCommand):
"""List the operations."""
detailed_help = {
'EXAMPLES': """\
To list all App Engine operations, run:
$ {command}
To list only 100 App Engine operations, run:
$ {command} --limit=100
To list only pending App Engine operations, run:
$ {command} --pending
""",
}
@staticmethod
def Args(parser):
parser.add_argument('--pending',
action='store_true',
default=False,
help='Only display pending operations')
parser.display_info.AddFormat('table(id, start_time, status)')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
if args.pending:
return api_client.ListOperations(op_filter='done:false')
else:
return api_client.ListOperations()

View File

@@ -0,0 +1,55 @@
# -*- 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.
"""The `app operations wait` 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 operations_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import log
from googlecloudsdk.core.console import progress_tracker
class Wait(base.Command):
"""Polls an operation until completion."""
detailed_help = {
'EXAMPLES': """\
To wait for an App Engine operation called o1 to complete, run:
$ {command} o1
""",
}
@staticmethod
def Args(parser):
parser.add_argument('operation', help='ID of operation.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
operation = api_client.GetOperation(args.operation)
if operation.done:
log.status.Print(
'Operation [{0}] is already done.'.format(args.operation))
return operation
else:
with progress_tracker.ProgressTracker(
'Waiting for operation [{0}] to complete.'.format(args.operation)):
return operations_util.WaitForOperation(
api_client.client.apps_operations, operation)

View File

@@ -0,0 +1,39 @@
# -*- 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.
"""The gcloud app regions group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Regions(base.Group):
"""View regional availability of App Engine runtime environments.
This command can be used to view availability of App Engine standard and
flexible runtime environments in all geographic regions.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To view regional availability of App Engine runtime environments, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,49 @@
# -*- 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.
"""The `app regions 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
class List(base.ListCommand):
"""List the availability of flex and standard environments for each region."""
detailed_help = {
'EXAMPLES': """\
To view regional availability of App Engine runtime environments, run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
region:sort=1,
standard.yesno(yes="YES", no="NO"):label='SUPPORTS STANDARD',
flexible.yesno(yes="YES", no="NO"):label='SUPPORTS FLEXIBLE',
search_api.yesno(yes="YES", no="NO"):label='SUPPORTS GAE SEARCH'
)
""")
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return sorted(api_client.ListRegions(), key=str)

View File

@@ -0,0 +1,52 @@
# -*- 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.
"""`gcloud app repair` 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.console import progress_tracker
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class Repair(base.Command):
"""Restores required App Engine features to the current application.
For example, this command will restore the App Engine staging bucket if it
has been deleted. It will no longer restore the service account, instead, the
IAM service account undelete API must be used for the purpose.
"""
detailed_help = {
'EXAMPLES': """\
To repair the application, run
$ {command}
""",
}
def Run(self, args):
api_client = appengine_api_client.AppengineApiClient.GetApiClient('v1beta')
with progress_tracker.ProgressTracker(
'Repairing the app [{0}]'.format(api_client.project)):
api_client.RepairApplication()

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""View runtimes available to Google App Engine."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Runtimes(base.Group):
"""List runtimes available to Google App Engine."""
category = base.APP_ENGINE_CATEGORY

View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""`gcloud app runtimes 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
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List the available runtimes.
This command lists all the available runtimes and their current stages, for
example,
GA, BETA or END OF SUPPORT.
"""
detailed_help = {
'EXAMPLES': """\
To list all the runtimes in the App Engine standard environment, run:
$ {command} --environment=standard
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'--environment',
required=True,
choices=['standard'],
help='Environment for the application.',
)
parser.display_info.AddFormat("""
table(
name,
stage,
environment
)
""")
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
environment = (
api_client.messages.AppengineAppsListRuntimesRequest.EnvironmentValueValuesEnum.STANDARD
)
if args.environment == 'standard':
environment = (
api_client.messages.AppengineAppsListRuntimesRequest.EnvironmentValueValuesEnum.STANDARD
)
response = api_client.ListRuntimes(environment)
return [Runtime(r) for r in response.runtimes]
class Runtime:
"""Runtimes wrapper for ListRuntimesResponse#Runtimes.
Attributes:
name: A string name of the runtime.
stage: An enum of the release state of the runtime, e.g., GA, BETA, etc.
environment: Environment of the runtime.
"""
def __init__(self, runtime):
self.name = runtime.name
self.stage = runtime.stage
self.environment = runtime.environment

View File

@@ -0,0 +1,44 @@
# -*- 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 services 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 Services(base.Group):
"""View and manage your App Engine services.
This set of commands can be used to view and manage your existing App Engine
services.
To create new deployments, use `{parent_command} deploy`.
For more information on App Engine services, see:
https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'EXAMPLES': """\
To list your deployed services, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,92 @@
# -*- 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 Browse command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import browser_dispatcher
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
class Browse(base.Command):
"""Open the specified service(s) in a browser.
"""
detailed_help = {
'EXAMPLES': """\
To show the url for the default service in the browser, run:
$ {command} default
To show version `v1` of service `myService` in the browser, run:
$ {command} myService --version="v1"
To show multiple services side-by-side, run:
$ {command} default otherService
To show multiple services side-by-side with a specific version, run:
$ {command} s1 s2 --version=v1
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
service:label=SERVICE,
url:label=URL
)
""")
flags.LAUNCH_BROWSER.AddToParser(parser)
parser.add_argument(
'services',
nargs='+',
help="""\
The services to open (optionally filtered by the --version flag).""")
parser.add_argument(
'--version',
'-v',
help="""\
If specified, open services with a given version. If not
specified, use a version based on the service's traffic split .
""")
def Run(self, args):
"""Launch a browser, or return a table of URLs to print."""
project = properties.VALUES.core.project.Get(required=True)
outputs = []
for service in args.services:
outputs.append(browser_dispatcher.BrowseApp(project,
service,
args.version,
args.launch_browser))
if any(outputs):
if args.launch_browser:
# We couldn't find a browser to launch
log.status.Print(
'Did not detect your browser. Go to these links to view your app:')
return outputs
return None

View File

@@ -0,0 +1,76 @@
# -*- 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.
"""`gcloud app services delete` 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 service_util
from googlecloudsdk.api_lib.app import version_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import text
from six import moves
class Delete(base.DeleteCommand):
"""Delete services in the current project."""
detailed_help = {
'EXAMPLES': """\
To delete a service (and all of its accompanying versions) in the
current project, run:
$ {command} service1
To delete multiple services (and all of their accompanying versions)
in the current project, run:
$ {command} service1 service2
""",
}
@staticmethod
def Args(parser):
parser.add_argument('services', nargs='+', help='The service(s) to delete.')
parser.add_argument(
'--version', help='Delete a specific version of the given service(s).')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
# Why do this? It lets us know if we're missing services up front (fail
# fast), and we get to control the error messages
all_services = api_client.ListServices()
services = service_util.GetMatchingServices(all_services, args.services)
if args.version:
console_io.PromptContinue(
'Deleting version [{0}] of {1} [{2}].'.format(
args.version, text.Pluralize(len(services), 'service'),
', '.join(moves.map(str, services))),
cancel_on_no=True)
versions = [version_util.Version(api_client.project, s.id, args.version)
for s in services]
version_util.DeleteVersions(api_client, versions)
else:
console_io.PromptContinue(
'Deleting {0} [{1}].'.format(
text.Pluralize(len(services), 'service'),
', '.join(moves.map(str, services))),
cancel_on_no=True)
service_util.DeleteServices(api_client, services)

View File

@@ -0,0 +1,46 @@
# -*- 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.
"""The `app services 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
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Describe(base.Command):
"""Display all data about an existing service."""
detailed_help = {
'EXAMPLES': """\
To show all the data about service s1, run
$ {command} s1
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'service',
help='The service to describe.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return api_client.GetServiceResource(args.service)

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.
"""`gcloud app services 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
class List(base.ListCommand):
"""List your existing services.
This command lists all services that are currently deployed to the App Engine
server.
"""
detailed_help = {
'EXAMPLES': """\
To list all services in the current project, run:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
id:label=SERVICE:sort=1,
versions.len():label=NUM_VERSIONS
)
""")
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
services = api_client.ListServices()
versions = api_client.ListVersions(services)
result = []
for service in services:
versions_for_service = [v for v in versions if v.service == service.id]
result.append(
{'id': service.id, 'versions': versions_for_service})
# Sort so the order is deterministic for testing.
return sorted(result, key=lambda r: r['id'])

View File

@@ -0,0 +1,135 @@
# -*- 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.
"""`gcloud app services set-traffic` 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 operations_util
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.resource import resource_printer
import six
class TrafficSplitError(exceptions.Error):
"""Errors occurring when setting traffic splits."""
pass
class SetTraffic(base.Command):
"""Set traffic splitting settings.
This command sets the traffic split of versions across a service or a project.
"""
detailed_help = {
'EXAMPLES': """\
To send all traffic to 'v2' of service 's1', run:
$ {command} s1 --splits=v2=1
To split traffic evenly between 'v1' and 'v2' of service 's1', run:
$ {command} s1 --splits=v2=.5,v1=.5
To split traffic across all services:
$ {command} --splits=v2=.5,v1=.5
""",
}
@staticmethod
def Args(parser):
parser.add_argument('services', nargs='*', help=(
'The services to modify.'))
parser.add_argument(
'--splits',
required=True,
type=arg_parsers.ArgDict(min_length=1),
help="""\
Key-value pairs describing what proportion of traffic should go to
each version. The split values are added together and used as
weights. The exact values do not matter, only their relation to each
other. For example, v1=2,v2=2 is equivalent to v1=.5,v2=.5""")
parser.add_argument(
'--split-by',
choices=['cookie', 'ip', 'random'],
default='ip',
help='Whether to split traffic based on cookie, IP address or random.')
parser.add_argument(
'--migrate',
action='store_true',
default=False,
help="""\
The migrate flag determines whether or not to use traffic migration
during the operation. Traffic migration will attempt to automatically
migrate traffic from the previous version to the new version, giving
the autoscaler time to respond. See the documentation here:
[](https://cloud.google.com/appengine/docs/python/console/trafficmigration)
for more information.""")
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.GetApiClientForTrack(self.ReleaseTrack())
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 six.iteritems(allocations):
display_allocations.append('{0}/{1}/{2}: {3}'.format(
api_client.project,
service.id,
version,
split))
fmt = 'list[title="Setting the following traffic allocation:"]'
resource_printer.Print(display_allocations, fmt, out=log.status)
log.status.Print(
'NOTE: Splitting traffic by {0}.'.format(args.split_by))
log.status.Print('Any other versions of the specified service will '
'receive zero traffic.')
console_io.PromptContinue(cancel_on_no=True)
errors = {}
for service in services:
try:
operations_util.CallAndCollectOpErrors(
api_client.SetTrafficSplit, service.id, allocations,
args.split_by.upper(), args.migrate)
except operations_util.MiscOperationError as err:
errors[service.id] = six.text_type(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(list(printable_errors.keys()))) +
'\n\n'.join(list(printable_errors.values())))

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 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.
"""`gcloud app services update` 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 operations_util
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
import six
class IngressSettingError(exceptions.Error):
"""Errors occurring when setting ingress settings."""
pass
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class Update(base.Command):
"""Update service-level settings.
Update ingress traffic settings for an app.
"""
detailed_help = {
'EXAMPLES': """To update ingress traffic settings for """
"""the default service, run:
$ {command} default --ingress=internal-only
""",
}
@staticmethod
def Args(parser):
parser.add_argument('services', nargs='*', help=('The services to modify.'))
parser.add_argument(
'--ingress',
choices=['all', 'internal-only', 'internal-and-cloud-load-balancing'],
default='all',
required=True,
help='Control what traffic can reach the app.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
all_services = api_client.ListServices()
services = service_util.GetMatchingServices(all_services, args.services)
errors = {}
for service in services:
ingress_traffic_allowed = (
api_client.messages.NetworkSettings
.IngressTrafficAllowedValueValuesEnum.INGRESS_TRAFFIC_ALLOWED_ALL)
if args.ingress == 'internal-only':
ingress_traffic_allowed = (
api_client.messages.NetworkSettings
.IngressTrafficAllowedValueValuesEnum
.INGRESS_TRAFFIC_ALLOWED_INTERNAL_ONLY)
elif args.ingress == 'internal-and-cloud-load-balancing':
ingress_traffic_allowed = (
api_client.messages.NetworkSettings
.IngressTrafficAllowedValueValuesEnum
.INGRESS_TRAFFIC_ALLOWED_INTERNAL_AND_LB)
try:
operations_util.CallAndCollectOpErrors(
api_client.SetIngressTrafficAllowed, service.id,
ingress_traffic_allowed)
except operations_util.MiscOperationError as err:
errors[service.id] = six.text_type(err)
if errors:
combined_error_msg = 'Error updating service(s): '
for service, error_msg in errors.items():
combined_error_msg += '\n- %s\n %s' % (service, error_msg)
raise IngressSettingError(combined_error_msg + '\n\n')

View File

@@ -0,0 +1,43 @@
# -*- 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 gcloud app ssl-certificates group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA,
base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class SslCertificates(base.Group):
"""View and manage your App Engine SSL certificates.
This set of commands can be used to view and manage your app's
SSL certificates.
"""
category = base.APP_ENGINE_CATEGORY
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list your App Engine SSL certificates, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,58 @@
# -*- 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.
"""Surface for uploading an SSL certificate to an App Engine app."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_ssl_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
class Create(base.CreateCommand):
"""Uploads a new SSL certificate.
The user must be the verified owner of the certificate domain(s). Use the
gcloud domains command group to manage domain ownership and verification.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To add a new SSL certificate to App Engine, run:
$ {command} --display-name='example cert' \
--certificate='/home/user/me/my_cert.cer' \
--private-key='/home/user/me/my_key.pfx'
""",
}
@staticmethod
def Args(parser):
flags.AddSslCertificateFlags(parser, required=True)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
cert = client.CreateSslCertificate(
args.display_name,
cert_path=args.certificate,
private_key_path=args.private_key)
log.CreatedResource(cert.id)
return cert

View File

@@ -0,0 +1,53 @@
# -*- 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.
"""Surface for deleting an SSL certificate from an App Engine app."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_ssl_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
class Delete(base.DeleteCommand):
"""Deletes an SSL certificate."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To delete an App Engine SSL certificate, run:
$ {command} 1234
""",
}
@staticmethod
def Args(parser):
flags.CERTIFICATE_ID_FLAG.AddToParser(parser)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
console_io.PromptContinue(
prompt_string=('Deleting certificate [{0}]'.format(args.id)),
cancel_on_no=True)
client.DeleteSslCertificate(args.id)
log.DeletedResource(args.id)

View File

@@ -0,0 +1,46 @@
# -*- 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.
"""Surface for retrieving a single SSL certificate for an App Engine app."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_ssl_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
class Describe(base.DescribeCommand):
"""Describes a specified SSL certificate."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To describe an App Engine SSL certificate, run:
$ {command} 1234
""",
}
@staticmethod
def Args(parser):
flags.CERTIFICATE_ID_FLAG.AddToParser(parser)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
return client.GetSslCertificate(args.id)

View File

@@ -0,0 +1,73 @@
# -*- 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.
"""Surface for listing all SSL certificates for an App Engine app."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_ssl_api_client as api_client
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""Lists the SSL certificates."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list all App Engine SSL certificates, run:
$ {command}
This will return certificates mapped to domain-mappings for the
current app as well as all certificates that apply to domains which
the current user owns.
To view your owned domains, run `gcloud domains list-user-verified`.
""",
}
def Run(self, args):
return api_client.GetApiClientForTrack(
self.ReleaseTrack()).ListSslCertificates()
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
id:sort=1,
display_name,
domain_names.list()
)
""")
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class ListBeta(List):
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
id:sort=1,
display_name,
domain_names.list(),
managed_certificate.status:label=MANAGED_CERTIFICATE_STATUS
)
""")

View File

@@ -0,0 +1,53 @@
# -*- 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.
"""Surface for updating an SSL certificate for an App Engine app."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.app.api import appengine_ssl_api_client as api_client
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
class Update(base.UpdateCommand):
"""Updates an SSL certificate."""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To update an App Engine SSL certificate, run:
$ {command} 1234 --display-name='updated name' \
--certificate='/home/user/me/new_cert.cer' \
--private-key='/home/user/me/new_key.pfx'
""",
}
@staticmethod
def Args(parser):
flags.CERTIFICATE_ID_FLAG.AddToParser(parser)
flags.AddSslCertificateFlags(parser, required=False)
def Run(self, args):
client = api_client.GetApiClientForTrack(self.ReleaseTrack())
ssl_cert = client.UpdateSslCertificate(args.id, args.display_name,
args.certificate, args.private_key)
log.UpdatedResource(args.id)
return ssl_cert

View File

@@ -0,0 +1,81 @@
# -*- 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.
"""The `app update` command."""
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import update_util
_DETAILED_HELP = {
'brief': 'Updates an App Engine application.',
'DESCRIPTION': """
This command is used to update settings on an app engine application.
""",
'EXAMPLES': """
To enable split health checks on an application:
$ {command} --split-health-checks
To update the app-level service account on an application:
$ {command} --service-account=SERVICE_ACCOUNT
To update the app-level minimum SSL policy of the application:
$ {command} --ssl-policy=TLS_VERSION_1_2
""",
}
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class UpdateGa(base.UpdateCommand):
"""Updates an App Engine application(GA version)."""
detailed_help = _DETAILED_HELP
@staticmethod
def Args(parser):
update_util.AddAppUpdateFlags(parser)
def Run(self, args):
update_util.PatchApplication(
self.ReleaseTrack(),
split_health_checks=args.split_health_checks,
service_account=args.service_account,
ssl_policy=args.ssl_policy,
)
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA)
class UpdateAlphaAndBeta(base.UpdateCommand):
"""Updates an App Engine application(Alpha and Beta version)."""
detailed_help = _DETAILED_HELP
@staticmethod
def Args(parser):
update_util.AddAppUpdateFlags(parser)
def Run(self, args):
update_util.PatchApplication(
self.ReleaseTrack(),
split_health_checks=args.split_health_checks,
service_account=args.service_account,
ssl_policy=args.ssl_policy,
)

View File

@@ -0,0 +1,43 @@
# -*- 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 versions 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 Versions(base.Group):
"""View and manage your App Engine versions.
This set of commands can be used to view and manage your existing App Engine
versions.
To create new deployments, use `{parent_command} deploy`.
For more information on App Engine versions, see:
https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine
## EXAMPLES
To list your deployed versions, run:
$ {command} list
"""
category = base.APP_ENGINE_CATEGORY

View File

@@ -0,0 +1,83 @@
# -*- 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 Browse command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.app import browser_dispatcher
from googlecloudsdk.command_lib.app import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
class Browse(base.Command):
"""Open the specified versions in a browser."""
detailed_help = {
'EXAMPLES': """\
To show version `v1` for the default service in the browser, run:
$ {command} v1
To show version `v1` of a specific service in the browser, run:
$ {command} v1 --service="myService"
To show multiple versions side-by-side, run:
$ {command} v1 v2 --service="myService"
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""
table(
version:label=VERSION,
url:label=URL
)
""")
flags.LAUNCH_BROWSER.AddToParser(parser)
parser.add_argument(
'versions',
nargs='+',
help="""\
The versions to open (optionally filtered by the --service flag).""")
parser.add_argument('--service', '-s',
help=('If specified, only open versions from the '
'given service. If not specified, use the '
'default service.'))
def Run(self, args):
"""Launch a browser, or return a table of URLs to print."""
project = properties.VALUES.core.project.Get(required=True)
outputs = []
for version in args.versions:
outputs.append(browser_dispatcher.BrowseApp(project,
args.service,
version,
args.launch_browser))
if any(outputs):
if args.launch_browser:
# We couldn't find a browser to launch
log.status.Print(
'Did not detect your browser. Go to these links to view your app:')
return outputs
return None

View File

@@ -0,0 +1,139 @@
# -*- 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 Delete command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import copy
from googlecloudsdk.api_lib.app import appengine_api_client
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.api_lib.app import version_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.resource import resource_printer
from googlecloudsdk.core.util import text
class VersionsDeleteError(exceptions.Error):
"""Errors occurring when deleting versions."""
pass
class Delete(base.DeleteCommand):
"""Delete a specified version.
You cannot delete a version of a service that is currently receiving traffic.
"""
detailed_help = {
'EXAMPLES': """\
To delete a specific version of a specific service, run:
$ {command} --service=myService v1
To delete a named version across all services, run:
$ {command} v1
To delete multiple versions of a specific service, run:
$ {command} --service=myService v1 v2
To delete multiple named versions across all services, run:
$ {command} v1 v2
""",
}
@staticmethod
def Args(parser):
parser.add_argument('versions', nargs='+', help=(
'The versions to delete (optionally filtered by the --service flag).'))
parser.add_argument('--service', '-s',
help=('If specified, only delete versions from the '
'given service.'))
def Run(self, args):
client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
services = client.ListServices()
# If a service is supplied, only list versions for that service
if args.service:
services = [s for s in services if s.id == args.service]
all_versions = client.ListVersions(services)
# Sort versions to make behavior deterministic enough for unit testing.
versions = sorted(version_util.GetMatchingVersions(all_versions,
args.versions,
args.service), key=str)
services_to_delete = []
for service in sorted(services):
service_versions = len(
[v for v in all_versions if v.service == service.id])
versions_to_delete = len([v for v in versions if v.service == service.id])
if service_versions == versions_to_delete and service_versions > 0:
if service.id == 'default':
raise VersionsDeleteError(
'The default service (module) may not be deleted, and must '
'comprise at least one version.'
)
else:
services_to_delete.append(service)
for version in copy.copy(versions):
if version.service == service.id:
versions.remove(version)
for version in versions:
if version.traffic_split:
# TODO(b/32869800): collect info on all versions before raising.
raise VersionsDeleteError(
'Version [{version}] is currently serving {allocation:.2f}% of '
'traffic for service [{service}].\n\n'
'Please move all traffic away via one of the following methods:\n'
' - deploying a new version with the `--promote` argument\n'
' - running `gcloud app services set-traffic`\n'
' - running `gcloud app versions migrate`'.format(
version=version.id,
allocation=version.traffic_split * 100,
service=version.service))
if services_to_delete:
word = text.Pluralize(len(services_to_delete), 'service')
log.warning(
'Requested deletion of all existing versions for the following {0}:'
.format(word))
resource_printer.Print(services_to_delete, 'list', out=log.status)
console_io.PromptContinue(prompt_string=(
'\nYou cannot delete all versions of a service. Would you like to '
'delete the entire {0} instead?').format(word), cancel_on_no=True)
service_util.DeleteServices(client, services_to_delete)
if versions:
fmt = 'list[title="Deleting the following versions:"]'
resource_printer.Print(versions, fmt, out=log.status)
console_io.PromptContinue(cancel_on_no=True)
else:
if not services_to_delete:
log.warning('No matching versions found.')
version_util.DeleteVersions(client, versions)

View File

@@ -0,0 +1,38 @@
# -*- 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.
"""`gcloud app versions 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
class Describe(base.DescribeCommand):
"""Display all data about an existing version."""
@staticmethod
def Args(parser):
parser.add_argument(
'--service', '-s', required=True,
help='The service corresponding to the version to show.')
parser.add_argument('version', help='The ID of the version to show.')
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
return api_client.GetVersionResource(service=args.service,
version=args.version)

View File

@@ -0,0 +1,103 @@
# -*- 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.
"""`gcloud app versions 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.api_lib.app import version_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
class ServiceNotFoundError(exceptions.Error):
pass
class List(base.ListCommand):
"""List your existing versions.
This command lists all the versions of all services that are currently
deployed to the App Engine server.
"""
detailed_help = {
'EXAMPLES': """\
To list all services and versions, run:
$ {command}
To list all versions for a specific service, run:
$ {command} --service=service1
To list only versions that are receiving traffic, run:
$ {command} --hide-no-traffic
To list all version information in JSON, run:
$ {command} --format="json"
To list versions created after a specific date, run:
$ {command} --filter="version.createTime.date('%Y-%m-%d', Z)>'2017-11-03'"
""",
}
@staticmethod
def Args(parser):
parser.add_argument('--service', '-s',
help='Only show versions from this service.')
parser.add_argument('--hide-no-traffic', action='store_true',
help='Only show versions that are receiving traffic.')
parser.display_info.AddFormat("""
table(
service,
version.id:label=VERSION.ID,
traffic_split.format("{0:.2f}", .),
last_deployed_time.date("%Y-%m-%dT%H:%M:%S%Oz", undefined="-")
:label=LAST_DEPLOYED,
version.servingStatus:label=SERVING_STATUS
)
""")
parser.display_info.AddUriFunc(version_util.GetUri)
def Run(self, args):
api_client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
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, key=str)

View File

@@ -0,0 +1,115 @@
# -*- 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.
"""The Migrate 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 operations_util
from googlecloudsdk.api_lib.app import service_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
import six
import six.moves
class VersionsMigrateError(exceptions.Error):
"""Errors when migrating versions."""
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Migrate(base.Command):
"""Migrate traffic from one version to another for a set of services."""
detailed_help = {
'EXAMPLES': """\
This only works for automatically scaled Standard versions.
To migrate from one version to another for all services where there
is a version v2 and shut down the previous version, run:
$ {command} v2
To migrate from one version to another for a specific service, run:
$ {command} v2 --service="s1"
""",
}
@staticmethod
def Args(parser):
parser.add_argument('version', help='The version to migrate to.')
parser.add_argument('--service', '-s',
help='If specified, only migrate versions from the '
'given service.')
def Run(self, args):
client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack())
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 = list(six.moves.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:
operations_util.CallAndCollectOpErrors(
client.SetTrafficSplit, service, allocations, shard_by='ip',
migrate=True)
except (operations_util.MiscOperationError) as err:
errors[service] = six.text_type(err)
if errors:
error_string = ('Issues migrating all traffic of '
'service(s): [{0}]\n\n{1}'.format(
', '.join(list(errors.keys())),
'\n\n'.join(list(errors.values()))))
raise VersionsMigrateError(error_string)

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 Start 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 operations_util
from googlecloudsdk.api_lib.app import version_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.console import progress_tracker
from googlecloudsdk.core.resource import resource_printer
import six
class VersionsStartError(exceptions.Error):
"""Errors occurring when starting versions."""
pass
class Start(base.Command):
"""Start serving specified versions.
This command starts serving the specified versions. It may only be used if the
scaling module for your service has been set to manual.
"""
detailed_help = {
'EXAMPLES': """\
To start a specific version across all services, run:
$ {command} v1
To start multiple named versions across all services, run:
$ {command} v1 v2
To start a single version on a single service, run:
$ {command} --service=servicename v1
To start multiple versions in a single service, run:
$ {command} --service=servicename v1 v2
""",
}
@staticmethod
def Args(parser):
parser.add_argument('versions', nargs='+', help=(
'The versions to start. (optionally filtered by the --service flag).'))
parser.add_argument('--service', '-s',
help=('If specified, only start versions from the '
'given service.'))
def Run(self, args):
# TODO(b/36052475): 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.GetApiClientForTrack(self.ReleaseTrack())
services = api_client.ListServices()
versions = version_util.GetMatchingVersions(
api_client.ListVersions(services),
args.versions, args.service)
if not versions:
log.warning('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, key=str):
try:
with progress_tracker.ProgressTracker('Starting [{0}]'.format(version)):
operations_util.CallAndCollectOpErrors(
api_client.StartVersion, version.service, version.id)
except operations_util.MiscOperationError as err:
errors[version] = six.text_type(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(list(printable_errors.keys()))) +
'\n\n'.join(list(printable_errors.values())))

View File

@@ -0,0 +1,103 @@
# -*- 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 Stop 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 operations_util
from googlecloudsdk.api_lib.app import version_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.console import progress_tracker
from googlecloudsdk.core.resource import resource_printer
import six
class VersionsStopError(exceptions.Error):
"""Errors occurring when stopping versions."""
pass
class Stop(base.Command):
"""Stop serving specified versions.
This command stops serving the specified versions. It may only be used if the
scaling module for your service has been set to manual.
"""
detailed_help = {
'EXAMPLES': """\
To stop a specific version across all services, run:
$ {command} v1
To stop multiple named versions across all services, run:
$ {command} v1 v2
To stop a single version on a single service, run:
$ {command} --service=servicename v1
To stop multiple versions in a single service, run:
$ {command} --service=servicename v1 v2
Note that that last example may be more simply written using the
`services stop` command (see its documentation for details).
""",
}
@staticmethod
def Args(parser):
parser.add_argument('versions', nargs='+', help=(
'The versions to stop (optionally filtered by the --service flag).'))
parser.add_argument('--service', '-s',
help=('If specified, only stop versions from the '
'given service.'))
def Run(self, args):
# TODO(b/36057452): 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.GetApiClientForTrack(self.ReleaseTrack())
services = api_client.ListServices()
versions = version_util.GetMatchingVersions(
api_client.ListVersions(services),
args.versions, args.service)
if versions:
fmt = 'list[title="Stopping the following versions:"]'
resource_printer.Print(versions, fmt, out=log.status)
console_io.PromptContinue(cancel_on_no=True)
else:
log.warning('No matching versions found.')
errors = []
for version in sorted(versions, key=str):
try:
with progress_tracker.ProgressTracker('Stopping [{0}]'.format(version)):
operations_util.CallAndCollectOpErrors(
api_client.StopVersion, version.service, version.id, block=True)
except operations_util.MiscOperationError as err:
errors.append(six.text_type(err))
if errors:
raise VersionsStopError('\n\n'.join(errors))