示例#1
0
    def finish_release(self, version, **values):
        if not Release.is_valid_version(version):
            raise HookValidationError('Invalid release version: %s' % version)

        values.setdefault('date_released', timezone.now())
        try:
            with transaction.atomic():
                release = Release.objects.create(
                    version=version, organization_id=self.project.organization_id, **values
                )
        except IntegrityError:
            release = Release.objects.get(
                version=version,
                organization_id=self.project.organization_id,
            )
            release.update(**values)

        release.add_project(self.project)

        Activity.objects.create(
            type=Activity.RELEASE,
            project=self.project,
            ident=Activity.get_version_ident(version),
            data={'version': version},
            datetime=values['date_released'],
        )
        self.set_refs(release=release, **values)
示例#2
0
 def create_release(self, project, user, version=None):
     from sentry.models import Release, Activity
     if version is None:
         version = os.urandom(20).encode('hex')
     with transaction.atomic():
         release = Release.objects.filter(
             version=version, organization_id=project.organization_id, projects=project
         ).first()
         if not release:
             release = Release.objects.filter(
                 version=version,
                 organization_id=project.organization_id,
             ).first()
             if not release:
                 release = Release.objects.create(
                     version=version,
                     organization_id=project.organization_id,
                 )
             release.add_project(project)
     Activity.objects.create(
         type=Activity.RELEASE,
         project=project,
         ident=Activity.get_version_ident(version),
         user=user,
         data={'version': version},
     )
     return release
示例#3
0
    def notify_if_ready(cls, deploy_id, fetch_complete=False):
        """
        create activity and send deploy notifications
        if they haven't been sent
        """
        from sentry.models import Activity, Environment, ReleaseCommit, ReleaseHeadCommit

        lock_key = cls.get_lock_key(deploy_id)
        lock = locks.get(lock_key, duration=30)
        with TimedRetryPolicy(10)(lock.acquire):
            deploy = cls.objects.filter(
                id=deploy_id,
            ).select_related('release').get()
            if deploy.notified:
                return

            release = deploy.release
            environment = Environment.objects.get(
                organization_id=deploy.organization_id,
                id=deploy.environment_id,
            )

            if not fetch_complete:
                release_has_commits = ReleaseCommit.objects.filter(
                    organization_id=release.organization_id,
                    release=release,
                ).exists()

                if not release_has_commits:
                    # check if we have head commits, which
                    # would indicate that we're waiting for
                    # fetch_commits to complete
                    if ReleaseHeadCommit.objects.filter(
                        organization_id=release.organization_id,
                        release=release,
                    ).exists():
                        return

            activity = None
            for project in deploy.release.projects.all():
                activity = Activity.objects.create(
                    type=Activity.DEPLOY,
                    project=project,
                    ident=Activity.get_version_ident(release.version),
                    data={
                        'version': release.version,
                        'deploy_id': deploy.id,
                        'environment': environment.name,
                    },
                    datetime=deploy.date_finished,
                )
            # Somewhat hacky, only send notification for one
            # Deploy Activity record because it will cover all projects
            if activity is not None:
                activity.send_notification()
                deploy.update(notified=True)
示例#4
0
文件: factories.py 项目: yaoqi/sentry
    def create_release(project, user=None, version=None, date_added=None):
        if version is None:
            version = os.urandom(20).encode('hex')

        if date_added is None:
            date_added = timezone.now()

        release = Release.objects.create(
            version=version,
            organization_id=project.organization_id,
            date_added=date_added,
        )

        release.add_project(project)

        Activity.objects.create(
            type=Activity.RELEASE,
            project=project,
            ident=Activity.get_version_ident(version),
            user=user,
            data={'version': version},
        )

        # add commits
        if user:
            author = Factories.create_commit_author(project=project, user=user)
            repo = Factories.create_repo(project, name='organization-{}'.format(project.slug))
            commit = Factories.create_commit(
                project=project,
                repo=repo,
                author=author,
                release=release,
                key='deadbeef',
                message='placeholder commit message',
            )

            release.update(
                authors=[six.text_type(author.id)],
                commit_count=1,
                last_commit_id=commit.id,
            )

        return release
