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,78 @@
# -*- 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.
"""Utilities for the cloud deploy automation resource."""
from googlecloudsdk.api_lib.clouddeploy import automation
from googlecloudsdk.command_lib.deploy import exceptions as cd_exceptions
from googlecloudsdk.core import resources
_AUTOMATION_COLLECTION = (
'clouddeploy.projects.locations.deliveryPipelines.automations'
)
def AutomationReference(automation_name, project, location_id):
"""Creates the automation reference base on the parameters.
Returns the reference of the automation name.
Args:
automation_name: str, in the format of pipeline_id/automation_id.
project: str,project number or ID.
location_id: str, region ID.
Returns:
Automation name reference.
"""
try:
pipeline_id, automation_id = automation_name.split('/')
except ValueError:
raise cd_exceptions.AutomationNameFormatError(automation_name)
return resources.REGISTRY.Parse(
None,
collection=_AUTOMATION_COLLECTION,
params={
'projectsId': project,
'locationsId': location_id,
'deliveryPipelinesId': pipeline_id,
'automationsId': automation_id,
},
)
def PatchAutomation(resource):
"""Patches an automation resource by calling the patch automation API.
Args:
resource: apitools.base.protorpclite.messages.Message, automation message.
Returns:
The operation message.
"""
return automation.AutomationsClient().Patch(resource)
def DeleteAutomation(name):
"""Deletes an automation resource by calling the delete automation API.
Args:
name: str, automation name.
Returns:
The operation message.
"""
return automation.AutomationsClient().Delete(name)

View File

@@ -0,0 +1,68 @@
# -*- 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.
"""Utilities for the cloud deploy custom target type resource."""
from googlecloudsdk.api_lib.clouddeploy import custom_target_type
from googlecloudsdk.core import resources
def CustomTargetTypeReference(name, project, region):
"""Creates the custom target type reference base on the parameters.
Returns the shared custom target type reference.
Args:
name: str, custom target type ID
project: str,project number or ID.
region: str, region ID.
Returns:
custom target type reference.
"""
return resources.REGISTRY.Parse(
name,
collection='clouddeploy.projects.locations.customTargetTypes',
params={
'projectsId': project,
'locationsId': region,
'customTargetTypesId': name,
},
)
def PatchCustomTargetType(resource):
"""Patches a custom target type resource.
Args:
resource: apitools.base.protorpclite.messages.Message, custom target type
message.
Returns:
The operation message
"""
return custom_target_type.CustomTargetTypesClient().Patch(resource)
def DeleteCustomTargetType(name):
"""Deletes a custom target type resource.
Args:
name: str, custom target type name.
Returns:
The operation message
"""
return custom_target_type.CustomTargetTypesClient().Delete(name)

View File

