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,101 @@
# -*- 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.
"""Utilities for flags for `gcloud scheduler` commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
def DescribeCmekConfigResourceFlag(parser):
"""Add flags for CMEK Describe."""
kms_location_arg = base.Argument(
'--location',
required=True,
help="""\
Google Cloud location for the KMS key.
""",
)
kms_location_arg.AddToParser(parser)
def UpdateAndClearCmekConfigResourceFlag(parser):
"""Add flags for CMEK Update."""
kms_key_name_arg = base.Argument(
'--kms-key-name',
help=(
'Fully qualified identifier for the key or just the key ID. The'
' latter requires that the --kms-keyring and --kms-project flags be'
' provided too.'
),
required=True,
)
kms_keyring_arg = base.Argument(
'--kms-keyring',
help="""\
KMS keyring of the KMS key.
""",
)
kms_location_arg = base.Argument(
'--location',
help="""\
Google Cloud location for the KMS key.
""",
)
kms_project_arg = base.Argument(
'--kms-project',
help="""\
Google Cloud project for the KMS key.
""",
)
# UPDATE
cmek_update_group = base.ArgumentGroup(
help='Flags for Updating CMEK Resource key',
)
cmek_update_group.AddArgument(kms_key_name_arg)
cmek_update_group.AddArgument(kms_keyring_arg)
cmek_update_group.AddArgument(kms_project_arg)
# CLEAR
clear_kms_key_name_flag = base.Argument(
'--clear-kms-key',
action='store_true',
required=True,
help=(
'Disables CMEK for Cloud Scheduler in the specified location by'
' clearing the Cloud KMS cryptokey from the Cloud Scheduler project'
' and CMEK configuration.'
),
)
cmek_clear_group = base.ArgumentGroup(
help='Flags for clearing CMEK Resource key.',
)
cmek_clear_group.AddArgument(clear_kms_key_name_flag)
# UPDATE AND CLEAR GROUP.
cmek_clear_update_group = base.ArgumentGroup(
help='Flags for Clearing or Updating CMEK Resource', mutex=True
)
cmek_clear_update_group.AddArgument(cmek_clear_group)
cmek_clear_update_group.AddArgument(cmek_update_group)
kms_location_arg.AddToParser(parser)
cmek_clear_update_group.AddToParser(parser)

View File

@@ -0,0 +1,265 @@
retry_attempts: &retry_attempts
api_field: job.retryConfig.retryCount
arg_name: max-retry-attempts
default: 0
help_text: |
Number of times to retry the request if it fails or times out. Must
be in range 0-5 inclusive. Default is 0.
clearable_retry_attempts:
group:
mutex: true
params:
- *retry_attempts
- arg_name: clear-max-retry-attempts
action: store_true
processor: googlecloudsdk.command_lib.scheduler.util:ClearFlag
help_text: |
Clear the field corresponding to `--max-retry-attempts`.
retry_duration: &retry_duration
api_field: job.retryConfig.maxRetryDuration
arg_name: max-retry-duration
type: googlecloudsdk.core.util.times:ParseDuration
processor: googlecloudsdk.core.util.times:FormatDurationForJson
help_text: |
Time limit for retrying a failed job, measured from when the job was
first run. If specified with `--max-retry-attempts` greater than 0, the
job will be retried until both limits are reached. Default is 0 seconds
(which means unlimited); however, if `--max-retry-attempts` is also 0, a
job attempt won't be retried if it fails.
clearable_retry_duration:
group:
mutex: true
params:
- *retry_duration
- arg_name: clear-max-retry-duration
action: store_true
processor: googlecloudsdk.command_lib.scheduler.util:ClearFlag
help_text: |
Clear the field corresponding to `--max-retry-duration`.
min_backoff: &min_backoff
api_field: job.retryConfig.minBackoffDuration
arg_name: min-backoff
type: googlecloudsdk.core.util.times:ParseDuration
processor: googlecloudsdk.core.util.times:FormatDurationForJson
default: 5s
help_text: |
Minimum amount of time to wait before retrying a job after it
fails. For example, `10s`. Default is `5s`.
clearable_min_backoff:
group:
mutex: true
params:
- *min_backoff
- arg_name: clear-min-backoff
action: store_true
processor: googlecloudsdk.command_lib.scheduler.util:ClearFlag
help_text: |
Clear the field corresponding to `--min-backoff`.
max_backoff: &max_backoff
api_field: job.retryConfig.maxBackoffDuration
arg_name: max-backoff
default: 3600s
type: googlecloudsdk.core.util.times:ParseDuration
processor: googlecloudsdk.core.util.times:FormatDurationForJson
help_text: |
Maximum amount of time to wait before retrying a job after it
fails. For example, `60s`. Default is `3600s` (1 hour).
clearable_max_backoff:
group:
mutex: true
params:
- *max_backoff
- arg_name: clear-max-backoff
action: store_true
processor: googlecloudsdk.command_lib.scheduler.util:ClearFlag
help_text: |
Clear the field corresponding to `--max-backoff`.
max_doublings: &max_doublings
api_field: job.retryConfig.maxDoublings
arg_name: max-doublings
default: 5
help_text: |
Maximum number of times that the interval between failed job
retries will be doubled before the increase becomes constant.
clearable_max_doublings:
group:
mutex: true
params:
- *max_doublings
- arg_name: clear-max-doublings
action: store_true
processor: googlecloudsdk.command_lib.scheduler.util:ClearFlag
help_text: |
Clear the field corresponding to `--max-doublings`.
schedule:
api_field: job.schedule
arg_name: schedule
help_text: |
Schedule on which the job will be executed.
As a general rule, execution `n + 1` of a job will not begin until
execution `n` has finished. Cloud Scheduler will never allow two
simultaneously outstanding executions. For example, this implies that if
the `n+1` execution is scheduled to run at `16:00` but the `n`
execution takes until `16:15`, the `n+1` execution will not start
until `16:15`. A scheduled start time will be delayed if the previous
execution has not ended when its scheduled time occurs. Learn more about the
[cron job format](https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules).
If `--retry-count` > 0 and a job attempt fails, the job will be tried a
total of `--retry-count` times, with exponential backoff, until the job
succeeds or the number of retries is exhausted. Note that the next
scheduled execution time might be skipped if the retries continue
through that time. For more information, see
[Retry jobs](https://cloud.google.com/scheduler/docs/configuring/retry-jobs).
timezone: &timezone
api_field: job.timeZone
arg_name: time-zone
default: Etc/UTC
help_text: |
Specifies the time zone to be used in interpreting --schedule. The value
of this field must be a time zone name from the tz database
(https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
Note that some time zones include a provision for
daylight savings time. The rules for daylight saving time are
determined by the chosen time zone.
For UTC use the string "utc". Default is "utc".
clearable_timezone:
group:
mutex: true
params:
- *timezone
- arg_name: clear-time-zone
action: store_true
processor: googlecloudsdk.command_lib.scheduler.util:ClearFlag
help_text: |
Clear the field corresponding to `--time-zone`.
attempt_deadline:
api_field: job.attemptDeadline
arg_name: attempt-deadline
type: googlecloudsdk.core.util.times:ParseDuration
processor: googlecloudsdk.core.util.times:FormatDurationForJson
help_text: |
The deadline for job attempts. If the request handler doesn't respond by this dealine,
the request is cancelled and the attempt is marked as failed. For example, 20s.
description:
api_field: job.description
arg_name: description
help_text: |
Human-readable description of the job.
pubsub_topic:
api_field: job.pubsubTarget.topicName
arg_name: topic
type: googlecloudsdk.command_lib.util.hooks.types:Resource:collection=pubsub.projects.topics
processor: googlecloudsdk.command_lib.util.hooks.processors:RelativeName
help_text: |
Name of the Google Cloud Pub/Sub topic to publish to when the job runs.
auth_tokens:
group:
help_text: |
How the request sent to the target when executing the job should be
authenticated.
mutex: true
params:
- group:
help_text: OpenId Connect
params:
- api_field: job.httpTarget.oidcToken.serviceAccountEmail
arg_name: oidc-service-account-email
required: true
help_text: |
The service account email to be used for generating an OpenId
Connect token to be included in the request sent to the target
when executing the job. The service account must be within the
same project as the job. The caller must have
iam.serviceAccounts.actAs permission for the service account.
- api_field: job.httpTarget.oidcToken.audience
arg_name: oidc-token-audience
help_text: |
The audience to be used when generating an OpenId Connect token
to be included in the request sent to the target when executing
the job. If not specified, the URI specified in target will be
used.
- group:
help_text: OAuth2
params:
- api_field: job.httpTarget.oauthToken.serviceAccountEmail
arg_name: oauth-service-account-email
required: true
help_text: |
The service account email to be used for generating an OAuth2
access token to be included in the request sent to the target
when executing the job. The service account must be within the
same project as the job. The caller must have
iam.serviceAccounts.actAs permission for the service account.
- api_field: job.httpTarget.oauthToken.scope
arg_name: oauth-token-scope
help_text: |
The scope to be used when generating an OAuth2 access token to
be included in the request sent to the target when executing the
job. If not specified,
"https://www.googleapis.com/auth/cloud-platform" will be used.
auth_token_openid:
group:
help_text: OpenId Connect
params:
- api_field: job.httpTarget.oidcToken.serviceAccountEmail
arg_name: oidc-service-account-email
required: true
help_text: |
The service account email to be used for generating an OpenId
Connect token to be included in the request sent to the target
when executing the job. The service account must be within the
same project as the job. The caller must have
iam.serviceAccounts.actAs permission for the service account. The
OIDC token is generally used *except* for Google APIs hosted on
`*.googleapis.com`: these APIs expect an OAuth token.
- api_field: job.httpTarget.oidcToken.audience
arg_name: oidc-token-audience
help_text: |
The audience to be used when generating an OpenId Connect token
to be included in the request sent to the target when executing
the job. If not specified, the URI specified in target will be
used.
auth_token_oauth:
group:
help_text: OAuth2
params:
- api_field: job.httpTarget.oauthToken.serviceAccountEmail
arg_name: oauth-service-account-email
required: true
help_text: |
The service account email to be used for generating an OAuth2
access token to be included in the request sent to the target
when executing the job. The service account must be within the
same project as the job. The caller must have
iam.serviceAccounts.actAs permission for the service account. The
token must be OAuth if the target is a Google APIs service
with URL `*.googleapis.com`.
- api_field: job.httpTarget.oauthToken.scope
arg_name: oauth-token-scope
help_text: |
The scope to be used when generating an OAuth2 access token to
be included in the request sent to the target when executing the
job. If not specified,
"https://www.googleapis.com/auth/cloud-platform" will be used.

View File

@@ -0,0 +1,75 @@
# -*- 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.
"""Utilities for parsing arguments to `gcloud scheduler` commands."""
import re
from googlecloudsdk.core import properties
_PROJECT = properties.VALUES.core.project.GetOrFail
def ParseFullKmsKeyName(kms_key_name):
"""Parses and retrieves the segments of a full KMS key name."""
if not kms_key_name:
return None
match = re.match(
r'projects\/(?P<project>.*)\/locations\/(?P<location>.*)\/keyRings\/(?P<keyring>.*)\/cryptoKeys\/(?P<key>.*)',
kms_key_name,
)
if match:
return [
match.group('project'),
match.group('location'),
match.group('keyring'),
match.group('key'),
]
return None
def ParseKmsDescribeArgs(args):
"""Parses KMS describe args."""
location_id = args.location if args.location else None
return _PROJECT(), location_id
def ParseKmsClearArgs(args):
"""Parses KMS clear args."""
location_id = args.location if args.location else None
return _PROJECT(), location_id
def ParseKmsUpdateArgs(args):
"""Parses KMS update args."""
location_id = args.location if args.location else None
full_kms_key_name = None
parse_result = ParseFullKmsKeyName(args.kms_key_name)
if parse_result:
# A full KMS key name resource was set as kms_key_name. Continue.
location_id = parse_result[1]
full_kms_key_name = args.kms_key_name
elif args.kms_key_name and args.kms_keyring and args.location:
# A full kms-key-name was not provided, so build the key using each
# component since they are available..
full_kms_key_name = 'projects/{kms_project_id}/locations/{location_id}/keyRings/{kms_keyring}/cryptoKeys/{kms_key_name}'.format(
kms_project_id=args.kms_project if args.kms_project else _PROJECT(),
location_id=location_id,
kms_keyring=args.kms_keyring,
kms_key_name=args.kms_key_name, # short key name
)
return _PROJECT(), location_id, full_kms_key_name

View File

@@ -0,0 +1,44 @@
location:
name: location
collection: cloudscheduler.projects.locations
attributes:
- &location
parameter_name: locationsId
attribute_name: location
help: |
The location of the job. By default, uses the location of the current project's App Engine app
if there is an associated app.
fallthroughs:
# Called when location is not specified.
- hook: "googlecloudsdk.command_lib.scheduler.util:AppLocationResolver:"
hint: defaults to App Engine's app location if not provided & an app exists
job:
name: job
collection: cloudscheduler.projects.locations.jobs
request_id_field: job.name
attributes:
- *location
- parameter_name: jobsId
attribute_name: job
help: The ID of the job.
cmekConfig:
name: cmekConfig
collection: cloudscheduler.projects.locations.cmekConfig
request_id_field: cmekConfig.name
attributes:
- *location
- parameter_name: cmekConfigName
attribute_name: cmekConfig
help: The ID of the CMEK Config.
operation:
name: operation
collection: cloudscheduler.projects.locations.operations
attributes:
- *location
- parameter_name: operationsId
attribute_name: operation
help: The ID of the operation.

View File

@@ -0,0 +1,448 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google Inc. 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.
"""Utilities for "gcloud scheduler" commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from apitools.base.py import exceptions as apitools_exceptions
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.command_lib.tasks import app
from googlecloudsdk.command_lib.tasks import constants
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.util import encoding
from googlecloudsdk.core.util import http_encoding
_LOCATION_LIST_FORMAT = '''table(
locationId:label="NAME",
name:label="FULL_NAME")'''
_PUBSUB_MESSAGE_URL = 'type.googleapis.com/google.pubsub.v1.PubsubMessage'
PROJECTS_COLLECTION = 'cloudscheduler.projects'
LOCATIONS_COLLECTION = 'cloudscheduler.projects.locations'
OPERATIONS_COLLECTION = 'cloudscheduler.projects.locations.operations'
def _GetPubsubMessages():
return apis.GetMessagesModule('pubsub', apis.ResolveVersion('pubsub'))
def _GetSchedulerClient():
return apis.GetClientInstance('cloudscheduler', 'v1')
def _GetSchedulerMessages():
return apis.GetMessagesModule('cloudscheduler', 'v1')
def ClearFlag(arg):
"""Clear the value for a flag."""
del arg
return None
def LogPauseSuccess(unused_response, unused_args):
"""Log message if job was successfully paused."""
_LogSuccessMessage('paused')
def LogResumeSuccess(unused_response, unused_args):
"""Log message if job was successfully resumed."""
_LogSuccessMessage('resumed')
def _LogSuccessMessage(action):
log.status.Print('Job has been {0}.'.format(action))
def ParseProject():
return resources.REGISTRY.Parse(
properties.VALUES.core.project.GetOrFail(),
collection=PROJECTS_COLLECTION)
def LocationsUriFunc(task):
return resources.REGISTRY.Parse(
task.name,
params={'projectId': properties.VALUES.core.project.GetOrFail()},
collection=LOCATIONS_COLLECTION).SelfLink()
def AddListLocationsFormats(parser):
parser.display_info.AddFormat(_LOCATION_LIST_FORMAT)
parser.display_info.AddUriFunc(LocationsUriFunc)
def ModifyCreateJobRequest(job_ref, args, create_job_req):
"""Change the job.name field to a relative name."""
del args # Unused in ModifyCreateJobRequest
create_job_req.job.name = job_ref.RelativeName()
return create_job_req
def ModifyCreatePubsubJobRequest(job_ref, args, create_job_req):
"""Add the pubsubMessage field to the given request.
Because the Cloud Scheduler API has a reference to a PubSub message, but
represents it as a bag of properties, we need to construct the object here and
insert it into the request.
Args:
job_ref: Resource reference to the job to be created (unused)
args: argparse namespace with the parsed arguments from the command line. In
particular, we expect args.message_body and args.attributes (optional)
to be AdditionalProperty types.
create_job_req: CloudschedulerProjectsLocationsJobsCreateRequest, the
request constructed from the remaining arguments.
Returns:
CloudschedulerProjectsLocationsJobsCreateRequest: the given request but with
the job.pubsubTarget.pubsubMessage field populated.
"""
ModifyCreateJobRequest(job_ref, args, create_job_req)
create_job_req.job.pubsubTarget.data = _EncodeMessageBody(
args.message_body or args.message_body_from_file)
if args.attributes:
create_job_req.job.pubsubTarget.attributes = args.attributes
return create_job_req
def SetRequestJobName(job_ref, unused_args, update_job_req):
"""Change the job.name field to a relative name."""
update_job_req.job.name = job_ref.RelativeName()
return update_job_req
def SetAppEngineRequestMessageBody(unused_job_ref, args, update_job_req):
"""Modify the App Engine update request to populate the message body."""
if args.clear_message_body:
update_job_req.job.appEngineHttpTarget.body = None
elif args.message_body or args.message_body_from_file:
update_job_req.job.appEngineHttpTarget.body = _EncodeMessageBody(
args.message_body or args.message_body_from_file)
return update_job_req
def SetAppEngineRequestUpdateHeaders(unused_job_ref, args, update_job_req):
"""Modify the App Engine update request to update, remove or clear headers."""
headers = None
if args.clear_headers:
headers = {}
elif args.update_headers or args.remove_headers:
if args.update_headers:
headers = args.update_headers
if args.remove_headers:
for key in args.remove_headers:
headers[key] = None
if headers:
update_job_req.job.appEngineHttpTarget.headers = _GenerateAdditionalProperties(
headers)
return update_job_req
def SetHTTPRequestMessageBody(unused_job_ref, args, update_job_req):
"""Modify the HTTP update request to populate the message body."""
if args.clear_message_body:
update_job_req.job.httpTarget.body = None
elif args.message_body or args.message_body_from_file:
update_job_req.job.httpTarget.body = _EncodeMessageBody(
args.message_body or args.message_body_from_file)
return update_job_req
def SetHTTPRequestUpdateHeaders(unused_job_ref, args, update_job_req):
"""Modify the HTTP update request to update, remove, or clear headers."""
headers = None
if args.clear_headers:
headers = {}
elif args.update_headers or args.remove_headers:
if args.update_headers:
headers = args.update_headers
if args.remove_headers:
for key in args.remove_headers:
headers[key] = None
if headers:
update_job_req.job.httpTarget.headers = _GenerateAdditionalProperties(
headers)
return update_job_req
def SetPubsubRequestMessageBody(unused_job_ref, args, update_job_req):
"""Modify the Pubsub update request to populate the message body."""
if args.message_body or args.message_body_from_file:
update_job_req.job.pubsubTarget.data = _EncodeMessageBody(
args.message_body or args.message_body_from_file)
return update_job_req
def SetPubsubRequestUpdateAttributes(unused_job_ref, args, update_job_req):
"""Modify the Pubsub update request to update, remove, or clear attributes."""
attributes = None
if args.clear_attributes:
attributes = {}
elif args.update_attributes or args.remove_attributes:
if args.update_attributes:
attributes = args.update_attributes
if args.remove_attributes:
for key in args.remove_attributes:
attributes[key] = None
if attributes:
update_job_req.job.pubsubTarget.attributes = _GenerateAdditionalProperties(
attributes)
return update_job_req
def ParseAttributes(attributes):
"""Parse "--attributes" flag as an argparse type.
The flag is given as a Calliope ArgDict:
--attributes key1=value1,key2=value2
Args:
attributes: str, the value of the --attributes flag.
Returns:
dict, a dict with 'additionalProperties' as a key, and a list of dicts
containing key-value pairs as the value.
"""
attributes = arg_parsers.ArgDict()(attributes)
return {
'additionalProperties':
[{'key': key, 'value': value}
for key, value in sorted(attributes.items())]
}
def UpdateAppEngineMaskHook(unused_ref, args, req):
"""Constructs updateMask for patch requests of AppEngine targets.
Args:
unused_ref: A resource ref to the parsed Job resource
args: The parsed args namespace from CLI
req: Created Patch request for the API call.
Returns:
Modified request for the API call.
"""
app_engine_fields = {
'--message-body': 'appEngineHttpTarget.body',
'--message-body-from-file': 'appEngineHttpTarget.body',
'--relative-url': 'appEngineHttpTarget.relativeUri',
'--version': 'appEngineHttpTarget.appEngineRouting.version',
'--service': 'appEngineHttpTarget.appEngineRouting.service',
'--clear-service': 'appEngineHttpTarget.appEngineRouting.service',
'--clear-relative-url': 'appEngineHttpTarget.relativeUri',
'--clear-headers': 'appEngineHttpTarget.headers',
'--remove-headers': 'appEngineHttpTarget.headers',
'--update-headers': 'appEngineHttpTarget.headers',
}
req.updateMask = _GenerateUpdateMask(args, app_engine_fields)
return req
def UpdateHTTPMaskHook(unused_ref, args, req):
"""Constructs updateMask for patch requests of PubSub targets.
Args:
unused_ref: A resource ref to the parsed Job resource
args: The parsed args namespace from CLI
req: Created Patch request for the API call.
Returns:
Modified request for the API call.
"""
http_fields = {
'--message-body': 'httpTarget.body',
'--message-body-from-file': 'httpTarget.body',
'--uri': 'httpTarget.uri',
'--http-method': 'httpTarget.httpMethod',
'--clear-headers': 'httpTarget.headers',
'--remove-headers': 'httpTarget.headers',
'--update-headers': 'httpTarget.headers',
'--oidc-service-account-email':
'httpTarget.oidcToken.serviceAccountEmail',
'--oidc-token-audience': 'httpTarget.oidcToken.audience',
'--oauth-service-account-email':
'httpTarget.oauthToken.serviceAccountEmail',
'--oauth-token-scope': 'httpTarget.oauthToken.scope',
'--clear-auth-token':
'httpTarget.oidcToken.serviceAccountEmail,'
'httpTarget.oidcToken.audience,'
'httpTarget.oauthToken.serviceAccountEmail,'
'httpTarget.oauthToken.scope',
}
req.updateMask = _GenerateUpdateMask(args, http_fields)
return req
def UpdatePubSubMaskHook(unused_ref, args, req):
"""Constructs updateMask for patch requests of PubSub targets.
Args:
unused_ref: A resource ref to the parsed Job resource
args: The parsed args namespace from CLI
req: Created Patch request for the API call.
Returns:
Modified request for the API call.
"""
pubsub_fields = {
'--message-body': 'pubsubTarget.data',
'--message-body-from-file': 'pubsubTarget.data',
'--topic': 'pubsubTarget.topicName',
'--clear-attributes': 'pubsubTarget.attributes',
'--remove-attributes': 'pubsubTarget.attributes',
'--update-attributes': 'pubsubTarget.attributes',
}
req.updateMask = _GenerateUpdateMask(args, pubsub_fields)
return req
def _GenerateUpdateMask(args, target_fields):
"""Constructs updateMask for patch requests.
Args:
args: The parsed args namespace from CLI
target_fields: A Dictionary of field mappings specific to the target.
Returns:
String containing update mask for patch request.
"""
arg_name_to_field = {
# Common flags
'--description': 'description',
'--schedule': 'schedule',
'--time-zone': 'timeZone',
'--clear-time-zone': 'timeZone',
'--attempt-deadline': 'attemptDeadline',
# Retry flags
'--max-retry-attempts': 'retryConfig.retryCount',
'--clear-max-retry-attempts': 'retryConfig.retryCount',
'--max-retry-duration': 'retryConfig.maxRetryDuration',
'--clear-max-retry-duration': 'retryConfig.maxRetryDuration',
'--min-backoff': 'retryConfig.minBackoffDuration',
'--clear-min-backoff': 'retryConfig.minBackoffDuration',
'--max-backoff': 'retryConfig.maxBackoffDuration',
'--clear-max-backoff': 'retryConfig.maxBackoffDuration',
'--max-doublings': 'retryConfig.maxDoublings',
'--clear-max-doublings': 'retryConfig.maxDoublings',
}
if target_fields:
arg_name_to_field.update(target_fields)
update_mask = []
for arg_name in args.GetSpecifiedArgNames():
if arg_name in arg_name_to_field:
update_mask.append(arg_name_to_field[arg_name])
return ','.join(sorted(list(set(update_mask))))
def _EncodeMessageBody(message_body):
"""HTTP encodes the given message body.
Args:
message_body: the message body to be encoded
Returns:
String containing HTTP encoded message body.
"""
message_body_str = encoding.Decode(message_body, encoding='utf-8')
return http_encoding.Encode(message_body_str)
def _GenerateAdditionalProperties(values_dict):
"""Format values_dict into additionalProperties-style dict."""
return {
'additionalProperties': [
{'key': key, 'value': value} for key, value
in sorted(values_dict.items())
]}
def _DoesCommandRequireAppEngineApp():
"""Returns whether the command being executed needs App Engine app."""
gcloud_env_key = constants.GCLOUD_COMMAND_ENV_KEY
if gcloud_env_key in os.environ:
return os.environ[gcloud_env_key] in constants.COMMANDS_THAT_NEED_APPENGINE
return False
class RegionResolvingError(exceptions.Error):
"""Error for when the app's region cannot be ultimately determined."""
class AppLocationResolver(object):
"""Callable that resolves and caches the app location for the project.
The "fallback" for arg marshalling gets used multiple times in the course of
YAML command translation. This prevents multiple API roundtrips without making
that class stateful.
"""
def __init__(self):
self.location = None
def __call__(self):
if self.location is None:
self.location = self._ResolveAppLocation()
return self.location
def _ResolveAppLocation(self):
"""Determines Cloud Scheduler location for the project."""
# Not setting the quota project to 'LEGACY' sends the current project ID in
# the App Engine API request. This prompts a check to see if the App Engine
# admin API is enabled for that project.
properties.VALUES.billing.quota_project.Set(
properties.VALUES.billing.LEGACY)
if app.AppEngineAppExists():
project = properties.VALUES.core.project.GetOrFail()
return self._GetLocation(project)
raise RegionResolvingError(
'Please use the location flag to manually specify a location.')
def _GetLocation(self, project):
"""Gets the location from the Cloud Scheduler API."""
try:
client = _GetSchedulerClient()
messages = _GetSchedulerMessages()
request = messages.CloudschedulerProjectsLocationsListRequest(
name='projects/{}'.format(project))
locations = list(
list_pager.YieldFromList(
client.projects_locations,
request,
batch_size=2,
limit=2,
field='locations',
batch_size_attribute='pageSize'))
if len(locations) >= 1:
location = locations[0].labels.additionalProperties[0].value
if len(locations) > 1 and not _DoesCommandRequireAppEngineApp():
log.warning(
constants.APP_ENGINE_DEFAULT_LOCATION_WARNING.format(location))
return location
return None
except apitools_exceptions.HttpNotFoundError:
return None