Exemplo n.º 1
0
    def test_release_locked(self):
        self.login_as(user=self.user)
        org = self.create_organization(owner=self.user, name="baz")
        repo = Repository.objects.create(name="example",
                                         provider="dummy",
                                         organization_id=org.id)

        old_release = Release.objects.create(organization_id=org.id,
                                             version="abcabcabc")
        commit = Commit.objects.create(organization_id=org.id,
                                       repository_id=repo.id,
                                       key="a" * 40)
        ReleaseHeadCommit.objects.create(organization_id=org.id,
                                         repository_id=repo.id,
                                         release=old_release,
                                         commit=commit)

        refs = [{"repository": repo.name, "commit": "b" * 40}]
        new_release = Release.objects.create(organization_id=org.id,
                                             version="12345678")

        lock = locks.get(Release.get_lock_key(org.id, new_release.id),
                         duration=10)
        lock.acquire()

        with self.tasks():
            fetch_commits(
                release_id=new_release.id,
                user_id=self.user.id,
                refs=refs,
                previous_release_id=old_release.id,
            )
        count_query = ReleaseHeadCommit.objects.filter(release=new_release)
        # No release commits should be made as the task should return early.
        assert count_query.count() == 0
Exemplo n.º 2
0
    def start_release(self, version, **values):
        values.setdefault('date_started', timezone.now())

        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id,
                                                version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id)
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values)

            release.add_project(self.project)
Exemplo n.º 3
0
    def start_release(self, version, **values):
        values.setdefault('date_started', timezone.now())

        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id, version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values
                        )

            release.add_project(self.project)
Exemplo n.º 4
0
    def test_commits_lock_conflict(self):
        user = self.create_user(is_staff=False, is_superuser=False)
        org = self.create_organization()
        org.flags.allow_joinleave = False
        org.save()

        team = self.create_team(organization=org)
        project = self.create_project(name="foo", organization=org, teams=[team])

        self.create_member(teams=[team], user=user, organization=org)
        self.login_as(user=user)

        release = self.create_release(project, self.user, version="1.2.1")
        release.add_project(project)

        # Simulate a concurrent request by using an existing release
        # that has its commit lock taken out.
        lock = locks.get(Release.get_lock_key(org.id, release.id), duration=10)
        lock.acquire()

        url = reverse(
            "sentry-api-0-organization-release-details",
            kwargs={"organization_slug": org.slug, "version": release.version},
        )
        response = self.client.put(url, data={"commits": [{"id": "a" * 40}, {"id": "b" * 40}]})
        assert response.status_code == 409, (response.status_code, response.content)
        assert "Release commits" in response.data["detail"]
Exemplo n.º 5
0
    def finish_release(self, version, **values):
        values.setdefault('date_released', timezone.now())
        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id,
                                                version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id,
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values)
            release.add_project(self.project)

        activity = Activity.objects.create(
            type=Activity.RELEASE,
            project=self.project,
            ident=version,
            data={'version': version},
            datetime=values['date_released'],
        )
        activity.send_notification()
Exemplo n.º 6
0
    def finish_release(self, version, **values):
        values.setdefault('date_released', timezone.now())
        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id, version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id,
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values
                        )
            release.add_project(self.project)

        activity = Activity.objects.create(
            type=Activity.RELEASE,
            project=self.project,
            ident=version,
            data={'version': version},
            datetime=values['date_released'],
        )
        activity.send_notification()