@@ -0,0 +1,179 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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 the cloud deploy delivery pipeline resource."""
import uuid
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.clouddeploy import client_util
from googlecloudsdk.api_lib.clouddeploy import delivery_pipeline
from googlecloudsdk.command_lib.deploy import exceptions as cd_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
_PIPELINES_WITH_GIVEN_TARGET_FILTER_TEMPLATE = (
'serialPipeline.stages.targetId:"{}"'
)
def DeliveryPipelineReference(name, project, region):
"""Creates the delivery pipeline reference base on the parameters.
Returns the shared delivery pipeline reference.
Args:
name: str, delivery pipeline ID
project: str,project number or ID.
region: str, region ID.
Returns:
custom target type reference.
"""
return resources.REGISTRY.Parse(
name,
collection='clouddeploy.projects.locations.deliveryPipelines',
params={
'projectsId': project,
'locationsId': region,
'deliveryPipelinesId': name,
},
)
def ListDeliveryPipelinesWithTarget(target_ref, location_ref):
"""Lists the delivery pipelines associated with the specified target.
The returned list is sorted by the delivery pipeline's create time.
Args:
target_ref: protorpc.messages.Message, target object.
location_ref: protorpc.messages.Message, location object.
Returns:
a sorted list of delivery pipelines.
"""
filter_str = _PIPELINES_WITH_GIVEN_TARGET_FILTER_TEMPLATE.format(
target_ref.Name()
)
pipelines = delivery_pipeline.DeliveryPipelinesClient().List(
location=location_ref.RelativeName(),
filter_str=filter_str,
page_size=0,
)
# ListDeliveryPipelines does not support orderBy=createTime field
# so sort result to get the same result you would get by using
# orderBy = 'createTime desc'
return sorted(
pipelines, key=lambda pipeline: pipeline.createTime, reverse=True
)
def PipelineToPipelineRef(pipeline):
pipeline_ref = resources.REGISTRY.ParseRelativeName(
pipeline.name,
collection='clouddeploy.projects.locations.deliveryPipelines',
)
return pipeline_ref
def GetPipeline(pipeline_name):
"""Gets the delivery pipeline and returns the value of its suspended field.
Args:
pipeline_name: str, the canonical resource name of the delivery pipeline
Returns:
The pipeline object
Raises:
apitools.base.py.HttpError
"""
try:
pipeline_obj = delivery_pipeline.DeliveryPipelinesClient().Get(
pipeline_name
)
return pipeline_obj
except apitools_exceptions.HttpError as error:
log.debug(
'Failed to get pipeline {}: {}'.format(pipeline_name, error.content)
)
log.status.Print('Unable to get delivery pipeline {}'.format(pipeline_name))
raise error
def ThrowIfPipelineSuspended(pipeline_obj, suspended_pipeline_msg):
"""Checks if the delivery pipeline associated with the release is suspended.
Args:
pipeline_obj: protorpc.messages.Message, delivery pipeline object.
suspended_pipeline_msg: str, error msg to show the user if the pipeline is
suspended.
Raises:
googlecloudsdk.command_lib.deploy.PipelineSuspendedError if the pipeline is
suspended
"""
if pipeline_obj.suspended:
raise cd_exceptions.PipelineSuspendedError(
pipeline_name=pipeline_obj.name,
failed_activity_msg=suspended_pipeline_msg,
)
def CreateRollbackTarget(
pipeline_rel_name,
target_id,
validate_only=False,
release_id=None,
rollout_id=None,
rollout_to_rollback=None,
rollout_obj=None,
starting_phase=None,
):
"""Creates a rollback rollout for the target based on the given inputs.
Args:
pipeline_rel_name: delivery_pipeline name
target_id: the target to rollback
validate_only: whether or not to validate only for the call
release_id: the release_id to rollback to
rollout_id: the rollout_id of the new rollout
rollout_to_rollback: the rollout that is being rolled back by this rollout
rollout_obj: the rollout resource to pass into rollbackTargetConfig
starting_phase: starting_phase of the rollout
Returns:
RollbackTargetResponse
"""
# if the rollout_id isn't provided, then generate one based off of UUID. This
# will give us the release id that we will use and generate the rollout id.
rollback_id = rollout_id if rollout_id else 'rollback-' + uuid.uuid4().hex
rollback_target_config = client_util.GetMessagesModule().RollbackTargetConfig(
rollout=rollout_obj, startingPhaseId=starting_phase
)
return delivery_pipeline.DeliveryPipelinesClient().RollbackTarget(
pipeline_rel_name,
client_util.GetMessagesModule().RollbackTargetRequest(
releaseId=release_id,
rollbackConfig=rollback_target_config,
rolloutId=rollback_id,
rolloutToRollBack=rollout_to_rollback,
targetId=target_id,
validateOnly=validate_only,
),
)

View File

@@ -0,0 +1,116 @@
# -*- 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.
"""Utilities for the cloud deploy deploy policy resource."""
from googlecloudsdk.api_lib.clouddeploy import deploy_policy
from googlecloudsdk.core import resources
def DeployPolicyReference(name, project, region):
"""Creates the deploy policy reference base on the parameters.
Returns the shared deploy policy reference.
Args:
name: str, deploy policy ID
project: str,project number or ID.
region: str, region ID.
Returns:
custom target type reference.
"""
return resources.REGISTRY.Parse(
name,
collection='clouddeploy.projects.locations.deployPolicies',
params={
'projectsId': project,
'locationsId': region,
'deployPoliciesId': name,
},
)
def PatchDeployPolicy(resource):
"""Patches a deploy policy resource.
Args:
resource: apitools.base.protorpclite.messages.Message, deploy policy
message.
Returns:
The operation message
"""
return deploy_policy.DeployPoliciesClient().Patch(resource)
def DeleteDeployPolicy(name):
"""Deletes a deploy policy resource.
Args:
name: str, deploy policy name.
Returns:
The operation message
"""
return deploy_policy.DeployPoliciesClient().Delete(name)
def CreateDeployPolicyNamesFromIDs(pipeline_ref, deploy_policy_ids):
"""Creates deploy policy canonical resource names from ids.
Args:
pipeline_ref: pipeline resource reference.
deploy_policy_ids: list of deploy policy ids (e.g. ['deploy-policy-1',
'deploy-policy-2'])
Returns:
A list of deploy policy canonical resource names.
"""
pipeline_dict = pipeline_ref.AsDict()
project_id = pipeline_dict.get('projectsId')
location_id = pipeline_dict.get('locationsId')
policies = []
if deploy_policy_ids:
for policy in deploy_policy_ids:
deploy_policy_resource_ref = resources.REGISTRY.Parse(
policy,
collection='clouddeploy.projects.locations.deployPolicies',
params={
'projectsId': project_id,
'locationsId': location_id,
'deployPoliciesId': policy,
},
)
policies.append(deploy_policy_resource_ref.RelativeName())
return policies
def GetDeployPolicy(deploy_policy_ref):
"""Gets the deploy policy message by calling the get deploy policy API.
Args:
deploy_policy_ref: protorpc.messages.Message, protorpc.messages.Message,
deploy policy reference.
Returns:
Deploy policy message.
Raises:
Exceptions raised by DeployPoliciesClient's get functions
"""
return deploy_policy.DeployPoliciesClient().Get(
deploy_policy_ref.RelativeName()
)

View File

@@ -0,0 +1,75 @@
# -*- 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.
"""General utilities for cloud deploy resources."""
import enum
from googlecloudsdk.core.resource import resource_property
class ResourceType(enum.Enum):
"""Indicates the cloud deploy resource type defined in the API proto."""
DELIVERY_PIPELINE = 'DeliveryPipeline'
TARGET = 'Target'
RELEASE = 'Release'
ROLLOUT = 'Rollout'
AUTOMATION = 'Automation'
CUSTOM_TARGET_TYPE = 'CustomTargetType'
DEPLOY_POLICY = 'DeployPolicy'
PIPELINE_ATTRIBUTE = 'DeliveryPipelineAttribute'
TARGET_ATTRIBUTE = 'TargetAttribute'
def SetMetadata(messages,
message,
resource_type,
annotations=None,
labels=None):
"""Sets the metadata of a cloud deploy resource message.
Args:
messages: module containing the definitions of messages for Cloud Deploy.
message: message in googlecloudsdk.generated_clients.apis.clouddeploy.
resource_type: ResourceType enum, the type of the resource to be updated,
which is defined in the API proto.
annotations: dict[str,str], a dict of annotation (key,value) pairs that allow
clients to store small amounts of arbitrary data in cloud deploy resources.
labels: dict[str,str], a dict of label (key,value) pairs that can be used to
select cloud deploy resources and to find collections of cloud deploy
resources that satisfy certain conditions.
"""
if annotations:
annotations_value_msg = getattr(messages,
resource_type.value).AnnotationsValue
annotations_value = annotations_value_msg()
for key, value in annotations.items():
annotations_value.additionalProperties.append(
annotations_value_msg.AdditionalProperty(key=key, value=value))
message.annotations = annotations_value
if labels:
labels_value_msg = getattr(messages, resource_type.value).LabelsValue
labels_value = labels_value_msg()
for key, value in labels.items():
labels_value.additionalProperties.append(
labels_value_msg.AdditionalProperty(
# Base on go/unified-cloud-labels-proposal,
# converts camel case key to snake case.
key=resource_property.ConvertToSnakeCase(key),
value=value))
message.labels = labels_value

View File

@@ -0,0 +1,370 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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 the cloud deploy describe commands."""
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.command_lib.deploy import delivery_pipeline_util
from googlecloudsdk.command_lib.deploy import rollout_util
from googlecloudsdk.command_lib.deploy import target_util
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
from googlecloudsdk.core.util import times
def DescribeTarget(target_ref, pipeline_id, skip_pipeline_lookup,
list_all_pipelines):
"""Describes details specific to the individual target, delivery pipeline qualified.
Args:
target_ref: protorpc.messages.Message, target reference.
pipeline_id: str, delivery pipeline ID.
skip_pipeline_lookup: Boolean, flag indicating whether to fetch information
from the pipeline(s) containing target. If set, pipeline information will
not be fetched.
list_all_pipelines: Boolean, flag indicating whether to fetch information
from all pipelines associated with target, if set to false, it will fetch
information from the most recently updated one.
Returns:
A dictionary of <section name:output>.
"""
target_obj = target_util.GetTarget(target_ref)
output = {'Target': target_obj}
if skip_pipeline_lookup:
return output
if pipeline_id:
return DescribeTargetWithPipeline(target_obj, target_ref, pipeline_id,
output)
else:
return DescribeTargetWithNoPipeline(target_obj, target_ref,
list_all_pipelines, output)
def DescribeTargetWithPipeline(target_obj, target_ref, pipeline_id, output):
"""Describes details specific to the individual target, delivery pipeline qualified.
The output contains four sections:
target
- detail of the target to be described.
latest release
- the detail of the active release in the target.
latest rollout
- the detail of the active rollout in the target.
deployed
- timestamp of the last successful deployment.
pending approvals
- list rollouts that require approval.
Args:
target_obj: protorpc.messages.Message, target object.
target_ref: protorpc.messages.Message, target reference.
pipeline_id: str, delivery pipeline ID.
output: A dictionary of <section name:output>.
Returns:
A dictionary of <section name:output>.
"""
target_dict = target_ref.AsDict()
pipeline_ref = resources.REGISTRY.Parse(
None,
collection='clouddeploy.projects.locations.deliveryPipelines',
params={
'projectsId': target_dict['projectsId'],
'locationsId': target_dict['locationsId'],
'deliveryPipelinesId': pipeline_id
})
current_rollout = target_util.GetCurrentRollout(target_ref, pipeline_ref)
output = SetCurrentReleaseAndRollout(current_rollout, output)
if target_obj.requireApproval:
output = ListPendingApprovals(target_ref, pipeline_ref, output)
return output
def DescribeTargetWithNoPipeline(target_obj, target_ref, list_all_pipelines,
output):
"""Describes details specific to the individual target.
In addition, it will also display details about pipelines associated
with the given target.
The output contains the following sections:
target
- details of the target to be described.
associated pipelines
- details of the pipelines that use the target.
For each associated pipeline, the following will be displayed:
latest release
- details of the active release in the target.
latest rollout
- details of the active rollout in the target.
deployed
- timestamp of the last successful deployment.
pending approvals
- list the rollouts that require approval.
Args:
target_obj: protorpc.messages.Message, target object.
target_ref: protorpc.messages.Message, target reference.
list_all_pipelines: Boolean, if true, will return information about all
pipelines associated with target, otherwise, the most recently active
pipeline information will be displayed.
output: A dictionary of <section name:output>.
Returns:
A dictionary of <section name:output>.
"""
sorted_pipelines = GetTargetDeliveryPipelines(target_ref)
if not sorted_pipelines:
return output
output['Number of associated delivery pipelines'] = len(sorted_pipelines)
pipeline_refs = list(
map(delivery_pipeline_util.PipelineToPipelineRef, sorted_pipelines))
if list_all_pipelines:
output['Associated delivery pipelines'] = ListAllPipelineReleaseRollout(
target_ref, pipeline_refs)
if target_obj.requireApproval:
ListPendingApprovalsMultiplePipelines(target_ref, pipeline_refs, output)
else:
active_pipeline_ref, latest_rollout = GetMostRecentlyActivePipeline(
target_ref, pipeline_refs)
if active_pipeline_ref and latest_rollout:
output['Active Pipeline'] = SetMostRecentlyActivePipeline(
active_pipeline_ref, latest_rollout)
if target_obj.requireApproval:
ListPendingApprovals(target_ref, active_pipeline_ref, output)
return output
def ListPendingApprovalsMultiplePipelines(target_ref, pipeline_refs, output):
"""Fetches a list of pending rollouts for each pipeline and appends the result to a single list.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_refs: protorpc.messages.Message, list of pipeline objects.
output: dictionary object
Returns:
The modified output object with the list of pending rollouts.
"""
all_pending_approvals = []
for pipeline_ref in pipeline_refs:
result_dict = ListPendingApprovals(target_ref, pipeline_ref, {})
approvals_one_pipeline = result_dict.get('Pending Approvals', [])
all_pending_approvals.extend(approvals_one_pipeline)
output['Pending Approvals'] = all_pending_approvals
return output
def SetCurrentReleaseAndRollout(current_rollout, output):
"""Set current release and the last deployment section in the output.
Args:
current_rollout: protorpc.messages.Message, rollout object.
output: dictionary object
Returns:
The modified output object with the rollout's parent release, the name of
the rollout, and the time it was deployed.
"""
if current_rollout:
current_rollout_ref = resources.REGISTRY.Parse(
current_rollout.name,
collection='clouddeploy.projects.locations.deliveryPipelines.releases.rollouts'
)
# get the name of the release associated with the current rollout.
output['Latest release'] = current_rollout_ref.Parent().RelativeName()
output['Latest rollout'] = current_rollout_ref.RelativeName()
output['Deployed'] = current_rollout.deployEndTime
return output
def ListPendingApprovals(target_ref, pipeline_ref, output):
"""Lists the rollouts in pending approval state for the specified target.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_ref: protorpc.messages.Message, pipeline object.
output: dictionary object
Returns:
The modified output object with the rollouts from the given pipeline pending
approval on the given target.
"""
try:
pending_approvals = rollout_util.ListPendingRollouts(
target_ref, pipeline_ref)
pending_approvals_names = []
for ro in pending_approvals:
pending_approvals_names.append(ro.name)
if pending_approvals_names:
output['Pending Approvals'] = pending_approvals_names
except apitools_exceptions.HttpError as error:
log.debug('Failed to list pending approvals: ' + error.content)
return output
def GetTargetDeliveryPipelines(target_ref):
"""Get all pipelines associated with a target.
Args:
target_ref: protorpc.messages.Message, target object.
Returns:
A list of delivery pipelines sorted by creation date, or an null list if
there is an error fetching the pipelines.
"""
target_dict = target_ref.AsDict()
location_ref = resources.REGISTRY.Parse(
None,
collection='clouddeploy.projects.locations',
params={
'projectsId': target_dict['projectsId'],
'locationsId': target_dict['locationsId'],
})
try:
return delivery_pipeline_util.ListDeliveryPipelinesWithTarget(
target_ref, location_ref)
except apitools_exceptions.HttpError as error:
log.warning('Failed to fetch pipelines for target: ' + error.content)
return None
def GetPipelinesAndRollouts(target_ref, pipeline_refs):
"""Retrieves the latest rollout for each delivery pipeline.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_refs: protorpc.messages.Message, pipeline object.
Returns:
A list with element [pipeline_ref, rollout] where the rollout is the latest
successful rollout of the pipeline resource.
"""
result = []
for pipeline_ref in pipeline_refs:
rollout = target_util.GetCurrentRollout(target_ref, pipeline_ref)
if rollout is not None:
result.append([pipeline_ref, rollout])
return result
def GetMostRecentlyActivePipeline(target_ref, sorted_pipeline_refs):
"""Retrieves latest rollout and release information for a list of delivery pipelines.
Args:
target_ref: protorpc.messages.Message, target object.
sorted_pipeline_refs: protorpc.messages.Message, a list of pipeline objects,
sorted in descending order by create time.
Returns:
A tuple of the pipeline with the most recent deploy time with
latest rollout.
"""
pipeline_rollouts = GetPipelinesAndRollouts(target_ref, sorted_pipeline_refs)
if not pipeline_rollouts:
log.debug('Target: {} has no recently active pipelines.'.format(
target_ref.RelativeName()))
return sorted_pipeline_refs[0], None
most_recent_pipeline_ref, most_recent_rollout = pipeline_rollouts[0]
most_recent_rollout_deploy_time = times.ParseDateTime(
most_recent_rollout.deployEndTime)
for pipeline_rollout_tuple in pipeline_rollouts[1:]:
pipeline_ref, rollout = pipeline_rollout_tuple
rollout_deploy_time = times.ParseDateTime(rollout.deployEndTime)
if rollout_deploy_time > most_recent_rollout_deploy_time:
most_recent_pipeline_ref = pipeline_ref
most_recent_rollout = rollout
most_recent_rollout_deploy_time = rollout_deploy_time
return most_recent_pipeline_ref, most_recent_rollout
def SetMostRecentlyActivePipeline(pipeline_ref, rollout):
"""Retrieves latest rollout and release information for a delivery pipeline.
Args:
pipeline_ref: protorpc.messages.Message a DeliveryPipeline object.
rollout: protorpc.messages.Message a Rollout object.
Returns:
A content directory.
"""
output = [{
pipeline_ref.RelativeName(): SetCurrentReleaseAndRollout(rollout, {})
}]
return output
def SetPipelineReleaseRollout(target_ref, pipeline_ref):
"""Retrieves latest rollout and release information for a single delivery pipeline.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_ref: protorpc.messages.Message, DeliveryPipeline object
Returns:
A content directory.
"""
current_rollout = target_util.GetCurrentRollout(target_ref, pipeline_ref)
output = {}
output = SetCurrentReleaseAndRollout(current_rollout, output)
return output
def ListAllPipelineReleaseRollout(target_ref, pipeline_refs):
"""Retrieves latest rollout and release information for each delivery pipeline.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_refs: protorpc.messages.Message a list of DeliveryPipeline objects
Returns:
A content directory.
"""
output = []
for pipeline_ref in pipeline_refs:
pipeline_entry = SetPipelineReleaseRollout(target_ref, pipeline_ref)
output.append({pipeline_ref.RelativeName(): pipeline_entry})
return output

