-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
release_deploys.py
181 lines (153 loc) · 7.36 KB
/
release_deploys.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import logging
from django.db.models import F
from django.utils import timezone
from rest_framework import serializers
from rest_framework.request import Request
from rest_framework.response import Response
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.bases.organization import OrganizationReleasesBaseEndpoint
from sentry.api.exceptions import ParameterValidationError, ResourceDoesNotExist
from sentry.api.paginator import OffsetPaginator
from sentry.api.serializers import serialize
from sentry.api.serializers.rest_framework.project import ProjectField
from sentry.models.deploy import Deploy
from sentry.models.environment import Environment
from sentry.models.release import Release
from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment
from sentry.signals import deploy_created
logger = logging.getLogger(__name__)
class DeploySerializer(serializers.Serializer):
name = serializers.CharField(max_length=64, required=False, allow_blank=True, allow_null=True)
environment = serializers.CharField(max_length=64)
url = serializers.URLField(required=False, allow_blank=True, allow_null=True)
dateStarted = serializers.DateTimeField(required=False, allow_null=True)
dateFinished = serializers.DateTimeField(required=False, allow_null=True)
projects = serializers.ListField(
child=ProjectField(scope="project:read"), required=False, allow_empty=False
)
def validate_environment(self, value):
if not Environment.is_valid_name(value):
raise serializers.ValidationError("Invalid value for environment")
return value
@region_silo_endpoint
class ReleaseDeploysEndpoint(OrganizationReleasesBaseEndpoint):
publish_status = {
"GET": ApiPublishStatus.UNKNOWN,
"POST": ApiPublishStatus.UNKNOWN,
}
def get(self, request: Request, organization, version) -> Response:
"""
List a Release's Deploys
````````````````````````
Return a list of deploys for a given release.
:pparam string organization_slug: the organization short name
:pparam string version: the version identifier of the release.
"""
try:
release = Release.objects.get(version=version, organization=organization)
except Release.DoesNotExist:
raise ResourceDoesNotExist
if not self.has_release_permission(request, organization, release):
raise ResourceDoesNotExist
queryset = Deploy.objects.filter(organization_id=organization.id, release=release)
return self.paginate(
request=request,
queryset=queryset,
order_by="-date_finished",
paginator_cls=OffsetPaginator,
on_results=lambda x: serialize(x, request.user),
)
def post(self, request: Request, organization, version) -> Response:
"""
Create a Deploy
```````````````
Create a deploy for a given release.
:pparam string organization_slug: the organization short name
:pparam string version: the version identifier of the release.
:param string environment: the environment you're deploying to
:param string name: the optional name of the deploy
:param list projects: the optional list of project slugs to
create a deploy within. If not provided, deploys
are created for all of the release's projects.
:param url url: the optional url that points to the deploy
:param datetime dateStarted: an optional date that indicates when
the deploy started
:param datetime dateFinished: an optional date that indicates when
the deploy ended. If not provided, the
current time is used.
"""
logging_info = {
"org_slug": organization.slug,
"org_id": organization.id,
"version": version,
}
try:
release = Release.objects.get(version=version, organization=organization)
except Release.DoesNotExist:
logger.info(
"create_release_deploy.release_not_found",
extra=logging_info,
)
raise ResourceDoesNotExist
if not self.has_release_permission(request, organization, release):
# Logic here copied from `has_release_permission` (lightly edited for results to be more
# human-readable)
auth = None
if getattr(request, "user", None) and request.user.id:
auth = f"user.id: {request.user.id}"
elif getattr(request, "auth", None) and getattr(request.auth, "id", None):
auth = f"auth.id: {request.auth.id}" # type: ignore
elif getattr(request, "auth", None) and getattr(request.auth, "entity_id", None):
auth = f"auth.entity_id: {request.auth.entity_id}" # type: ignore
if auth is not None:
logging_info.update({"auth": auth})
logger.info(
"create_release_deploy.no_release_permission",
extra=logging_info,
)
raise ResourceDoesNotExist
serializer = DeploySerializer(
data=request.data, context={"organization": organization, "access": request.access}
)
if serializer.is_valid():
result = serializer.validated_data
release_projects = list(release.projects.all())
projects = result.get("projects", release_projects)
invalid_projects = {project.slug for project in projects} - {
project.slug for project in release_projects
}
if len(invalid_projects) > 0:
raise ParameterValidationError(
f"Invalid projects ({', '.join(invalid_projects)}) for release {release.version}"
)
env = Environment.objects.get_or_create(
name=result["environment"], organization_id=organization.id
)[0]
for project in projects:
env.add_project(project)
deploy = Deploy.objects.create(
organization_id=organization.id,
release=release,
environment_id=env.id,
date_finished=result.get("dateFinished", timezone.now()),
date_started=result.get("dateStarted"),
name=result.get("name"),
url=result.get("url"),
)
deploy_created.send_robust(deploy=deploy, sender=self.__class__)
# XXX(dcramer): this has a race for most recent deploy, but
# should be unlikely to hit in the real world
Release.objects.filter(id=release.id).update(
total_deploys=F("total_deploys") + 1, last_deploy_id=deploy.id
)
for project in projects:
ReleaseProjectEnvironment.objects.create_or_update(
release=release,
environment=env,
project=project,
values={"last_deploy_id": deploy.id},
)
Deploy.notify_if_ready(deploy.id)
return Response(serialize(deploy, request.user), status=201)
return Response(serializer.errors, status=400)