Exemplo n.º 7
0
def ensure_release_exists(instance, created, **kwargs):
    if instance.key != 'sentry:release':
        return

    if instance.data and instance.data.get('release_id'):
        return

    affected = Release.objects.filter(
        organization_id=instance.project.organization_id,
        version=instance.value,
        projects=instance.project
    ).update(date_added=instance.first_seen)
    if not affected:
        release = Release.objects.filter(
            organization_id=instance.project.organization_id,
            version=instance.value
        ).first()
        if release:
            release.update(date_added=instance.first_seen)
        else:
            lock_key = Release.get_lock_key(instance.project.organization_id, instance.value)
            lock = locks.get(lock_key, duration=5)
            with TimedRetryPolicy(10)(lock.acquire):
                try:
                    release = Release.objects.get(
                        organization_id=instance.project.organization_id,
                        version=instance.value,
                    )
                except Release.DoesNotExist:
                    release = Release.objects.create(
                        organization_id=instance.project.organization_id,
                        version=instance.value,
                        date_added=instance.first_seen,
                    )
                    instance.update(data={'release_id': release.id})
        release.add_project(instance.project)
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    def set_commits(self, version, commit_list):
        """
        Commits should be ordered oldest to newest.

        Calling this method will remove all existing commit history.
        """
        project = self.project
        release = Release.objects.filter(
            organization_id=project.organization_id,
            version=version,
            projects=self.project).first()
        if not release:
            release = Release.objects.filter(
                organization_id=project.organization_id,
                version=version,
            ).first()
            if not release:
                lock_key = Release.get_lock_key(project.organization_id,
                                                version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            organization_id=project.organization_id,
                            version=version)
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            organization_id=project.organization_id,
                            version=version)
            release.add_project(project)

        with transaction.atomic():
            # TODO(dcramer): would be good to optimize the logic to avoid these
            # deletes but not overly important
            ReleaseCommit.objects.filter(release=release, ).delete()

            authors = {}
            repos = {}
            for idx, data in enumerate(commit_list):
                repo_name = data.get('repository') or 'project-{}'.format(
                    project.id)
                if repo_name not in repos:
                    repos[repo_name] = repo = Repository.objects.get_or_create(
                        organization_id=project.organization_id,
                        name=repo_name,
                    )[0]
                else:
                    repo = repos[repo_name]

                author_email = data.get('author_email')
                if author_email is None and data.get('author_name'):
                    author_email = self._to_email(data['author_name'])

                if not author_email:
                    author = None
                elif author_email not in authors:
                    authors[
                        author_email] = author = CommitAuthor.objects.get_or_create(
                            organization_id=project.organization_id,
                            email=author_email,
                            defaults={
                                'name': data.get('author_name'),
                            })[0]
                    if data.get('author_name'
                                ) and author.name != data['author_name']:
                        author.update(name=data['author_name'])
                else:
                    author = authors[author_email]

                commit = Commit.objects.get_or_create(
                    organization_id=project.organization_id,
                    repository_id=repo.id,
                    key=data['id'],
                    defaults={
                        'message': data.get('message'),
                        'author': author,
                        'date_added': data.get('timestamp') or timezone.now(),
                    })[0]

                ReleaseCommit.objects.create(
                    organization_id=project.organization_id,
                    release=release,
                    commit=commit,
                    order=idx,
                )
Exemplo n.º 11
0
    def set_commits(self, version, commit_list):
        """
        Commits should be ordered oldest to newest.

        Calling this method will remove all existing commit history.
        """
        project = self.project
        release = Release.objects.filter(
            organization_id=project.organization_id,
            version=version,
            projects=self.project
        ).first()
        if not release:
            release = Release.objects.filter(
                organization_id=project.organization_id,
                version=version,
            ).first()
            if not release:
                lock_key = Release.get_lock_key(project.organization_id, version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            organization_id=project.organization_id,
                            version=version
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            organization_id=project.organization_id,
                            version=version
                        )
            release.add_project(project)

        with transaction.atomic():
            # TODO(dcramer): would be good to optimize the logic to avoid these
            # deletes but not overly important
            ReleaseCommit.objects.filter(
                release=release,
            ).delete()

            authors = {}
            repos = {}
            for idx, data in enumerate(commit_list):
                repo_name = data.get('repository') or 'project-{}'.format(project.id)
                if repo_name not in repos:
                    repos[repo_name] = repo = Repository.objects.get_or_create(
                        organization_id=project.organization_id,
                        name=repo_name,
                    )[0]
                else:
                    repo = repos[repo_name]

                author_email = data.get('author_email')
                if author_email is None and data.get('author_name'):
                    author_email = self._to_email(data['author_name'])

                if not author_email:
                    author = None
                elif author_email not in authors:
                    authors[author_email] = author = CommitAuthor.objects.get_or_create(
                        organization_id=project.organization_id,
                        email=author_email,
                        defaults={
                            'name': data.get('author_name'),
                        }
                    )[0]
                    if data.get('author_name') and author.name != data['author_name']:
                        author.update(name=data['author_name'])
                else:
                    author = authors[author_email]

                commit = Commit.objects.get_or_create(
                    organization_id=project.organization_id,
                    repository_id=repo.id,
                    key=data['id'],
                    defaults={
                        'message': data.get('message'),
                        'author': author,
                        'date_added': data.get('timestamp') or timezone.now(),
                    }
                )[0]

                ReleaseCommit.objects.create(
                    organization_id=project.organization_id,
                    release=release,
                    commit=commit,
                    order=idx,
                )