View File

@@ -0,0 +1,203 @@
# -*- 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.
"""Exceptions for cloud deploy libraries."""
from googlecloudsdk.calliope import exceptions as c_exceptions
from googlecloudsdk.core import exceptions
HTTP_ERROR_FORMAT = 'Status code: {status_code}. {status_message}.'
class ParserError(exceptions.Error):
"""Error parsing JSON into a dictionary."""
def __init__(self, path, msg):
"""Initialize a exceptions.ParserError.
Args:
path: str, build artifacts file path.
msg: str, error message.
"""
msg = 'parsing {path}: {msg}'.format(
path=path,
msg=msg,
)
super(ParserError, self).__init__(msg)
class ReleaseInactiveError(exceptions.Error):
"""Error when a release is not deployed to any target."""
def __init__(self):
super(ReleaseInactiveError, self).__init__(
'This release is not deployed to a target in the active delivery '
'pipeline. Include the --to-target parameter to indicate which target '
'to promote to.'
)
class AbandonedReleaseError(exceptions.Error):
"""Error when an activity happens on an abandoned release."""
def __init__(self, error_msg, release_name):
error_template = '{} Release {} is abandoned.'.format(
error_msg, release_name
)
super(AbandonedReleaseError, self).__init__(error_template)
class NoStagesError(exceptions.Error):
"""Error when a release doesn't contain any pipeline stages."""
def __init__(self, release_name):
super(NoStagesError, self).__init__(
'No pipeline stages in the release {}.'.format(release_name)
)
class InvalidReleaseNameError(exceptions.Error):
"""Error when a release has extra $ signs after expanding template terms."""
def __init__(self, release_name, error_indices):
error_msg = (
"Invalid character '$'"
" for release name '{}' at indices:"
' {}. Did you mean to use $DATE or $TIME?'
)
super(InvalidReleaseNameError, self).__init__(
error_msg.format(release_name, error_indices)
)
class CloudDeployConfigError(exceptions.Error):
"""Error raised for errors in the cloud deploy yaml config."""
@classmethod
def for_unnamed_manifest(cls, num, message):
return cls(f'Error parsing manifest #{num}: {message}')
@classmethod
def for_resource(cls, kind, name, message):
return cls(f'Error parsing {kind} "{name}": {message}')
@classmethod
def for_resource_field(cls, kind, name, field, message):
return cls(f'Error parsing {kind} "{name}" field "{field}": {message}')
class ManifestTransformException(exceptions.Error):
"""Error raised when a manifest transform fails due to a bug."""
class ListRolloutsError(exceptions.Error):
"""Error when it failed to list the rollouts that belongs to a release."""
def __init__(self, release_name):
super(ListRolloutsError, self).__init__(
'Failed to list rollouts for {}.'.format(release_name)
)
class RedeployRolloutError(exceptions.Error):
"""Error when a rollout can't be redeployed.
Redeploy can only be used for rollouts that are in a SUCCEEDED or FAILED
state.
"""
def __init__(self, target_name, rollout_name, rollout_state):
error_msg = (
"Unable to redeploy target {}. Rollout {} is in state {} that can't "
'be redeployed'.format(target_name, rollout_name, rollout_state)
)
super(RedeployRolloutError, self).__init__(error_msg)
class RolloutIDExhaustedError(exceptions.Error):
"""Error when there are too many rollouts for a given release."""
def __init__(self, release_name):
super(RolloutIDExhaustedError, self).__init__(
'Rollout name space exhausted in release {}. Use --rollout-id to '
'specify rollout ID.'.format(release_name)
)
class RolloutInProgressError(exceptions.Error):
"""Error when there is a rollout in progress, no to-target value is given and a promote is attempted."""
def __init__(self, release_name, target_name):
super(RolloutInProgressError, self).__init__(
'Unable to promote release {} to target {}. '
'A rollout is already in progress.'.format(release_name, target_name)
)
class RolloutNotInProgressError(exceptions.Error):
"""Error when a rollout is not in_progress, but is expected to be."""
def __init__(self, rollout_name):
super(RolloutNotInProgressError, self).__init__(
'Rollout {} is not IN_PROGRESS.'.format(rollout_name)
)
class RolloutCannotAdvanceError(exceptions.Error):
"""Error when a rollout cannot be advanced because of a failed precondition."""
def __init__(self, rollout_name, failed_activity_msg):
error_msg = '{} Rollout {} cannot be advanced.'.format(
failed_activity_msg, rollout_name
)
super(RolloutCannotAdvanceError, self).__init__(error_msg)
class PipelineSuspendedError(exceptions.Error):
"""Error when a user performs an activity on a suspended pipeline."""
def __init__(self, pipeline_name, failed_activity_msg):
error_msg = '{} DeliveryPipeline {} is suspended.'.format(
failed_activity_msg, pipeline_name
)
super(PipelineSuspendedError, self).__init__(error_msg)
class AutomationNameFormatError(exceptions.Error):
"""Error when the name of the automation in the config file is not formatted correctly."""
def __init__(self, automation_name):
super(AutomationNameFormatError, self).__init__(
'Automation name {} in the configuration should be in the format'
' of pipeline_id/automation_id.'.format(automation_name)
)
class AutomationWaitFormatError(exceptions.Error):
"""Error when the name of the automation in the config file is not formatted correctly."""
def __init__(self):
super(AutomationWaitFormatError, self).__init__(
'Wait must be numbers with the last character m, e.g. 5m.'
)
class MissingCoupledArgumentsException(c_exceptions.ToolException):
"""An exception for when only one of several arguments that need to be passed together is passed."""
def __init__(self, parameter_names):
super(MissingCoupledArgumentsException, self).__init__(
f'All of these flags {parameter_names} must be supplied together'
)

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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 the cloud deploy export commands."""
from googlecloudsdk.core.resource import resource_printer
from googlecloudsdk.core.util import files
def Export(manifest_dict, args):
"""Writes a message as YAML to a stream.
Args:
manifest_dict: parsed yaml definition.
args: arguments from command line.
"""
if args.destination:
with files.FileWriter(args.destination) as stream:
resource_printer.Print(manifest_dict, print_format='yaml', out=stream)
else:
resource_printer.Print(manifest_dict, print_format='yaml')

View File

@@ -0,0 +1,572 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Flags for the deploy command group."""
import textwrap
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
_SOURCE_HELP_TEXT = """
The location of the source that contains skaffold.yaml. The location can be a directory on a local disk or a gzipped archive file (.tar.gz) in Google Cloud Storage.
If the source is a local directory, this command skips the files specified in the --ignore-file. If --ignore-file is not specified, use.gcloudignore file. If a .gcloudignore file is absent and a .gitignore file is present in the local source directory, gcloud will use a generated Git-compatible .gcloudignore file that respects your .gitignored files.
The global .gitignore is not respected. For more information on .gcloudignore, see gcloud topic gcloudignore.
"""
def AddGcsSourceStagingDirFlag(parser, hidden=False):
"""Adds a Google Cloud Storage directory flag for staging the build."""
parser.add_argument(
'--gcs-source-staging-dir',
hidden=hidden,
help=(
'A directory in Google Cloud Storage to copy the source used for '
'staging the build. If the specified bucket does not exist, Cloud '
"Deploy will create one. If you don't set this field, "
'```gs://[DELIVERY_PIPELINE_ID]_clouddeploy/source``` is used.'
),
)
def AddIgnoreFileFlag(parser, hidden=False):
"""Adds an ignore file flag."""
parser.add_argument(
'--ignore-file',
hidden=hidden,
help=(
'Override the `.gcloudignore` file and use the specified file '
'instead.'
),
)
def AddToTargetFlag(parser, hidden=False):
"""Adds to-target flag."""
parser.add_argument(
'--to-target',
hidden=hidden,
help='Specifies a target to deliver into upon release creation',
)
def AddImagesGroup(parser, hidden=False):
"""Adds Images flag."""
images_group = parser.add_mutually_exclusive_group()
images_group.add_argument(
'--images',
metavar='NAME=TAG',
type=arg_parsers.ArgDict(),
hidden=hidden,
help=textwrap.dedent("""\
Reference to a collection of individual image name to image full path replacements.
For example:
$ gcloud deploy releases create foo \\
--images image1=path/to/image1:v1@sha256:45db24
"""),
)
images_group.add_argument(
'--build-artifacts',
hidden=hidden,
help=(
'Reference to a Skaffold build artifacts output file from skaffold'
" build --file-output=BUILD_ARTIFACTS. If you aren't using Skaffold,"
' use the --images flag below to specify the'
' image-names-to-tagged-image references.'
),
)
def AddConfigFile(parser, hidden=False):
"""Adds config flag."""
parser.add_argument(
'--file',
hidden=hidden,
required=True,
help=(
'Path to yaml file containing Delivery Pipeline(s), Target(s)'
' declarative definitions.'
),
)
def AddToTarget(parser, hidden=False):
"""Adds to-target flag."""
parser.add_argument(
'--to-target', hidden=hidden, help='Destination target to promote into.'
)
def AddRolloutID(parser, hidden=False):
"""Adds rollout-id flag."""
parser.add_argument(
'--rollout-id',
hidden=hidden,
help='ID to assign to the generated rollout for promotion.',
)
def AddRelease(parser, help_text, hidden=False):
"""Adds release flag."""
parser.add_argument('--release', hidden=hidden, help=help_text)
def AddForce(parser, help_text, hidden=False):
"""Adds force flag."""
parser.add_argument(
'--force',
hidden=hidden,
action='store_true',
help=help_text,
)
def AddDescription(parser, help_text, name='--description'):
"""Adds description related flag."""
parser.add_argument(
name,
help=help_text,
)
def AddDeliveryPipeline(parser, required=True):
"""Adds delivery pipeline flag."""
parser.add_argument(
'--delivery-pipeline',
help='The name of the Cloud Deploy delivery pipeline',
required=required,
)
def AddAnnotationsFlag(parser, resource_type):
"""Adds --annotations flag."""
help_text = textwrap.dedent("""\
Annotations to apply to the %s. Annotations take the form of key/value string pairs.
Examples:
Add annotations:
$ {command} --annotations="from_target=test,status=stable"
""") % (resource_type)
parser.add_argument(
'--annotations',
metavar='KEY=VALUE',
type=arg_parsers.ArgDict(),
help=help_text,
)
def AddLabelsFlag(parser, resource_type):
"""Add --labels flag."""
help_text = textwrap.dedent("""\
Labels to apply to the %s. Labels take the form of key/value string pairs.
Examples:
Add labels:
$ {command} --labels="commit=abc123,author=foo"
""") % (resource_type)
parser.add_argument(
'--labels',
metavar='KEY=VALUE',
type=arg_parsers.ArgDict(),
help=help_text,
)
def AddDockerVersion(parser):
"""Adds docker version flag."""
parser.add_argument(
'--docker-version',
help='Version of the Docker binary.',
type=str,
)
def AddHelmVersion(parser):
"""Adds helm version flag."""
parser.add_argument(
'--helm-version',
help='Version of the Helm binary.',
type=str,
)
def AddKptVersion(parser):
"""Adds kpt version flag."""
parser.add_argument(
'--kpt-version',
help='Version of the Kpt binary.',
type=str,
)
def AddKubectlVersion(parser):
"""Adds kubectl version flag."""
parser.add_argument(
'--kubectl-version',
help='Version of the Kubectl binary.',
type=str,
)
def AddKustomizeVersion(parser):
"""Adds kustomize version flag."""
parser.add_argument(
'--kustomize-version',
help='Version of the Kustomize binary.',
type=str,
)
def AddSkaffoldVersion(parser):
"""Adds skaffold version flag."""
parser.add_argument(
'--skaffold-version', help='Version of the Skaffold binary.', type=str
)
def AddSkaffoldFileFlag():
"""Add --skaffold-file flag."""
help_text = textwrap.dedent("""\
Path of the skaffold file absolute or relative to the source directory.
Examples:
Use Skaffold file with relative path:
The current working directory is expected to be some part of the skaffold path (e.g. the current working directory could be /home/user)
$ {command} --source=/home/user/source --skaffold-file=config/skaffold.yaml
The skaffold file absolute file path is expected to be:
/home/user/source/config/skaffold.yaml
Use Skaffold file with absolute path and with or without source argument:
$ {command} --source=/home/user/source --skaffold-file=/home/user/source/config/skaffold.yaml
$ {command} --skaffold-file=/home/user/source/config/skaffold.yaml
""")
return base.Argument('--skaffold-file', help=help_text)
def AddSourceFlag():
"""Adds source flag."""
return base.Argument(
'--source', help=_SOURCE_HELP_TEXT, default='.'
) # By default, the current directory is used.
def AddKubernetesFileFlag():
return base.Argument(
'--from-k8s-manifest',
help=(
'The path to a Kubernetes manifest, which Cloud Deploy will use to '
'generate a skaffold.yaml file for you (for example, '
'foo/bar/k8.yaml). The generated Skaffold file will be available in '
'the Google Cloud Storage source staging directory (see '
'--gcs-source-staging-dir flag) after the release is complete.'
),
)
def AddCloudRunFileFlag():
return base.Argument(
'--from-run-manifest',
help=(
'The path to a Cloud Run manifest, which Cloud Deploy will use to'
' generate a skaffold.yaml file for you (for example,'
' foo/bar/service.yaml). The generated Skaffold file will be'
' available in the Google Cloud Storage source staging directory (see'
' --gcs-source-staging-dir flag) after the release is complete.'
),
)
def AddSkaffoldSources(parser):
"""Add Skaffold sources."""
config_group = parser.add_mutually_exclusive_group()
AddKubernetesFileFlag().AddToParser(config_group)
AddCloudRunFileFlag().AddToParser(config_group)
source_group = config_group.add_group(mutex=False)
AddSourceFlag().AddToParser(source_group)
AddSkaffoldFileFlag().AddToParser(source_group)
def AddDescriptionFlag(parser):
"""Add --description flag."""
parser.add_argument(
'--description',
help='Description of rollout created during a rollback.',
hidden=False,
default=None,
required=False,
)
def AddListAllPipelines(parser):
"""Add --list-all-pipelines flag."""
help_text = textwrap.dedent("""\
List all Delivery Pipelines associated with a target.
Usage:
$ {command} --list-all-pipelines
""")
parser.add_argument(
'--list-all-pipelines', action='store_true', default=None, help=help_text
)
def AddSkipPipelineLookup(parser):
"""Add --skip-pipeline-lookup flag."""
help_text = textwrap.dedent("""\
If set, skip fetching details of associated pipelines when describing a target.
Usage:
$ {command} --skip-pipeline-lookup
""")
parser.add_argument(
'--skip-pipeline-lookup',
action='store_true',
default=False,
help=help_text,
)
def AddRollbackOfRollout(parser):
"""Add --rollback-of-rollout flag."""
help_text = textwrap.dedent("""\
If set, this validates whether the rollout name specified by the flag matches
the rollout on the target.
Examples:
Validate that `test-rollout` is the rollout to rollback on the target.
$ {command} --rollback-of-rollout=projects/test-project/locations/us-central1/deliveryPipelines/test-pipeline/releases/test-release/rollouts/test-rollout
""")
parser.add_argument(
'--rollback-of-rollout',
help=help_text,
hidden=False,
# By default, None is used.
default=None,
required=False,
)
def AddStartingPhaseId(parser):
"""Add --starting-phase-id flag."""
help_text = textwrap.dedent("""\
If set, starts the created rollout at the specified phase.
Start rollout at `stable` phase:
$ {command} --starting-phase-id=stable
""")
parser.add_argument(
'--starting-phase-id',
help=help_text,
hidden=False,
# By default, None is used.
default=None,
required=False,
)
def AddInitialRolloutLabelsFlag():
"""Add --initial-rollout-labels flag."""
help_text = textwrap.dedent("""\
Labels to apply to the initial rollout when creating the release. Labels take
the form of key/value string pairs.
Examples:
Add labels:
$ {command} initial-rollout-labels="commit=abc123,author=foo"
""")
return base.Argument(
'--initial-rollout-labels',
help=help_text,
metavar='KEY=VALUE',
type=arg_parsers.ArgDict(),
)
def AddInitialRolloutAnnotationsFlag():
"""Adds --initial-rollout-annotations flag."""
help_text = textwrap.dedent("""\
Annotations to apply to the initial rollout when creating the release.
Annotations take the form of key/value string pairs.
Examples:
Add annotations:
$ {command} --initial-rollout-annotations="from_target=test,status=stable"
""")
return base.Argument(
'--initial-rollout-annotations',
help=help_text,
metavar='KEY=VALUE',
type=arg_parsers.ArgDict(),
)
def AddInitialRolloutPhaseIDFlag():
"""Adds --initial-rollout-phase-id flag."""
help_text = textwrap.dedent("""\
The phase to start the initial rollout at when creating the release.
The phase ID must be a valid phase on the rollout. If not specified, then the
rollout will start at the first phase.
Examples:
Start rollout at `stable` phase:
$ {command} --initial-rollout-phase-id=stable
""")
return base.Argument(
'--initial-rollout-phase-id',
help=help_text,
hidden=False,
# By default, None is used.
default=None,
required=False,
)
def AddEnableInitialRolloutFlag():
"""Adds --enable-initial-rollout flag."""
return base.Argument(
'--enable-initial-rollout',
action='store_const',
help=(
'Creates a rollout in the first target defined in the delivery'
' pipeline. This is the default behavior.'
),
const=True,
)
def AddDisableInitialRolloutFlag():
"""Adds --disable-initial-rollout flag."""
return base.Argument(
'--disable-initial-rollout',
action='store_const',
help=(
'Skips creating a rollout in the first target defined in the delivery'
' pipeline.'
),
const=True,
)
def AddInitialRolloutGroup(parser):
"""Adds initial-rollout flag group."""
group = parser.add_mutually_exclusive_group()
# Create a group that contains the flags to enable an initial rollout and add
# labels and annotations to that rollout. The group itself is mutually
# exclusive of the disable initial rollout group.
enable_initial_rollout_group = group.add_group(mutex=False)
AddInitialRolloutLabelsFlag().AddToParser(enable_initial_rollout_group)
AddInitialRolloutAnnotationsFlag().AddToParser(enable_initial_rollout_group)
AddInitialRolloutPhaseIDFlag().AddToParser(enable_initial_rollout_group)
AddEnableInitialRolloutFlag().AddToParser(enable_initial_rollout_group)
# Add the disable initial rollout flag to the mutex group.
AddDisableInitialRolloutFlag().AddToParser(group)
def AddJobId(parser, hidden=False):
"""Adds job-id flag."""
parser.add_argument(
'--job-id',
hidden=hidden,
help='Job ID on a rollout resource',
required=True,
)
def AddPhaseId(parser, required=True, hidden=False):
"""Adds phase-id flag."""
parser.add_argument(
'--phase-id',
hidden=hidden,
help='Phase ID on a rollout resource',
required=required,
)
def AddDeployParametersFlag(parser, hidden=False):
"""Add --deploy-parameters flag."""
help_text = textwrap.dedent("""\
Deployment parameters to apply to the release. Deployment parameters take the form of key/value string pairs.
Examples:
Add deployment parameters:
$ {command} --deploy-parameters="key1=value1,key2=value2"
""")
parser.add_argument(
'--deploy-parameters',
metavar='KEY=VALUE',
type=arg_parsers.ArgDict(),
hidden=hidden,
help=help_text,
)
def AddOverrideDeployPolicies(parser, hidden=False):
"""Adds override-deploy-policies flag."""
parser.add_argument(
'--override-deploy-policies',
metavar='POLICY',
hidden=hidden,
type=arg_parsers.ArgList(),
help='Deploy policies to override',
)