示例#5
0
    def test_assignment(self, mock_func):
        """
        Test that a Slack message is sent with the expected payload when an issue is assigned
        """
        notification = AssignedActivityNotification(
            Activity(
                project=self.project,
                group=self.group,
                user=self.user,
                type=ActivityType.ASSIGNED,
                data={"assignee": self.user.id},
            ))
        with self.tasks():
            notification.send()

        attachment = get_attachment()

        assert attachment["title"] == "Assigned"
        assert attachment[
            "text"] == f"{self.name} assigned {self.short_id} to {self.name}"
        assert (
            attachment["footer"] ==
            f"<http://testserver/organizations/{self.organization.slug}/issues/{self.group.id}/?referrer=AssignedActivitySlack|{self.short_id}> via <http://testserver/settings/account/notifications/?referrer=AssignedActivitySlack|Notification Settings>"
        )
示例#6
0
    def test_regression(self, mock_func):
        """
        Test that a Slack message is sent with the expected payload when an issue regresses
        """
        notification = RegressionActivityNotification(
            Activity(
                project=self.project,
                group=self.group,
                user=self.user,
                type=ActivityType.SET_REGRESSION,
                data={},
            ))
        with self.tasks():
            notification.send()

        attachment = get_attachment()

        assert attachment["title"] == "Regression"
        assert attachment[
            "text"] == f"{self.name} marked {self.short_id} as a regression"
        assert (
            attachment["footer"] ==
            f"<http://testserver/organizations/{self.organization.slug}/issues/{self.group.id}/?referrer=RegressionActivitySlack|{self.short_id}> via <http://testserver/settings/account/notifications/?referrer=RegressionActivitySlack|Notification Settings>"
        )
示例#7
0
    def get(self, request):
        org = Organization(
            id=1,
            slug='organization',
            name='My Company',
        )
        project = Project(
            id=1,
            organization=org,
            slug='project',
            name='My Project',
        )

        group = next(make_group_generator(
            get_random(request),
            project,
        ), )

        event = Event(
            id=1,
            project=project,
            group=group,
            message=group.message,
            data=load_data('python'),
            datetime=datetime(2016, 6, 13, 3, 8, 24, tzinfo=timezone.utc),
        )

        activity = Activity(group=event.group,
                            project=event.project,
                            **self.get_activity(request, event))

        return render_to_response(
            'sentry/debug/mail/preview.html', {
                'preview': ActivityMailPreview(request, activity),
                'format': request.GET.get('format'),
            })
示例#8
0
    def test_resolved(self, mock_func):
        """
        Test that a Slack message is sent with the expected payload when an issue is resolved
        """
        notification = ResolvedActivityNotification(
            Activity(
                project=self.project,
                group=self.group,
                user=self.user,
                type=ActivityType.SET_RESOLVED,
                data={"assignee": ""},
            ))
        with self.tasks():
            notification.send()

        attachment, text = get_attachment()
        assert (
            text ==
            f"{self.name} marked <http://testserver/organizations/{self.organization.slug}/issues/{self.group.id}/?referrer=activity_notification|{self.short_id}> as resolved"
        )
        assert (
            attachment["footer"] ==
            f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=ResolvedActivitySlack|Notification Settings>"
        )
