def post(self, request, organization): """ Create a New Release for an Organization ```````````````````````````````````````` Create a new release for the given Organization. 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. :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 array projects: a list of project slugs that are involved in this release :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :param array commits: an optional list of commit data to be associated with the release. Commits must include parameters ``id`` (the sha of the commit), and can optionally include ``repository``, ``message``, ``patch_set``, ``author_name``, ``author_email``, and ``timestamp``. See [release without integration example](/workflow/releases/). :param array refs: an optional way to indicate the start and end commits for each repository included in a release. Head commits must include parameters ``repository`` and ``commit`` (the HEAD sha). They can optionally include ``previousCommit`` (the sha of the HEAD of the previous release), which should be specified if this is the first time you've sent commit data. ``commit`` may contain a range in the form of ``previousCommit..commit`` :auth: required """ serializer = ReleaseSerializerWithProjects(data=request.data) if serializer.is_valid(): result = serializer.validated_data allowed_projects = { p.slug: p for p in self.get_projects(request, organization) } projects = [] for slug in result['projects']: if slug not in allowed_projects: return Response({'projects': ['Invalid project slugs']}, status=400) projects.append(allowed_projects[slug]) # release creation is idempotent to simplify user # experiences try: with transaction.atomic(): release, created = Release.objects.create( organization_id=organization.id, version=result['version'], ref=result.get('ref'), url=result.get('url'), owner=result.get('owner'), date_released=result.get('dateReleased'), ), True except IntegrityError: release, created = Release.objects.get( organization_id=organization.id, version=result['version'], ), False else: release_created.send_robust(release=release, sender=self.__class__) new_projects = [] for project in projects: created = release.add_project(project) if created: new_projects.append(project) if release.date_released: for project in new_projects: Activity.objects.create( type=Activity.RELEASE, project=project, ident=Activity.get_version_ident(result['version']), data={'version': result['version']}, datetime=release.date_released, ) commit_list = result.get('commits') if commit_list: release.set_commits(commit_list) refs = result.get('refs') if not refs: refs = [{ 'repository': r['repository'], 'previousCommit': r.get('previousId'), 'commit': r['currentId'], } for r in result.get('headCommits', [])] if refs: if not request.user.is_authenticated(): return Response( { 'refs': [ 'You must use an authenticated API token to fetch refs' ] }, status=400) fetch_commits = not commit_list try: release.set_refs(refs, request.user, fetch=fetch_commits) except InvalidRepository as exc: return Response({'refs': [exc.message]}, status=400) if not created and not new_projects: # 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 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 """ 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_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 return Response(serialize(release, request.user), status=status) return Response(serializer.errors, status=400)
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: Request, organization) -> Response: """ Create a New Release for an Organization ```````````````````````````````````````` Create a new release for the given Organization. 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. :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 array projects: a list of project slugs that are involved in this release :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :param array commits: an optional list of commit data to be associated with the release. Commits must include parameters ``id`` (the sha of the commit), and can optionally include ``repository``, ``message``, ``patch_set``, ``author_name``, ``author_email``, and ``timestamp``. See [release without integration example](/workflow/releases/). :param array refs: an optional way to indicate the start and end commits for each repository included in a release. Head commits must include parameters ``repository`` and ``commit`` (the HEAD sha). They can optionally include ``previousCommit`` (the sha of the HEAD of the previous release), which should be specified if this is the first time you've sent commit data. ``commit`` may contain a range in the form of ``previousCommit..commit`` :auth: required """ bind_organization_context(organization) serializer = ReleaseSerializerWithProjects( data=request.data, context={"organization": organization}) with configure_scope() as scope: if serializer.is_valid(): result = serializer.validated_data scope.set_tag("version", result["version"]) allowed_projects = { p.slug: p for p in self.get_projects(request, organization) } projects = [] for slug in result["projects"]: if slug not in allowed_projects: return Response( {"projects": ["Invalid project slugs"]}, status=400) projects.append(allowed_projects[slug]) new_status = result.get("status") # release creation is idempotent to simplify user # experiences try: release, created = Release.objects.get_or_create( organization_id=organization.id, version=result["version"], defaults={ "ref": result.get("ref"), "url": result.get("url"), "owner": result.get("owner"), "date_released": result.get("dateReleased"), "status": new_status or ReleaseStatus.OPEN, }, ) except IntegrityError: raise ConflictError( "Could not create the release it conflicts with existing data", ) if created: release_created.send_robust(release=release, sender=self.__class__) if not created and new_status is not None and new_status != release.status: release.status = new_status release.save() new_projects = [] for project in projects: created = release.add_project(project) if created: new_projects.append(project) if release.date_released: for project in new_projects: Activity.objects.create( type=Activity.RELEASE, project=project, ident=Activity.get_version_ident( result["version"]), data={"version": result["version"]}, datetime=release.date_released, ) commit_list = result.get("commits") if commit_list: try: release.set_commits(commit_list) self.track_set_commits_local( request, organization_id=organization.id, project_ids=[project.id for project in projects], ) except ReleaseCommitError: raise ConflictError( "Release commits are currently being processed") refs = result.get("refs") if not refs: refs = [{ "repository": r["repository"], "previousCommit": r.get("previousId"), "commit": r["currentId"], } for r in result.get("headCommits", [])] scope.set_tag("has_refs", bool(refs)) if refs: if not request.user.is_authenticated: scope.set_tag("failure_reason", "user_not_authenticated") return Response( { "refs": [ "You must use an authenticated API token to fetch refs" ] }, status=400, ) fetch_commits = not commit_list try: release.set_refs(refs, request.user, fetch=fetch_commits) except InvalidRepository as e: scope.set_tag("failure_reason", "InvalidRepository") return Response({"refs": [str(e)]}, status=400) if not created and not new_projects: # 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=organization.id, project_ids=[project.id for project in projects], 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, organization): """ Create a New Release for an Organization ```````````````````````````````````````` Create a new release for the given Organization. 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. :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 array projects: a list of project slugs that are involved in this release :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :param array commits: an optional list of commit data to be associated with the release. Commits must include parameters ``id`` (the sha of the commit), and can optionally include ``repository``, ``message``, ``author_name``, ``author_email``, and ``timestamp``. :param array refs: an optional way to indicate the start and end commits for each repository included in a release. Head commits must include parameters ``repository`` and ``commit`` (the HEAD sha). They can optionally include ``previousCommit`` (the sha of the HEAD of the previous release), which should be specified if this is the first time you've sent commit data. :auth: required """ serializer = ReleaseSerializerWithProjects(data=request.DATA) if serializer.is_valid(): result = serializer.object allowed_projects = { p.slug: p for p in self.get_allowed_projects( request, organization)} projects = [] for slug in result['projects']: if slug not in allowed_projects: return Response({'projects': ['Invalid project slugs']}, status=400) projects.append(allowed_projects[slug]) # release creation is idempotent to simplify user # experiences try: with transaction.atomic(): release, created = Release.objects.create( organization_id=organization.id, version=result['version'], ref=result.get('ref'), url=result.get('url'), owner=result.get('owner'), date_released=result.get('dateReleased'), ), True except IntegrityError: release, created = Release.objects.get( organization_id=organization.id, version=result['version'], ), False else: release_created.send_robust(release=release, sender=self.__class__) new_projects = [] for project in projects: created = release.add_project(project) if created: new_projects.append(project) if release.date_released: for project in new_projects: Activity.objects.create( type=Activity.RELEASE, project=project, ident=Activity.get_version_ident(result['version']), data={'version': result['version']}, datetime=release.date_released, ) commit_list = result.get('commits') if commit_list: release.set_commits(commit_list) refs = result.get('refs') if not refs: refs = [ { 'repository': r['repository'], 'previousCommit': r.get('previousId'), 'commit': r['currentId'], } for r in result.get('headCommits', []) ] if refs: if not request.user.is_authenticated(): return Response( { 'refs': ['You must use an authenticated API token to fetch refs'] }, status=400 ) fetch_commits = not commit_list try: release.set_refs(refs, request.user, fetch=fetch_commits) except InvalidRepository as exc: return Response({'refs': [exc.message]}, status=400) if not created and not new_projects: # 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)