View File

@@ -0,0 +1,207 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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 the promote operation."""
from googlecloudsdk.api_lib.clouddeploy import rollout
from googlecloudsdk.command_lib.deploy import exceptions
from googlecloudsdk.command_lib.deploy import release_util
from googlecloudsdk.command_lib.deploy import rollout_util
from googlecloudsdk.command_lib.deploy import target_util
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
_LAST_TARGET_IN_SEQUENCE = (
'Release {} is already deployed to the last target '
'({}) in the promotion sequence.\n- Release: {}\n- Target: {}\n'
)
# In progress List filter
IN_PROGRESS_FILTER_TEMPLATE = 'state="IN_PROGRESS"'
def Promote(
release_ref,
release_obj,
to_target,
is_create,
rollout_id=None,
annotations=None,
labels=None,
description=None,
starting_phase_id=None,
override_deploy_policies=None,
):
"""Creates a rollout for the given release in the destination target.
If to_target is not specified and there is a rollout in progress, the promote
will fail. Otherwise this computes the destination target based on the
promotion sequence.
Args:
release_ref: protorpc.messages.Message, release resource object.
release_obj: apitools.base.protorpclite.messages.Message, release message
object.
to_target: str, the target to promote the release to.
is_create: bool, if creates a rollout during release creation.
rollout_id: str, ID to assign to the generated rollout.
annotations: dict[str,str], a dict of annotation (key,value) pairs that
allow clients to store small amounts of arbitrary data in cloud deploy
resources.
labels: dict[str,str], a dict of label (key,value) pairs that can be used to
select cloud deploy resources and to find collections of cloud deploy
resources that satisfy certain conditions.
description: str, rollout description.
starting_phase_id: str, the starting phase for the rollout.
override_deploy_policies: List of Deploy Policies to override.
Raises:
googlecloudsdk.command_lib.deploy.exceptions.RolloutIdExhausted
googlecloudsdk.command_lib.deploy.exceptions.ReleaseInactiveError
Returns:
The rollout resource created.
"""
dest_target = to_target
if not dest_target:
dest_target = GetToTargetID(release_obj, is_create)
rollout_resource = rollout_util.CreateRollout(
release_ref,
dest_target,
rollout_id,
annotations,
labels,
description,
starting_phase_id,
override_deploy_policies,
)
# Check if it requires approval.
target_obj = release_util.GetSnappedTarget(release_obj, dest_target)
if target_obj and target_obj.requireApproval:
log.status.Print('The rollout is pending approval.')
return rollout_resource
def GetToTargetID(release_obj, is_create):
"""Get the to_target parameter for promote API.
This checks the promotion sequence to get the next stage to promote the
release to.
Args:
release_obj: apitools.base.protorpclite.messages.Message, release message.
is_create: bool, if getting the target for release creation.
Returns:
the target ID.
Raises:
NoStagesError: if no pipeline stages exist in the release.
ReleaseInactiveError: if this is not called during release creation and the
specified release has no rollouts.
"""
if not release_obj.deliveryPipelineSnapshot.serialPipeline.stages:
raise exceptions.NoStagesError(release_obj.name)
# Use release short name to avoid the issue by mixed use of
# the project number and id.
release_ref = resources.REGISTRY.ParseRelativeName(
release_obj.name,
collection='clouddeploy.projects.locations.deliveryPipelines.releases',
)
to_target = release_obj.deliveryPipelineSnapshot.serialPipeline.stages[
0
].targetId
# The order of pipeline stages represents the promotion sequence.
# E.g. test->stage->prod. Here we start with the last stage.
reversed_stages = list(
reversed(release_obj.deliveryPipelineSnapshot.serialPipeline.stages)
)
release_dict = release_ref.AsDict()
for i, stage in enumerate(reversed_stages):
target_ref = target_util.TargetReference(
stage.targetId, release_dict['projectsId'], release_dict['locationsId']
)
# Starting with the last target in the promotion sequence per above, find
# the last successfully deployed rollout to that target.
current_rollout = target_util.GetCurrentRollout(
target_ref,
resources.REGISTRY.Parse(
None,
collection='clouddeploy.projects.locations.deliveryPipelines',
params={
'projectsId': release_dict['projectsId'],
'locationsId': release_dict['locationsId'],
'deliveryPipelinesId': release_dict['deliveryPipelinesId'],
},
),
)
if current_rollout:
current_rollout_ref = resources.REGISTRY.Parse(
current_rollout.name,
collection='clouddeploy.projects.locations.deliveryPipelines.releases.rollouts',
)
# Promotes the release from the target that is farthest along in the
# promotion sequence to its next stage in the promotion sequence.
if current_rollout_ref.Parent().Name() == release_ref.Name():
if i > 0:
to_target = reversed_stages[i - 1].targetId
else:
log.status.Print(
_LAST_TARGET_IN_SEQUENCE.format(
release_ref.Name(),
target_ref.Name(),
release_ref.RelativeName(),
target_ref.RelativeName(),
)
)
to_target = target_ref.RelativeName()
# Once a target to promote to is found break out of the loop
break
# This means the release is not deployed to any target,
# to_target flag is required in this case.
if (
to_target
== release_obj.deliveryPipelineSnapshot.serialPipeline.stages[0].targetId
and not is_create
):
raise exceptions.ReleaseInactiveError()
return target_util.TargetId(to_target)
def CheckIfInProgressRollout(release_ref, release_obj, to_target_id):
"""Checks if there are any rollouts in progress for the given release.
Args:
release_ref: protorpc.messages.Message, release resource object.
release_obj: apitools.base.protorpclite.messages.Message, release message
object.
to_target_id: string, target id (e.g. 'prod')
Raises:
googlecloudsdk.command_lib.deploy.exceptions.RolloutInProgressError
"""
rollouts = list(
rollout.RolloutClient().List(
release_ref.RelativeName(), IN_PROGRESS_FILTER_TEMPLATE, limit=1
)
)
if rollouts:
raise exceptions.RolloutInProgressError(release_obj.name, to_target_id)

View File

@@ -0,0 +1,414 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Resource flags and helpers for the deploy command group."""
from googlecloudsdk.calliope.concepts import concepts
from googlecloudsdk.calliope.concepts import deps
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.core import properties
def DeliveryPipelineAttributeConfig():
"""Creates the delivery pipeline resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='delivery-pipeline',
fallthroughs=[
deps.PropertyFallthrough(
properties.FromString('deploy/delivery_pipeline')
)
],
help_text=(
'The delivery pipeline associated with the {resource}. '
' Alternatively, set the property [deploy/delivery-pipeline].'
),
)
def AddReleaseResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Add --release resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Release.'
concept_parsers.ConceptParser.ForResource(
'release' if positional else '--release',
GetReleaseResourceSpec(),
help_text,
required=required,
plural=False).AddToParser(parser)
def GetReleaseResourceSpec():
"""Constructs and returns the Resource specification for Delivery Pipeline."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deliveryPipelines.releases',
resource_name='release',
deliveryPipelinesId=DeliveryPipelineAttributeConfig(),
releasesId=ReleaseAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False)
def ReleaseAttributeConfig():
"""Creates the release resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='release', help_text='The release associated with the {resource}.')
def LocationAttributeConfig():
"""Creates the location resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='region',
parameter_name='locationsId',
fallthroughs=[
deps.PropertyFallthrough(properties.FromString('deploy/region'))
],
help_text=(
'The Cloud region for the {resource}. '
' Alternatively, set the property [deploy/region].'
),
)
def AddLocationResourceArg(parser):
"""Adds a resource argument for a cloud deploy region.
NOTE: Must be used only if it's the only resource arg in the command.
Args:
parser: argparse.ArgumentParser, the parser for the command.
"""
concept_parsers.ConceptParser.ForResource(
'--region',
GetLocationResourceSpec(),
'The Cloud region of {resource}.',
required=True).AddToParser(parser)
def GetLocationResourceSpec():
"""Constructs and returns the Resource specification for location."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations',
resource_name='location',
locationsId=LocationAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
)
def TargetAttributeConfig():
"""Creates the target resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='target', help_text='The target associated with the {resource}.')
def GetTargetResourceSpec():
"""Constructs and returns the target specification for Target."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.targets',
resource_name='target',
targetsId=TargetAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False)
def AddTargetResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Add target resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Target.'
concept_parsers.ConceptParser.ForResource(
'target' if positional else '--target',
GetTargetResourceSpec(),
help_text,
required=required,
plural=False).AddToParser(parser)
def AddDeliveryPipelineResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Adds --delivery-pipeline resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Delivery Pipeline.'
concept_parsers.ConceptParser.ForResource(
'delivery_pipeline' if positional else '--delivery-pipeline',
GetDeliveryPipelineResourceSpec(),
help_text,
required=required,
plural=False).AddToParser(parser)
def GetDeliveryPipelineResourceSpec():
"""Constructs and returns the Resource specification for Delivery Pipeline."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deliveryPipelines',
resource_name='delivery_pipeline',
deliveryPipelinesId=DeliveryPipelineAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False)
def RolloutAttributeConfig():
"""Creates the rollout resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='rollout', help_text='The rollout associated with the {resource}.')
def GetRolloutResourceSpec():
"""Constructs and returns the resource specification for Rollout."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deliveryPipelines.releases.rollouts',
resource_name='rollout',
deliveryPipelinesId=DeliveryPipelineAttributeConfig(),
releasesId=ReleaseAttributeConfig(),
rolloutsId=RolloutAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False)
def AddRolloutResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Add --rollout resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Rollout.'
concept_parsers.ConceptParser.ForResource(
'rollout' if positional else '--rollout',
GetRolloutResourceSpec(),
help_text,
required=required,
plural=False).AddToParser(parser)
def GetJobRunResourceSpec():
"""Constructs and returns the Resource specification for Job Run."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deliveryPipelines.releases.rollouts.jobRuns',
resource_name='job_run',
deliveryPipelinesId=DeliveryPipelineAttributeConfig(),
releasesId=ReleaseAttributeConfig(),
rolloutsId=RolloutAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False,
)
def AddJobRunResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Add --job-run resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Job Run.'
concept_parsers.ConceptParser.ForResource(
'job_run' if positional else '--job-run',
GetJobRunResourceSpec(),
help_text,
required=required,
plural=False,
).AddToParser(parser)
def AddAutomationRunResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Add --automation-run resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the AutomationRun.'
concept_parsers.ConceptParser.ForResource(
'automation_run' if positional else '--automation-run',
GetAutomationRunResourceSpec(),
help_text,
required=required,
plural=False,
).AddToParser(parser)
def AddAutomationResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Add --automation resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Automation.'
concept_parsers.ConceptParser.ForResource(
'automation' if positional else '--automation',
GetAutomationResourceSpec(),
help_text,
required=required,
plural=False,
).AddToParser(parser)
def GetAutomationRunResourceSpec():
"""Constructs and returns the Resource specification for AutomationRun."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deliveryPipelines.automationRuns',
resource_name='automation_run',
deliveryPipelinesId=DeliveryPipelineAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False,
)
def GetAutomationResourceSpec():
"""Constructs and returns the Resource specification for Automation."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deliveryPipelines.automations',
resource_name='automation',
deliveryPipelinesId=DeliveryPipelineAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False,
)
def CustomTargetTypeAttributeConfig():
"""Creates the Custom Target Type resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='custom_target_type',
help_text='The Custom Target Type associated with the {resource}.',
)
def GetCustomTargetTypeResourceSpec():
"""Constructs and returns the Resource specification for Custom Target Type."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.customTargetTypes',
resource_name='custom_target_type',
customTargetTypesId=CustomTargetTypeAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False,
)
def AddCustomTargetTypeResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Adds --custom-target-type resource argument to the parser.
Args:
parser: argparse.ArgumentPArser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Custom Target Type.'
concept_parsers.ConceptParser.ForResource(
'custom_target_type' if positional else '--custom-target-type',
GetCustomTargetTypeResourceSpec(),
help_text,
required=required,
plural=False,
).AddToParser(parser)
def DeployPolicyAttributeConfig():
"""Creates the Deploy Policy resource attribute."""
return concepts.ResourceParameterAttributeConfig(
name='deploy_policy',
help_text='The Deploy Policy associated with the {resource}.',
)
def GetDeployPolicyResourceSpec():
"""Constructs and returns the Resource specification for Deploy Policy."""
return concepts.ResourceSpec(
'clouddeploy.projects.locations.deployPolicies',
resource_name='deploy policy',
deployPoliciesId=DeployPolicyAttributeConfig(),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
locationsId=LocationAttributeConfig(),
disable_auto_completers=False,
)
def AddDeployPolicyResourceArg(
parser, help_text=None, positional=False, required=True
):
"""Adds --deploy-policy resource argument to the parser.
Args:
parser: argparse.ArgumentParser, the parser for the command.
help_text: help text for this flag.
positional: if it is a positional flag.
required: if it is required.
"""
help_text = help_text or 'The name of the Deploy Policy.'
concept_parsers.ConceptParser.ForResource(
'deploy_policy' if positional else '--deploy_policy',
GetDeployPolicyResourceSpec(),
help_text,
required=required,
plural=False,
).AddToParser(parser)

View File

@@ -0,0 +1,170 @@
project:
name: project
collection: clouddeploy.projects
attributes:
- &project
parameter_name: projectsId
attribute_name: project
help: |
Project of the {resource}.
disable_auto_completers: false
location:
name: location
collection: clouddeploy.projects.locations
disable_auto_completers: false
attributes:
- &location
parameter_name: locationsId
attribute_name: region
help: |
Location of the {resource}.
property: deploy/region
operation:
name: operation
collection: clouddeploy.projects.locations.operations
attributes:
- *project
- *location
- &operation
parameter_name: operationsId
attribute_name: operation
help: |
Operation of the {resource}.
disable_auto_completers: true
delivery_pipeline:
name: delivery_pipeline
collection: clouddeploy.projects.locations.deliveryPipelines
request_id_field: deliveryPipelineId
attributes:
- *project
- *location
- &delivery_pipeline
parameter_name: deliveryPipelinesId
attribute_name: delivery_pipeline
help: |
The name of the Cloud Deploy delivery pipeline.
property: deploy/delivery_pipeline
disable_auto_completers: false
release:
name: release
collection: clouddeploy.projects.locations.deliveryPipelines.releases
request_id_field: releaseId
attributes:
- *project
- *location
- *delivery_pipeline
- &release
parameter_name: releasesId
attribute_name: release
help: |
The name of the Cloud Deploy release.
disable_auto_completers: false
target:
name: target
collection: clouddeploy.projects.locations.targets
request_id_field: targetId
attributes:
- *project
- *location
- parameter_name: targetsId
attribute_name: target
help: |
The name of the Cloud Deploy target.
disable_auto_completers: false
rollout:
name: rollout
collection: clouddeploy.projects.locations.deliveryPipelines.releases.rollouts
request_id_field: rolloutId
attributes:
- *project
- *location
- *delivery_pipeline
- *release
- &rollout
parameter_name: rolloutsId
attribute_name: rollout
help: |
The name of the Cloud Deploy rollout.
disable_auto_completers: false
job_run:
name: job run
collection: clouddeploy.projects.locations.deliveryPipelines.releases.rollouts.jobRuns
request_id_field: jobRunId
attributes:
- *project
- *location
- *delivery_pipeline
- *release
- *rollout
- &job_run
parameter_name: jobRunsId
attribute_name: job_run
help: |
The name of the Cloud Deploy job run.
disable_auto_completers: false
automation_run:
name: automation run
collection: clouddeploy.projects.locations.deliveryPipelines.automationRuns
request_id_field: automationRunId
attributes:
- *project
- *location
- *delivery_pipeline
- &automation_run
parameter_name: automationRunsId
attribute_name: automation_run
help: |
The name of the Cloud Deploy automation run.
disable_auto_completers: false
automation:
name: automation
collection: clouddeploy.projects.locations.deliveryPipelines.automations
request_id_field: automationId
attributes:
- *project
- *location
- *delivery_pipeline
- &automation
parameter_name: automationsId
attribute_name: automation
help: |
The name of the Cloud Deploy automation.
disable_auto_completers: false
custom_target_type:
name: custom target type
collection: clouddeploy.projects.locations.customTargetTypes
request_id_field: customTargetTypeId
attributes:
- *project
- *location
- &custom_target_type
parameter_name: customTargetTypesId
attribute_name: custom_target_type
help: |
The name of the Cloud Deploy custom target type.
disable_auto_completers: false
deploy_policy:
name: deploy policy
collection: clouddeploy.projects.locations.deployPolicies
# The field in the create request that holds the name of the Deploy Policy being created.
request_id_field: deployPolicyId
attributes:
- *project
- *location
- &deploy_policy
parameter_name: deployPoliciesId
attribute_name: deploy_policy
help: |
The name of the Cloud Deploy Deploy Policy.
disable_auto_completers: false

View File

@@ -0,0 +1,287 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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 the cloud deploy rollout resource."""
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.clouddeploy import client_util
from googlecloudsdk.api_lib.clouddeploy import release
from googlecloudsdk.api_lib.clouddeploy import rollout
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.deploy import exceptions as cd_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
from googlecloudsdk.generated_clients.apis.clouddeploy.v1 import clouddeploy_v1_messages
_ROLLOUT_COLLECTION = (
'clouddeploy.projects.locations.deliveryPipelines.releases.rollouts'
)
PENDING_APPROVAL_FILTER_TEMPLATE = (
'approvalState="NEEDS_APPROVAL" AND '
'state="PENDING_APPROVAL" AND targetId="{}"'
)
DEPLOYED_ROLLOUT_FILTER_TEMPLATE = (
'(approvalState!="REJECTED" AND '
'approvalState!="NEEDS_APPROVAL") AND state="SUCCEEDED" AND targetId="{}"'
)
ROLLOUT_IN_TARGET_FILTER_TEMPLATE = 'targetId="{}"'
ROLLOUT_ID_TEMPLATE = '{}-to-{}-{:04d}'
WILDCARD_RELEASE_NAME_TEMPLATE = '{}/releases/-'
SUCCEED_ROLLOUT_ORDERBY = 'DeployEndTime desc'
PENDING_ROLLOUT_ORDERBY = 'CreateTime desc'
ENQUEUETIME_ROLLOUT_ORDERBY = 'EnqueueTime desc'
def RolloutReferenceFromName(rollout_name):
"""Returns a rollout reference object from a rollout message.
Args:
rollout_name: str, full canonical resource name of the rollout
Returns:
Rollout reference object
"""
return resources.REGISTRY.ParseRelativeName(
rollout_name, collection=_ROLLOUT_COLLECTION
)
def RolloutId(rollout_name_or_id):
"""Returns rollout ID.
Args:
rollout_name_or_id: str, rollout full name or ID.
Returns:
Rollout ID.
"""
rollout_id = rollout_name_or_id
if 'projects/' in rollout_name_or_id:
rollout_id = resources.REGISTRY.ParseRelativeName(
rollout_name_or_id, collection=_ROLLOUT_COLLECTION
).Name()
return rollout_id
def ListPendingRollouts(target_ref, pipeline_ref):
"""Lists the rollouts in PENDING_APPROVAL state for the releases associated with the specified target.
The rollouts must be approvalState=NEEDS_APPROVAL and
state=PENDING_APPROVAL. The returned list is sorted by rollout's create
time.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_ref: protorpc.messages.Message, pipeline object.
Returns:
a sorted list of rollouts.
"""
filter_str = PENDING_APPROVAL_FILTER_TEMPLATE.format(target_ref.Name())
parent = WILDCARD_RELEASE_NAME_TEMPLATE.format(pipeline_ref.RelativeName())
return rollout.RolloutClient().List(
release_name=parent,
filter_str=filter_str,
order_by=PENDING_ROLLOUT_ORDERBY,
)
def GetFilteredRollouts(
target_ref, pipeline_ref, filter_str, order_by, page_size=None, limit=None
):
"""Gets successfully deployed rollouts for the releases associated with the specified target and index.
Args:
target_ref: protorpc.messages.Message, target object.
pipeline_ref: protorpc.messages.Message, pipeline object.
filter_str: Filter string to use when listing rollouts.
order_by: order_by field to use when listing rollouts.
page_size: int, the maximum number of objects to return per page.
limit: int, the maximum number of `Rollout` objects to return.
Returns:
a rollout object or None if no rollouts in the target.
"""
parent = WILDCARD_RELEASE_NAME_TEMPLATE.format(pipeline_ref.RelativeName())
return rollout.RolloutClient().List(
release_name=parent,
filter_str=filter_str.format(target_ref.Name()),
order_by=order_by,
page_size=page_size,
limit=limit,
)
def GenerateRolloutId(to_target, release_ref) -> str:
filter_str = ROLLOUT_IN_TARGET_FILTER_TEMPLATE.format(to_target)
try:
rollouts = rollout.RolloutClient().List(
release_ref.RelativeName(), filter_str
)
return ComputeRolloutID(release_ref.Name(), to_target, rollouts)
except apitools_exceptions.HttpError:
raise cd_exceptions.ListRolloutsError(release_ref.RelativeName())
def CreateRollout(
release_ref,
to_target,
rollout_id=None,
annotations=None,
labels=None,
description=None,
starting_phase_id=None,
override_deploy_policies=None,
) -> clouddeploy_v1_messages.Rollout:
"""Creates a rollout by calling the rollout create API and waits for the operation to finish.
Args:
release_ref: protorpc.messages.Message, release resource object.
to_target: str, the target to create create the rollout in.
rollout_id: str, rollout ID.
annotations: dict[str,str], a dict of annotation (key,value) pairs that
allow clients to store small amounts of arbitrary data in cloud deploy
resources.
labels: dict[str,str], a dict of label (key,value) pairs that can be used to
select cloud deploy resources and to find collections of cloud deploy
resources that satisfy certain conditions.
description: str, rollout description.
starting_phase_id: str, rollout starting phase.
override_deploy_policies: List of Deploy Policies to override.
Raises:
ListRolloutsError: an error occurred calling rollout list API.
Returns:
The rollout resource created.
"""
final_rollout_id = rollout_id
if not final_rollout_id:
final_rollout_id = GenerateRolloutId(to_target, release_ref)
resource_dict = release_ref.AsDict()
rollout_ref = resources.REGISTRY.Parse(
final_rollout_id,
collection=_ROLLOUT_COLLECTION,
params={
'projectsId': resource_dict.get('projectsId'),
'locationsId': resource_dict.get('locationsId'),
'deliveryPipelinesId': resource_dict.get('deliveryPipelinesId'),
'releasesId': release_ref.Name(),
},
)
rollout_obj = client_util.GetMessagesModule().Rollout(
name=rollout_ref.RelativeName(),
targetId=to_target,
description=description,
)
log.status.Print(
'Creating rollout {} in target {}'.format(
rollout_ref.RelativeName(), to_target
)
)
operation = rollout.RolloutClient().Create(
rollout_ref,
rollout_obj,
annotations,
labels,
starting_phase_id,
override_deploy_policies,
)
operation_ref = resources.REGISTRY.ParseRelativeName(
operation.name, collection='clouddeploy.projects.locations.operations'
)
client_util.OperationsClient().WaitForOperation(
operation,
operation_ref,
'Waiting for rollout creation operation to complete',
)
return rollout.RolloutClient().Get(rollout_ref.RelativeName())
def GetValidRollBackCandidate(target_ref, pipeline_ref):
"""Gets the currently deployed release and the next valid release that can be rolled back to.
Args:
target_ref: protorpc.messages.Message, target resource object.
pipeline_ref: protorpc.messages.Message, pipeline resource object.
Raises:
HttpException: an error occurred fetching a resource.
Returns:
An list containg the currently deployed release and the next valid
deployable release.
"""
iterable = GetFilteredRollouts(
target_ref=target_ref,
pipeline_ref=pipeline_ref,
filter_str=DEPLOYED_ROLLOUT_FILTER_TEMPLATE,
order_by=SUCCEED_ROLLOUT_ORDERBY,
limit=None,
page_size=10,
)
rollouts = []
for rollout_obj in iterable:
if not rollouts: # Currently deployed rollout in target
rollouts.append(rollout_obj)
elif not _RolloutIsFromAbandonedRelease(rollout_obj):
rollouts.append(rollout_obj)
if len(rollouts) >= 2:
break
return rollouts
def _RolloutIsFromAbandonedRelease(rollout_obj):
rollout_ref = RolloutReferenceFromName(rollout_obj.name)
release_ref = rollout_ref.Parent()
try:
release_obj = release.ReleaseClient().Get(release_ref.RelativeName())
except apitools_exceptions.HttpError as error:
raise exceptions.HttpException(error)
return release_obj.abandoned
def ComputeRolloutID(release_id, target_id, rollouts) -> str:
"""Generates a rollout ID.
Args:
release_id: str, release ID.
target_id: str, target ID.
rollouts: [apitools.base.protorpclite.messages.Message], list of rollout
messages.
Returns:
rollout ID.
Raises:
googlecloudsdk.command_lib.deploy.exceptions.RolloutIdExhaustedError: if
there are more than 1000 rollouts with auto-generated ID.
"""
rollout_ids = {RolloutId(r.name) for r in rollouts}
for i in range(1, 1001):
# If the rollout ID is too long, the resource will fail to be created.
# It is up to the user to mitigate this by passing an explicit rollout ID
# to use, instead.
rollout_id = ROLLOUT_ID_TEMPLATE.format(release_id, target_id, i)
if rollout_id not in rollout_ids:
return rollout_id
raise cd_exceptions.RolloutIDExhaustedError(release_id)