示例#9
0
def group(request, team, project, group, event_id=None):
    # It's possible that a message would not be created under certain
    # circumstances (such as a post_save signal failing)
    if event_id:
        event = get_object_or_404(group.event_set, id=event_id)
    else:
        event = group.get_latest_event() or Event()

    Event.objects.bind_nodes([event], 'data')

    # bind params to group in case they get hit
    event.group = group
    event.project = project

    if request.POST.get('o') == 'note' and request.user.is_authenticated():
        add_note_form = NewNoteForm(request.POST)
        if add_note_form.is_valid():
            add_note_form.save(event, request.user)
            return HttpResponseRedirect(request.path)
    else:
        add_note_form = NewNoteForm()

    activity_qs = Activity.objects.order_by('-datetime').select_related('user')
    # if event_id:
    #     activity_qs = activity_qs.filter(
    #         Q(event=event) | Q(event__isnull=True),
    #     )

    if project in Project.objects.get_for_user(request.user,
                                               team=team,
                                               superuser=False):
        # update that the user has seen this group
        create_or_update(GroupSeen,
                         group=group,
                         user=request.user,
                         project=project,
                         defaults={
                             'last_seen': timezone.now(),
                         })

    # filter out dupe activity items
    activity_items = set()
    activity = []
    for item in activity_qs.filter(group=group)[:20]:
        sig = (item.event_id, item.type, item.ident, item.user_id)
        # TODO: we could just generate a signature (hash(text)) for notes
        # so theres no special casing
        if item.type == Activity.NOTE:
            activity.append(item)
        elif sig not in activity_items:
            activity_items.add(sig)
            activity.append(item)

    activity.append(
        Activity(project=project,
                 group=group,
                 type=Activity.FIRST_SEEN,
                 datetime=group.first_seen))

    # trim to latest 5
    activity = activity[:7]

    seen_by = sorted(filter(lambda ls: ls[0] != request.user and ls[0].email, [
        (gs.user, gs.last_seen)
        for gs in GroupSeen.objects.filter(group=group).select_related('user')
    ]),
                     key=lambda ls: ls[1],
                     reverse=True)
    seen_by_extra = len(seen_by) - 5
    if seen_by_extra < 0:
        seen_by_extra = 0
    seen_by_faces = seen_by[:5]

    context = {
        'add_note_form': add_note_form,
        'page': 'details',
        'activity': activity,
        'seen_by': seen_by,
        'seen_by_faces': seen_by_faces,
        'seen_by_extra': seen_by_extra,
    }

    is_public = group_is_public(group, request.user)

    if is_public:
        template = 'sentry/groups/public_details.html'
        context['PROJECT_LIST'] = [project]
    else:
        template = 'sentry/groups/details.html'

    return render_with_group_context(group,
                                     template,
                                     context,
                                     request,
                                     event=event,
                                     is_public=is_public)
示例#10
0
    def test_get_participants_without_actor(self):
        group, (user, ) = self.get_fixture_data(1)

        email = ActivityEmail(Activity(project=group.project, group=group))

        assert set(email.get_participants()) == set([user])
    def put(self, request, organization, version):
        """
        Update an Organization'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 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.
        :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
        """
        try:
            release = Release.objects.get(
                organization_id=organization,
                version=version,
            )
        except Release.DoesNotExist:
            raise ResourceDoesNotExist

        if not self.has_release_permission(request, organization, release):
            raise ResourceDoesNotExist

        serializer = OrganizationReleaseSerializer(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        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:
            # TODO(dcramer): handle errors with release payloads
            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 was_released and release.date_released):
            for project in release.projects.all():
                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, organization, version):
        """
        Update an Organization'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 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.
        :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
        """
        bind_organization_context(organization)

        with configure_scope() as scope:
            scope.set_tag("version", version)
            try:
                release = Release.objects.get(organization_id=organization, version=version)
                projects = release.projects.all()
            except Release.DoesNotExist:
                scope.set_tag("failure_reason", "Release.DoesNotExist")
                raise ResourceDoesNotExist

            if not self.has_release_permission(request, organization, release):
                scope.set_tag("failure_reason", "no_release_permission")
                raise ResourceDoesNotExist

            serializer = OrganizationReleaseSerializer(data=request.data)

            if not serializer.is_valid():
                scope.set_tag("failure_reason", "serializer_error")
                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 result.get("status"):
                kwargs["status"] = result["status"]

            if kwargs:
                release.update(**kwargs)

            commit_list = result.get("commits")
            if commit_list:
                # TODO(dcramer): handle errors with release payloads
                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 was_released and release.date_released:
                for project in projects:
                    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))
示例#13
0
    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)
示例#14
0
    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)
示例#15
0
    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)

            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)
示例#16
0
 def _get_activity(self, request, group, num):
     return Activity.get_activities_for_group(group, num)
示例#17
0
    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)
