def test_minimal(self): project = self.create_project() version = "bbee5b51f84611e4b14834363b8514c2" data_list = [ { "id": "c7155651831549cf8a5e47889fce17eb", "message": "foo", "author_email": "*****@*****.**", }, { "id": "bbee5b51f84611e4b14834363b8514c2", "message": "bar", "author_name": "Joe^^" }, ] hook = ReleaseHook(project) hook.set_commits(version, data_list) release = Release.objects.get(projects=project, version=version) commit_list = list( Commit.objects.filter(releasecommit__release=release). select_related("author").order_by("releasecommit__order")) assert len(commit_list) == 2 assert commit_list[0].key == "c7155651831549cf8a5e47889fce17eb" assert commit_list[0].message == "foo" assert commit_list[0].author.name is None assert commit_list[0].author.email == "*****@*****.**" assert commit_list[1].key == "bbee5b51f84611e4b14834363b8514c2" assert commit_list[1].message == "bar" assert commit_list[1].author.name == "Joe^^" assert commit_list[1].author.email == "joe@localhost"
def test_minimal(self): project = self.create_project() version = "bbee5b51f84611e4b14834363b8514c2" hook = ReleaseHook(project) hook.start_release(version) release = Release.objects.get(organization_id=project.organization_id, version=version) assert release.organization assert ReleaseProject.objects.get(release=release, project=project)
def test_update_release(self): project = self.create_project() version = "bbee5b51f84611e4b14834363b8514c2" r = Release.objects.create(organization_id=project.organization_id, version=version) r.add_project(project) hook = ReleaseHook(project) hook.start_release(version) release = Release.objects.get(projects=project, version=version) assert release.organization == project.organization
def test_bad_version(self): project = self.create_project() hook = ReleaseHook(project) version = "" with self.assertRaises(HookValidationError): hook.start_release(version) with self.assertRaises(HookValidationError): hook.finish_release(version) with self.assertRaises(HookValidationError): hook.set_commits(version, []) version = "." with self.assertRaises(HookValidationError): hook.start_release(version) with self.assertRaises(HookValidationError): hook.finish_release(version) with self.assertRaises(HookValidationError): hook.set_commits(version, []) version = ".." with self.assertRaises(HookValidationError): hook.start_release(version) with self.assertRaises(HookValidationError): hook.finish_release(version) with self.assertRaises(HookValidationError): hook.set_commits(version, [])
def post(self, request, project): """ Create a New Release for a Project `````````````````````````````````` Create a new release and/or associate a project with a release. Release versions that are the same across multiple projects within an Organization will be treated as the same release in Sentry. Releases are used by Sentry to improve its error reporting abilities by correlating first seen events with the release that might have introduced the problem. Releases are also necessary for sourcemaps and other debug features that require manual upload for functioning well. :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to create a release for. :param string version: a version identifier for this release. Can be a version number, a commit hash etc. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateStarted: an optional date that indicates when the release process started. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ serializer = ReleaseSerializer(data=request.DATA) if serializer.is_valid(): result = serializer.object # release creation is idempotent to simplify user # experiences try: with transaction.atomic(): release, created = Release.objects.create( organization_id=project.organization_id, version=result['version'], ref=result.get('ref'), url=result.get('url'), owner=result.get('owner'), date_started=result.get('dateStarted'), date_released=result.get('dateReleased'), ), True was_released = False except IntegrityError: release, created = Release.objects.get( organization_id=project.organization_id, version=result['version'], ), False was_released = bool(release.date_released) created = release.add_project(project) commit_list = result.get('commits') if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if (not was_released and release.date_released): Activity.objects.create( type=Activity.RELEASE, project=project, ident=result['version'], data={'version': result['version']}, datetime=release.date_released, ) if not created: # This is the closest status code that makes sense, and we want # a unique 2xx response code so people can understand when # behavior differs. # 208 Already Reported (WebDAV; RFC 5842) status = 208 else: status = 201 return Response(serialize(release, request.user), status=status) return Response(serializer.errors, status=400)
def put(self, request, project, version): """ Update a Project's Release `````````````````````````` Update a release. This can change some metadata associated with the release (the ref, url, and dates). :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to change the release of. :pparam string version: the version identifier of the release. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ try: release = Release.objects.get( organization_id=project.organization_id, projects=project, version=version) except Release.DoesNotExist: raise ResourceDoesNotExist serializer = ReleaseSerializer(data=request.data, partial=True) if not serializer.is_valid(): return Response(serializer.errors, status=400) result = serializer.validated_data was_released = bool(release.date_released) kwargs = {} if result.get("dateReleased"): kwargs["date_released"] = result["dateReleased"] if result.get("ref"): kwargs["ref"] = result["ref"] if result.get("url"): kwargs["url"] = result["url"] if kwargs: release.update(**kwargs) commit_list = result.get("commits") if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if not was_released and release.date_released: Activity.objects.create( type=Activity.RELEASE, project=project, ident=Activity.get_version_ident(release.version), data={"version": release.version}, datetime=release.date_released, ) return Response(serialize(release, request.user))
def put(self, request, project, version): """ Update a Release ```````````````` Update a release. This can change some metadata associated with the release (the ref, url, and dates). :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to change the release of. :pparam string version: the version identifier of the release. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateStarted: an optional date that indicates when the release process started. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ try: release = Release.objects.get( project=project, version=version, ) except Release.DoesNotExist: raise ResourceDoesNotExist serializer = ReleaseSerializer(data=request.DATA, partial=True) if not serializer.is_valid(): return Response(serializer.errors, status=400) result = serializer.object was_released = bool(release.date_released) kwargs = {} if result.get('dateStarted'): kwargs['date_started'] = result['dateStarted'] if result.get('dateReleased'): kwargs['date_released'] = result['dateReleased'] if result.get('ref'): kwargs['ref'] = result['ref'] if result.get('url'): kwargs['url'] = result['url'] if kwargs: release.update(**kwargs) commit_list = result.get('commits') if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if (not was_released and release.date_released): activity = Activity.objects.create( type=Activity.RELEASE, project=project, ident=release.version, data={'version': release.version}, datetime=release.date_released, ) activity.send_notification() return Response(serialize(release, request.user))
def post(self, request, project): """ Create a New Release for a Project `````````````````````````````````` Create a new release and/or associate a project with a release. Release versions that are the same across multiple projects within an Organization will be treated as the same release in Sentry. Releases are used by Sentry to improve its error reporting abilities by correlating first seen events with the release that might have introduced the problem. Releases are also necessary for sourcemaps and other debug features that require manual upload for functioning well. :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to create a release for. :param string version: a version identifier for this release. Can be a version number, a commit hash etc. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ bind_organization_context(project.organization) serializer = ReleaseWithVersionSerializer(data=request.data) with configure_scope() as scope: if serializer.is_valid(): result = serializer.validated_data scope.set_tag("version", result["version"]) # release creation is idempotent to simplify user # experiences try: with transaction.atomic(): release, created = ( Release.objects.create( organization_id=project.organization_id, version=result["version"], ref=result.get("ref"), url=result.get("url"), owner=result.get("owner"), date_released=result.get("dateReleased"), ), True, ) was_released = False except IntegrityError: release, created = ( Release.objects.get( organization_id=project.organization_id, version=result["version"]), False, ) was_released = bool(release.date_released) else: release_created.send_robust(release=release, sender=self.__class__) created = release.add_project(project) commit_list = result.get("commits") if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if not was_released and release.date_released: Activity.objects.create( type=Activity.RELEASE, project=project, ident=Activity.get_version_ident(result["version"]), data={"version": result["version"]}, datetime=release.date_released, ) if not created: # This is the closest status code that makes sense, and we want # a unique 2xx response code so people can understand when # behavior differs. # 208 Already Reported (WebDAV; RFC 5842) status = 208 else: status = 201 analytics.record( "release.created", user_id=request.user.id if request.user and request.user.id else None, organization_id=project.organization_id, project_ids=[project.id], user_agent=request.META.get("HTTP_USER_AGENT", ""), created_status=status, ) scope.set_tag("success_status", status) return Response(serialize(release, request.user), status=status) scope.set_tag("failure_reason", "serializer_error") return Response(serializer.errors, status=400)
def post(self, request, project): """ Create a New Release ```````````````````` Create a new release and/or associate a project with a release. Release versions that are the same across multiple projects within an Organization will be treated as the same release in Sentry. Releases are used by Sentry to improve its error reporting abilities by correlating first seen events with the release that might have introduced the problem. Releases are also necessary for sourcemaps and other debug features that require manual upload for functioning well. :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to create a release for. :param string version: a version identifier for this release. Can be a version number, a commit hash etc. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateStarted: an optional date that indicates when the release process started. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ serializer = ReleaseSerializer(data=request.DATA) if serializer.is_valid(): result = serializer.object # release creation is idempotent to simplify user # experiences release = Release.objects.filter( organization_id=project.organization_id, version=result['version'], projects=project).first() created = False if release: was_released = bool(release.date_released) else: release = Release.objects.filter( organization_id=project.organization_id, version=result['version'], ).first() if not release: lock_key = Release.get_lock_key(project.organization_id, result['version']) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release, created = Release.objects.get( version=result['version'], organization_id=project.organization_id), False except Release.DoesNotExist: release, created = Release.objects.create( organization_id=project.organization_id, version=result['version'], ref=result.get('ref'), url=result.get('url'), owner=result.get('owner'), date_started=result.get('dateStarted'), date_released=result.get('dateReleased'), ), True was_released = False try: with transaction.atomic(): ReleaseProject.objects.create(project=project, release=release) created = True except IntegrityError: pass commit_list = result.get('commits') if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if (not was_released and release.date_released): activity = Activity.objects.create( type=Activity.RELEASE, project=project, ident=result['version'], data={'version': result['version']}, datetime=release.date_released, ) activity.send_notification() if not created: # This is the closest status code that makes sense, and we want # a unique 2xx response code so people can understand when # behavior differs. # 208 Already Reported (WebDAV; RFC 5842) status = 208 else: status = 201 return Response(serialize(release, request.user), status=status) return Response(serializer.errors, status=400)