View File

@@ -0,0 +1,86 @@
# -*- 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.
"""Helper methods to generate a skaffold file."""
import collections
from googlecloudsdk.core import yaml
CLOUD_RUN_GENERATED_SKAFFOLD_TEMPLATE = """\
apiVersion: skaffold/v3alpha1
kind: Config
manifests:
rawYaml:
- {}
deploy:
cloudrun: {{}}
"""
GKE_GENERATED_SKAFFOLD_TEMPLATE = """\
apiVersion: skaffold/v2beta28
kind: Config
deploy:
kubectl:
manifests:
- {}
"""
def _GetUniqueProfiles(pipeline_obj):
"""Gets unique profiles from pipeline_obj."""
profiles = set()
for stage in pipeline_obj.serialPipeline.stages:
for profile in stage.profiles:
profiles.add(profile)
return profiles
def _AddProfiles(skaffold, pipeline_obj):
"""Adds the profiles in the provided pipeline to the skaffold configuration."""
profiles = _GetUniqueProfiles(pipeline_obj)
if not profiles:
return
skaffold['profiles'] = []
for profile in profiles:
skaffold['profiles'].append(collections.OrderedDict([('name', profile)]))
return
def CreateSkaffoldFileForManifest(pipeline_obj, manifest, template):
"""Creates skaffold file when a cloud run or GKE manifest is provided to the release create command.
Args:
pipeline_obj: A Delivery Pipeline object, the profiles in the Delivery
Pipeline stages will be added to the skaffold file.
manifest: The name of the manifest file.
template: The skaffold.yaml template.
Returns:
skaffold yaml.
"""
skaffold = yaml.load(
template.format(manifest),
round_trip=True,
)
_AddProfiles(skaffold, pipeline_obj)
return skaffold

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Support library to handle the staging bucket."""
import six
def GetDefaultStagingBucket(pipeline_uuid):
"""Returns the default source staging bucket."""
if not pipeline_uuid:
raise ValueError(
'Expected a value for pipeline uid but the string is either empty or "None"'
)
uid_str = six.text_type(pipeline_uuid)
bucket_name = uid_str + '_clouddeploy'
if len(bucket_name) > 63:
raise ValueError(
'The length of the bucket id: {} must not exceed 63 characters'.format(
bucket_name))
return bucket_name

View File

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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 the cloud deploy target resource."""
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.clouddeploy import target
from googlecloudsdk.command_lib.deploy import rollout_util
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
_SHARED_TARGET_COLLECTION = 'clouddeploy.projects.locations.targets'
def GetCurrentRollout(target_ref, pipeline_ref):
"""Gets the releases in the specified target and the last deployment associated with the target.
Args:
target_ref: protorpc.messages.Message, target resource object.
pipeline_ref: protorpc.messages.Message, pipeline object.
Returns:
release messages associated with the target.
last deployed rollout message.
Raises:
Exceptions raised by RolloutClient.GetCurrentRollout()
"""
current_rollout = None
try:
# find the last deployed rollout.
rollouts = list(
rollout_util.GetFilteredRollouts(
target_ref,
pipeline_ref,
filter_str=rollout_util.DEPLOYED_ROLLOUT_FILTER_TEMPLATE,
order_by=rollout_util.SUCCEED_ROLLOUT_ORDERBY,
limit=1))
if rollouts:
current_rollout = rollouts[0]
except apitools_exceptions.HttpError as error:
log.debug('failed to get the current rollout of target {}: {}'.format(
target_ref.RelativeName(), error.content))
return current_rollout
def TargetReferenceFromName(target_name):
"""Creates a target reference from full name.
Args:
target_name: str, target resource name.
Returns:
Target reference.
"""
return resources.REGISTRY.ParseRelativeName(
target_name, collection=_SHARED_TARGET_COLLECTION)
def TargetId(target_name_or_id):
"""Returns target ID.
Args:
target_name_or_id: str, target full name or ID.
Returns:
Target ID.
"""
if 'projects/' in target_name_or_id:
return TargetReferenceFromName(target_name_or_id).Name()
return target_name_or_id
def TargetReference(target_name_or_id, project, location_id):
"""Creates the target reference base on the parameters.
Returns the shared target reference.
Args:
target_name_or_id: str, target full name or ID.
project: str,project number or ID.
location_id: str, region ID.
Returns:
Target reference.
"""
return resources.REGISTRY.Parse(
None,
collection=_SHARED_TARGET_COLLECTION,
params={
'projectsId': project,
'locationsId': location_id,
'targetsId': TargetId(target_name_or_id),
})
def GetTarget(target_ref):
"""Gets the target message by calling the get target API.
Args:
target_ref: protorpc.messages.Message, protorpc.messages.Message, target
reference.
Returns:
Target message.
Raises:
Exceptions raised by TargetsClient's get functions
"""
return target.TargetsClient().Get(target_ref.RelativeName())
def PatchTarget(target_obj):
"""Patches a target resource by calling the patch target API.
Args:
target_obj: apitools.base.protorpclite.messages.Message, target message.
Returns:
The operation message.
"""
return target.TargetsClient().Patch(target_obj)
def DeleteTarget(name):
"""Deletes a target resource by calling the delete target API.
Args:
name: str, target name.
Returns:
The operation message.
"""
return target.TargetsClient().Delete(name)
def ListTarget(parent_name):
"""List target resources by calling the list target API.
Args:
parent_name: str, the name of the collection that owns the targets.
Returns:
List of targets returns from target list API call.
"""
return target.TargetsClient().List(parent_name)