示例#18
0
    def create_release(
        project: Project,
        user: Optional[User] = None,
        version: Optional[str] = None,
        date_added: Optional[datetime] = None,
        additional_projects: Optional[Sequence[Project]] = None,
        environments: Optional[Sequence[Environment]] = None,
        date_released: Optional[datetime] = None,
        adopted: Optional[datetime] = None,
        unadopted: Optional[datetime] = None,
    ):
        if version is None:
            version = force_text(hexlify(os.urandom(20)))

        if date_added is None:
            date_added = timezone.now()

        if additional_projects is None:
            additional_projects = []

        release = Release.objects.create(
            version=version,
            organization_id=project.organization_id,
            date_added=date_added,
            date_released=date_released,
        )

        release.add_project(project)
        for additional_project in additional_projects:
            release.add_project(additional_project)

        for environment in environments or []:
            ReleaseEnvironment.objects.create(
                organization=project.organization,
                release=release,
                environment=environment)
            for project in [project] + additional_projects:
                ReleaseProjectEnvironment.objects.create(
                    project=project,
                    release=release,
                    environment=environment,
                    adopted=adopted,
                    unadopted=unadopted,
                )

        Activity.objects.create(
            type=Activity.RELEASE,
            project=project,
            ident=Activity.get_version_ident(version),
            user=user,
            data={"version": version},
        )

        # add commits
        if user:
            author = Factories.create_commit_author(project=project, user=user)
            repo = Factories.create_repo(project,
                                         name=f"organization-{project.slug}")
            commit = Factories.create_commit(
                project=project,
                repo=repo,
                author=author,
                release=release,
                key="deadbeef",
                message="placeholder commit message",
            )

            release.update(authors=[str(author.id)],
                           commit_count=1,
                           last_commit_id=commit.id)

        return release
示例#19
0
    def test_uses_default(self):
        user6 = self.create_user()
        self.create_member(user=user6,
                           organization=self.org,
                           teams=[self.team])

        UserOption.objects.set_value(
            user=user6,
            organization=None,
            key='deploy-emails',
            value=UserOptionValue.all_deploys,
        )

        release = Release.objects.create(
            version='b' * 40,
            organization_id=self.project.organization_id,
            date_released=timezone.now(),
        )
        release.add_project(self.project)
        release.add_project(self.project2)
        deploy = Deploy.objects.create(
            release=release,
            organization_id=self.org.id,
            environment_id=self.environment.id,
        )

        email = ReleaseActivityEmail(
            Activity(
                project=self.project,
                user=self.user,
                type=Activity.RELEASE,
                data={
                    'version': release.version,
                    'deploy_id': deploy.id,
                },
            ))

        # user3 and user 6 are included because they oped into all deploy emails
        # (one on an org level, one as their default)
        assert len(email.get_participants()) == 2

        assert email.get_participants() == {
            user6: GroupSubscriptionReason.deploy_setting,
            self.user3: GroupSubscriptionReason.deploy_setting,
        }

        context = email.get_context()
        assert context['environment'] == 'production'
        assert context['repos'] == []

        user_context = email.get_user_context(user6)
        # make sure this only includes projects user has access to
        assert len(user_context['projects']) == 1
        assert user_context['projects'][0][0] == self.project

        with self.tasks():
            email.send()

        assert len(mail.outbox) == 2

        sent_email_addresses = {msg.to[0] for msg in mail.outbox}

        assert sent_email_addresses == {self.user3.email, user6.email}
示例#20
0
    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))
示例#21
0
    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 put(self, request, organization, version):
        """
        Update an Organization'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 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.
        :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
        """
        try:
            release = Release.objects.get(
                organization_id=organization,
                version=version,
            )
        except Release.DoesNotExist:
            raise ResourceDoesNotExist

        if not self.has_release_permission(request, organization, release):
            raise ResourceDoesNotExist

        serializer = OrganizationReleaseSerializer(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        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:
            # TODO(dcramer): handle errors with release payloads
            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 was_released and release.date_released):
            for project in release.projects.all():
                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))
示例#23
0
    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)
示例#24
0
    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.object

        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))