def _needs_upgrade(): version_configured = options.get('sentry:version-configured') if not version_configured: # If we were never previously upgraded (being a new install) # we want to force an upgrade, even if the values are set. return True smtp_disabled = not is_smtp_enabled() # Check all required options to see if they've been set for key in options.filter(flag=options.FLAG_REQUIRED): # ignore required flags which can be empty if key.flags & options.FLAG_ALLOW_EMPTY: continue # Ignore mail.* keys if smtp is disabled if smtp_disabled and key.name[:5] == 'mail.': continue if not options.isset(key.name): return True if version_configured != sentry.get_version(): # Everything looks good, but version changed, so let's bump it options.set('sentry:version-configured', sentry.get_version()) return False
def get_install_id(): from sentry import options install_id = options.get('sentry:install-id') if not install_id: install_id = sha1(uuid4().bytes).hexdigest() options.set('sentry:install-id', install_id) return install_id
def test_registration_requires_subscribe_choice_with_newsletter(self): options.set('auth.allow-registration', True) with self.feature('auth:register'): resp = self.client.post( self.path, { 'username': '******', 'password': '******', 'name': 'Foo Bar', 'op': 'register', } ) assert resp.status_code == 200 with self.feature('auth:register'): resp = self.client.post( self.path, { 'username': '******', 'password': '******', 'name': 'Foo Bar', 'op': 'register', 'subscribe': '0', } ) assert resp.status_code == 302 user = User.objects.get(username='******') assert user.email == '*****@*****.**' assert user.check_password('foobar') assert user.name == 'Foo Bar' assert not OrganizationMember.objects.filter( user=user, ).exists() assert newsletter.get_subscriptions(user) == {'subscriptions': []}
def test_snuba_environment(self): options.set('snuba.events-queries.enabled', True) url = u'/api/0/issues/{}/events/latest/'.format(self.group.id) response = self.client.get(url, format='json', data={'environment': ['production']}) assert response.status_code == 200 assert response.data['id'] == six.text_type(self.event2.event_id)
def put(self, request): # TODO(dcramer): this should validate options before saving them for k, v in six.iteritems(request.DATA): if v and isinstance(v, six.string_types): v = v.strip() try: option = options.lookup_key(k) except options.UnknownOption: # TODO(dcramer): unify API errors return Response({ 'error': 'unknown_option', 'errorDetail': { 'option': k, }, }, status=400) try: if not (option.flags & options.FLAG_ALLOW_EMPTY) and not v: options.delete(k) else: options.set(k, v) except TypeError as e: return Response({ 'error': 'invalid_type', 'errorDetail': { 'option': k, 'message': six.text_type(e), }, }, status=400) # TODO(dcramer): this has nothing to do with configuring options and # should not be set here options.set('sentry:version-configured', sentry.get_version()) return Response(status=200)
def test_chunk_parameters(self): response = self.client.get( self.url, HTTP_AUTHORIZATION=u'Bearer {}'.format(self.token.token), format='json' ) endpoint = options.get('system.upload-url-prefix') # We fallback to default system url if config is not set if len(endpoint) == 0: endpoint = options.get('system.url-prefix') assert response.status_code == 200, response.content assert response.data['chunkSize'] == CHUNK_UPLOAD_BLOB_SIZE assert response.data['chunksPerRequest'] == MAX_CHUNKS_PER_REQUEST assert response.data['maxRequestSize'] == MAX_REQUEST_SIZE assert response.data['maxFileSize'] == options.get('system.maximum-file-size') assert response.data['concurrency'] == MAX_CONCURRENCY assert response.data['hashAlgorithm'] == HASH_ALGORITHM assert response.data['url'] == options.get('system.url-prefix') + self.url options.set('system.upload-url-prefix', 'test') response = self.client.get( self.url, HTTP_AUTHORIZATION=u'Bearer {}'.format(self.token.token), format='json' ) assert response.data['url'] == options.get('system.upload-url-prefix') + self.url
def sync_docs(): from sentry import http, options session = http.build_session() logger.info('Syncing documentation (platform index)') data = session.get(BASE_URL.format('_index.json')).json() platform_list = [] for platform_id, integrations in data['platforms'].iteritems(): platform_list.append({ 'id': platform_id, 'name': integrations['_self']['name'], 'integrations': [ { 'id': get_integration_id(platform_id, i_id), 'name': i_data['name'], 'type': i_data['type'], 'link': i_data['doc_link'], } for i_id, i_data in sorted( integrations.iteritems(), key=lambda x: x[1]['name'] ) ], }) platform_list.sort(key=lambda x: x['name']) options.set('sentry:docs', {'platforms': platform_list}) for platform_id, platform_data in data['platforms'].iteritems(): for integration_id, integration in platform_data.iteritems(): logger.info('Syncing documentation for %s integration', integration_id) sync_integration(platform_id, integration_id, integration['details'])
def test_update_is_not_available(self): options.set('sentry:latest_version', '5.5.1') with mock.patch('sentry.get_version') as get_version: get_version.return_value = '5.5.0' resp = self.client.get(self.path) assert self.UPDATE_MESSAGE in resp.content
def put(self, request): # TODO(dcramer): this should validate options before saving them for k, v in request.DATA.iteritems(): if v and isinstance(v, basestring): v = v.strip() try: if not v: options.delete(k) else: options.set(k, v) except options.UnknownOption: # TODO(dcramer): unify API errors return Response({ 'error': 'unknown_option', 'errorDetail': { 'option': k, }, }, status=400) except TypeError as e: return Response({ 'error': 'invalid_type', 'errorDetail': { 'option': k, 'message': unicode(e), }, }, status=400) # TODO(dcramer): this has nothing to do with configuring options and # should not be set here options.set('sentry:version-configured', sentry.get_version()) return Response(status=200)
def set(option, value): "Set a configuration option to a new value." from sentry import options from sentry.options.manager import UnknownOption try: options.set(option, value) except UnknownOption: raise click.ClickException('unknown option: %s' % option)
def test_set_sentry_version_old(self, get_version): options.set(self.KEY, self.NEW) get_version.return_value = self.CURRENT set_sentry_version(latest=self.OLD) self.assertEqual(options.get(key=self.KEY), self.NEW)
def default_issue_plugin_config(plugin, project, form_data): plugin_key = plugin.get_conf_key() for field, value in six.iteritems(form_data): key = '%s:%s' % (plugin_key, field) if project: ProjectOption.objects.set_value(project, key, value) else: options.set(key, value)
def put(self, request): try: for k, v in request.DATA.iteritems(): options.set(k, v) except Exception as e: return Response(unicode(e), status=400) options.set('sentry:version-configured', sentry.get_version()) return Response(status=200)
def test_register_renders_correct_template(self): options.set('auth.allow-registration', True) register_path = reverse('sentry-register') resp = self.client.get(register_path) assert resp.status_code == 200 assert resp.context['op'] == 'register' self.assertTemplateUsed('sentry/login.html')
def default_plugin_config(plugin, project, request): plugin_key = plugin.get_conf_key() form_class = plugin.get_conf_form(project) template = plugin.get_conf_template(project) if form_class is None: return HttpResponseRedirect(reverse( 'sentry-manage-project', args=[project.organization.slug, project.slug])) test_results = None form = form_class( request.POST if request.POST.get('plugin') == plugin.slug else None, initial=plugin.get_conf_options(project), prefix=plugin_key, ) if form.is_valid(): if 'action_test' in request.POST and plugin.is_testable(): try: test_results = plugin.test_configuration(project) except Exception as exc: if hasattr(exc, 'read') and callable(exc.read): test_results = '%s\n%s' % (exc, exc.read()) else: logging.exception('Plugin(%s) raised an error during test', plugin_key) test_results = 'There was an internal error with the Plugin' if not test_results: test_results = 'No errors returned' else: for field, value in form.cleaned_data.iteritems(): key = '%s:%s' % (plugin_key, field) if project: ProjectOption.objects.set_value(project, key, value) else: options.set(key, value) messages.add_message( request, messages.SUCCESS, _('Your settings were saved successfully.')) return HttpResponseRedirect(request.path) # TODO(mattrobenolt): Reliably determine if a plugin is configured # if hasattr(plugin, 'is_configured'): # is_configured = plugin.is_configured(project) # else: # is_configured = True is_configured = True return mark_safe(render_to_string(template, { 'form': form, 'request': request, 'plugin': plugin, 'plugin_description': plugin.get_description() or '', 'plugin_test_results': test_results, 'plugin_is_configured': is_configured, }, context_instance=RequestContext(request)))
def set(option, value): "Set a configuration option to a new value." from sentry import options from sentry.options.manager import UnknownOption try: options.set(option, value) except UnknownOption: raise click.ClickException("unknown option: %s" % option) except TypeError as e: raise click.ClickException(unicode(e))
def set_sentry_version(latest=None, **kwargs): import sentry current = sentry.get_version() version = options.get('sentry:latest_version') for ver in (current, version): if Version(ver) >= Version(latest): return options.set('sentry:latest_version', (latest or current))
def send_beacon(): """ Send a Beacon to a remote server operated by the Sentry team. See the documentation for more details. """ from sentry import options from sentry.models import Organization, Project, Team, User if not settings.SENTRY_BEACON: logger.info('Not sending beacon (disabled)') return # TODO(dcramer): move version code off of PyPi and into beacon install_id = options.get('sentry:install-id') if not install_id: logger.info('Generated installation ID: %s', install_id) install_id = sha1(uuid4().hex).hexdigest() options.set('sentry:install-id', install_id) internal_project_ids = filter(bool, [ settings.SENTRY_PROJECT, settings.SENTRY_FRONTEND_PROJECT, ]) platform_list = list(set(Project.objects.exclude( id__in=internal_project_ids, ).values_list('platform', flat=True))) payload = { 'install_id': install_id, 'version': sentry.get_version(), 'admin_email': settings.SENTRY_ADMIN_EMAIL, 'data': { # TODO(dcramer): we'd also like to get an idea about the throughput # of the system (i.e. events in 24h) 'platforms': platform_list, 'users': User.objects.count(), 'projects': Project.objects.count(), 'teams': Team.objects.count(), 'organizations': Organization.objects.count(), } } # TODO(dcramer): relay the response 'notices' as admin broadcasts try: request = safe_urlopen(BEACON_URL, json=payload, timeout=5) response = safe_urlread(request) except Exception: logger.warning('Failed sending beacon', exc_info=True) return data = json.loads(response) if 'version' in data: options.set('sentry:latest_version', data['version']['stable'])
def send_beacon(): """ Send a Beacon to a remote server operated by the Sentry team. See the documentation for more details. """ from sentry import options from sentry.models import Organization, Project, Team, User if not settings.SENTRY_BEACON: logger.info('Not sending beacon (disabled)') return install_id = options.get('sentry:install-id') if not install_id: logger.info('Generated installation ID: %s', install_id) install_id = sha1(uuid4().hex).hexdigest() options.set('sentry:install-id', install_id) end = timezone.now() events_24h = tsdb.get_sums( model=tsdb.models.internal, keys=['events.total'], start=end - timedelta(hours=24), end=end, )['events.total'] payload = { 'install_id': install_id, 'version': sentry.get_version(), 'admin_email': settings.SENTRY_ADMIN_EMAIL, 'data': { # TODO(dcramer): we'd also like to get an idea about the throughput # of the system (i.e. events in 24h) 'users': User.objects.count(), 'projects': Project.objects.count(), 'teams': Team.objects.count(), 'organizations': Organization.objects.count(), 'events.24h': events_24h, } } # TODO(dcramer): relay the response 'notices' as admin broadcasts try: request = safe_urlopen(BEACON_URL, json=payload, timeout=5) response = safe_urlread(request) except Exception: logger.warning('Failed sending beacon', exc_info=True) return data = json.loads(response) if 'version' in data: options.set('sentry:latest_version', data['version']['stable'])
def test_optimizer_enabled(self): prev_optimizer_enabled = options.get('snuba.search.pre-snuba-candidates-optimizer') options.set('snuba.search.pre-snuba-candidates-optimizer', True) try: results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) finally: options.set('snuba.search.pre-snuba-candidates-optimizer', prev_optimizer_enabled)
def sync_integration(platform_id, integration_id, path): from sentry import http, options session = http.build_session() data = session.get(BASE_URL.format(path)).json() key = get_integration_id(platform_id, integration_id) options.set('sentry:docs:{}'.format(key), { 'id': key, 'name': data['name'], 'html': data['body'], 'link': data['doc_link'], })
def test_closed(self): project = self.project # force creation url = '/extensions/github/webhook/' secret = 'b3002c3e321d4b7880360d397db2ccfd' options.set('github-app.webhook-secret', secret) future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5) integration = Integration.objects.create( provider='github', external_id='12345', name='octocat', metadata={'access_token': '1234', 'expires_at': future_expires.isoformat()} ) integration.add_organization(project.organization.id) repo = Repository.objects.create( organization_id=project.organization.id, external_id='35129377', provider='integrations:github', name='baxterthehacker/public-repo', ) response = self.client.post( path=url, data=PULL_REQUEST_CLOSED_EVENT_EXAMPLE, content_type='application/json', HTTP_X_GITHUB_EVENT='pull_request', HTTP_X_HUB_SIGNATURE='sha1=49db856f5658b365b73a2fa73a7cffa543f4d3af', HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()) ) assert response.status_code == 204 prs = PullRequest.objects.filter( repository_id=repo.id, organization_id=project.organization.id, ) assert len(prs) == 1 pr = prs[0] assert pr.key == '1' assert pr.message == u'new closed body' assert pr.title == u'new closed title' assert pr.author.name == u'baxterthehacker' assert pr.merge_commit_sha == '0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c'
def test_registration_valid(self): options.set('auth.allow-registration', True) with self.feature('auth:register'): resp = self.client.post(self.path, { 'username': '******', 'password': '******', 'name': 'Foo Bar', 'op': 'register', }) assert resp.status_code == 302 user = User.objects.get(username='******') assert user.email == '*****@*****.**' assert user.check_password('foobar') assert user.name == 'Foo Bar'
def set_sentry_version(latest=None, **kwargs): import sentry current = sentry.VERSION version = options.get("sentry:latest_version") for ver in (current, version): if Version(ver) >= Version(latest): latest = ver if latest == version: return options.set("sentry:latest_version", (latest or current))
def put(self, request): # TODO(dcramer): this should validate options before saving them for k, v in request.DATA.iteritems(): try: options.set(k, v) except options.UnknownOption: # TODO(dcramer): unify API errors return Response({ 'error': 'unknown_option', 'errorDetail': { 'option': k, }, }, status=400) options.set('sentry:version-configured', sentry.get_version()) return Response(status=200)
def test_opened(self): project = self.project # force creation url = '/extensions/github/webhook/' secret = 'b3002c3e321d4b7880360d397db2ccfd' options.set('github-app.webhook-secret', secret) future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5) integration = Integration.objects.create( provider='github', external_id='12345', name='octocat', metadata={'access_token': '1234', 'expires_at': future_expires.isoformat()} ) integration.add_organization(project.organization.id) repo = Repository.objects.create( organization_id=project.organization.id, external_id='35129377', provider='integrations:github', name='baxterthehacker/public-repo', ) response = self.client.post( path=url, data=PULL_REQUEST_OPENED_EVENT_EXAMPLE, content_type='application/json', HTTP_X_GITHUB_EVENT='pull_request', HTTP_X_HUB_SIGNATURE='sha1=5b82806b2c96eb546b2898f0dca98cc326d7d9ae', HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()) ) assert response.status_code == 204 prs = PullRequest.objects.filter( repository_id=repo.id, organization_id=project.organization.id, ) assert len(prs) == 1 pr = prs[0] assert pr.key == '1' assert pr.message == u'This is a pretty simple change that we need to pull into master.' assert pr.title == u'Update the README with new information' assert pr.author.name == u'baxterthehacker'
def test_edited(self): project = self.project # force creation url = '/extensions/github/webhook/' secret = 'b3002c3e321d4b7880360d397db2ccfd' options.set('github-app.webhook-secret', secret) future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5) integration = Integration.objects.create( provider='github', external_id='12345', name='octocat', metadata={'access_token': '1234', 'expires_at': future_expires.isoformat()} ) integration.add_organization(project.organization.id) repo = Repository.objects.create( organization_id=project.organization.id, external_id='35129377', provider='integrations:github', name='baxterthehacker/public-repo', ) pr = PullRequest.objects.create( key='1', repository_id=repo.id, organization_id=project.organization.id, ) response = self.client.post( path=url, data=PULL_REQUEST_EDITED_EVENT_EXAMPLE, content_type='application/json', HTTP_X_GITHUB_EVENT='pull_request', HTTP_X_HUB_SIGNATURE='sha1=42e0d5cf6bd7521fcc7576f7e41c31621c88091e', HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()) ) assert response.status_code == 204 pr = PullRequest.objects.get(id=pr.id) assert pr.key == '1' assert pr.message == u'new edited body' assert pr.title == u'new edited title' assert pr.author.name == u'baxterthehacker'
def test_simple(self, safe_urlread, safe_urlopen, mock_get_all_package_versions): mock_get_all_package_versions.return_value = {'foo': '1.0'} safe_urlread.return_value = json.dumps({ 'notices': [], 'version': {'stable': '1.0.0'}, }) assert options.set('system.admin-email', '*****@*****.**') send_beacon() install_id = options.get('sentry:install-id') assert install_id and len(install_id) == 40 safe_urlopen.assert_called_once_with(BEACON_URL, json={ 'install_id': install_id, 'version': sentry.get_version(), 'data': { 'organizations': 1, 'users': 0, 'projects': 1, 'teams': 1, 'events.24h': 0, }, 'admin_email': '*****@*****.**', 'packages': mock_get_all_package_versions.return_value, }, timeout=5) safe_urlread.assert_called_once_with(safe_urlopen.return_value) assert options.get('sentry:latest_version') == '1.0.0'
def _needs_upgrade(): version_configured = options.get('sentry:version-configured') if not version_configured: # If we were never previously upgraded (being a new install) # we want to force an upgrade, even if the values are set. return True # Check all required options to see if they've been set for key in options.filter(flag=options.FLAG_REQUIRED): if not options.get(key.name): return True if version_configured != sentry.get_version(): # Everything looks good, but version changed, so let's bump it options.set('sentry:version-configured', sentry.get_version()) return False
def set(key, value, secret): "Set a configuration option to a new value." from sentry import options from sentry.options.manager import UnknownOption if value is None: if secret: value = click.prompt('(hidden) Value', hide_input=True) else: value = click.prompt('Value') try: options.set(key, value) except UnknownOption: raise click.ClickException('unknown option: %s' % key) except TypeError as e: raise click.ClickException(six.text_type(e))
def test_invalid_signature_event(self): url = "/extensions/github/webhook/" secret = "2d7565c3537847b789d6995dca8d9f84" options.set("github-app.webhook-secret", secret) response = self.client.post( path=url, data=PUSH_EVENT_EXAMPLE_INSTALLATION, content_type="application/json", HTTP_X_GITHUB_EVENT="push", HTTP_X_HUB_SIGNATURE= "sha1=33521abeaaf9a57c2abf486e0ccd54d23cf36fec", HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()), ) assert response.status_code == 401
def test_move_to_symbolicate_event_old( default_project, mock_process_event, mock_save_event, mock_symbolicate_event, register_plugin ): # Temporarily test old behavior register_plugin(BasicPreprocessorPlugin) data = { "project": default_project.id, "platform": "native", "logentry": {"formatted": "test"}, "event_id": EVENT_ID, "extra": {"foo": "bar"}, } options.set("sentry:preprocess-use-new-behavior", False) preprocess_event(data=data) assert mock_symbolicate_event.delay.call_count == 0 assert mock_process_event.delay.call_count == 1 assert mock_save_event.delay.call_count == 0
def test_pre_and_post_filtering(self): prev_max_pre = options.get('snuba.search.max-pre-snuba-candidates') options.set('snuba.search.max-pre-snuba-candidates', 1) try: # normal queries work as expected results = self.backend.query(self.project, query='foo') assert set(results) == set([self.group1]) results = self.backend.query(self.project, query='bar') assert set(results) == set([self.group2]) # no candidate matches in Sentry, immediately return empty paginator results = self.backend.query(self.project, query='NO MATCHES IN SENTRY') assert set(results) == set() # too many candidates, skip pre-filter, requires >1 postfilter queries results = self.backend.query(self.project) assert set(results) == set([self.group1, self.group2]) finally: options.set('snuba.search.max-pre-snuba-candidates', prev_max_pre)
def test_registration_single_org(self): options.set("auth.allow-registration", True) with self.feature("auth:register"): resp = self.client.post( self.path, { "username": "******", "password": "******", "name": "Foo Bar", "op": "register", }, ) assert resp.status_code == 302, (resp.context["register_form"].errors if resp.status_code == 200 else None) user = User.objects.get( username="******") # User is part of the default org assert OrganizationMember.objects.filter(user=user).exists()
def test_snuba_no_prev(self): options.set('snuba.events-queries.enabled', True) url = reverse( 'sentry-api-0-project-event-details', kwargs={ 'event_id': self.prev_event.event_id, 'project_slug': self.prev_event.project.slug, 'organization_slug': self.prev_event.project.organization.slug, } ) response = self.client.get(url, format='json', data={ 'enable_snuba': '1' }) assert response.status_code == 200, response.content assert response.data['id'] == six.text_type(self.prev_event.event_id) assert response.data['previousEventID'] is None assert response.data['nextEventID'] == self.cur_event.event_id assert response.data['groupID'] == six.text_type(self.prev_event.group.id)
def test_unregistered_event(self): project = self.project # force creation url = u"/extensions/github/webhook/".format(project.organization.id) secret = "b3002c3e321d4b7880360d397db2ccfd" options.set("github-app.webhook-secret", secret) response = self.client.post( path=url, data=PUSH_EVENT_EXAMPLE_INSTALLATION, content_type="application/json", HTTP_X_GITHUB_EVENT="UnregisteredEvent", HTTP_X_HUB_SIGNATURE= "sha1=56a3df597e02adbc17fb617502c70e19d96a6136", HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()), ) assert response.status_code == 204
def test_update_repo_name(self): project = self.project # force creation url = "/extensions/github/webhook/" secret = "b3002c3e321d4b7880360d397db2ccfd" options.set("github-app.webhook-secret", secret) future_expires = datetime.now().replace(microsecond=0) + timedelta( minutes=5) integration = Integration.objects.create( provider="github", external_id="12345", name="octocat", metadata={ "access_token": "1234", "expires_at": future_expires.isoformat() }, ) integration.add_organization(project.organization, self.user) repo_out_of_date_name = Repository.objects.create( organization_id=project.organization.id, external_id="35129377", provider="integrations:github", name="emmathehacker/public-repo", # out of date url="https://github.com/baxterthehacker/public-repo", config={"name": "baxterthehacker/public-repo"}, ) response = self.client.post( path=url, data=PULL_REQUEST_OPENED_EVENT_EXAMPLE, content_type="application/json", HTTP_X_GITHUB_EVENT="pull_request", HTTP_X_HUB_SIGNATURE= "sha1=bc7ce12fc1058a35bf99355e6fc0e6da72c35de3", HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()), ) assert response.status_code == 204 # name has been updated repo_out_of_date_name.refresh_from_db() assert repo_out_of_date_name.name == "baxterthehacker/public-repo"
def test_registration_valid(self): options.set('auth.allow-registration', True) with self.feature('auth:register'): resp = self.client.post( self.path, { 'username': '******', 'password': '******', 'name': 'Foo Bar', 'op': 'register', } ) assert resp.status_code == 302, resp.context['register_form'].errors if resp.status_code == 200 else None user = User.objects.get(username='******') assert user.email == '*****@*****.**' assert user.check_password('foobar') assert user.name == 'Foo Bar' assert not OrganizationMember.objects.filter( user=user, ).exists()
def put(self, request): # TODO(dcramer): this should validate options before saving them for k, v in six.iteritems(request.data): if v and isinstance(v, six.string_types): v = v.strip() try: option = options.lookup_key(k) except options.UnknownOption: # TODO(dcramer): unify API errors return Response( { "error": "unknown_option", "errorDetail": { "option": k } }, status=400) try: if not (option.flags & options.FLAG_ALLOW_EMPTY) and not v: options.delete(k) else: options.set(k, v) except (TypeError, AssertionError) as e: # TODO(chadwhitacre): Use a custom exception for the # immutability case, especially since asserts disappear with # `python -O`. return Response( { "error": "invalid_type" if type(e) is TypeError else "immutable_option", "errorDetail": { "option": k, "message": six.text_type(e) }, }, status=400, ) # TODO(dcramer): this has nothing to do with configuring options and # should not be set here options.set("sentry:version-configured", sentry.get_version()) return Response(status=200)
def _safe_modify(killswitch_name, modify_func): from sentry import killswitches, options option_value = options.get(killswitch_name) new_option_value = modify_func(option_value) if option_value == new_option_value: click.echo("No changes!") raise click.Abort() click.echo("Before:") click.echo(killswitches.print_conditions(option_value)) click.echo("After:") click.echo(killswitches.print_conditions(new_option_value)) click.confirm("Should the changes be applied?", default=False, show_default=True, abort=True) options.set(killswitch_name, new_option_value)
def test_getfile_fs_cache(self): file_content = b"this is a test" file = self.create_file(name="dummy.txt") file.putfile(BytesIO(file_content)) release_file = self.create_release_file(file=file) expected_path = os.path.join( options.get("releasefile.cache-path"), str(self.organization.id), str(file.id), ) # Set the threshold to zero to force caching on the file system options.set("releasefile.cache-limit", 0) with ReleaseFile.cache.getfile(release_file) as f: assert f.read() == file_content assert f.name == expected_path # Check that the file was cached os.stat(expected_path)
def test_processes_resource_change_task_uses_sampling_option(self, delay): options.set('post-process.use-error-hook-sampling', True) options.set('post-process.error-hook-sample-rate', 1) event = self.store_event( data={ 'message': 'Foo bar', 'level': 'error', 'exception': {"type": "Foo", "value": "shits on fiah yo"}, 'timestamp': timezone.now().isoformat()[:19] }, project_id=self.project.id, assert_no_errors=False ) self.create_service_hook( project=self.project, organization=self.project.organization, actor=self.user, events=['error.created'], ) post_process_group( event=event, is_new=False, is_regression=False, is_sample=False, is_new_group_environment=False, ) kwargs = { 'project_id': self.project.id, 'group_id': event.group.id, } delay.assert_called_once_with( action='created', sender='Error', instance_id=event.event_id, **kwargs )
def test_caching(self): # Set the threshold to zero to force caching on the file system options.set("releasefile.cache-limit", 0) project = self.project release = Release.objects.create( organization_id=project.organization_id, version="abc") release.add_project(project) file = File.objects.create( name="file.min.js", type="release.file", headers={"Content-Type": "application/json; charset=utf-8"}, ) binary_body = unicode_body.encode("utf-8") file.putfile(BytesIO(binary_body)) ReleaseFile.objects.create( name="file.min.js", release_id=release.id, organization_id=project.organization_id, file=file, ) result = fetch_release_file("file.min.js", release) assert isinstance(result.body, bytes) assert result == http.UrlResult( "file.min.js", {"content-type": "application/json; charset=utf-8"}, binary_body, 200, "utf-8", ) # test with cache hit, coming from the FS new_result = fetch_release_file("file.min.js", release) assert result == new_result
def test_simple(self, safe_urlread, safe_urlopen, mock_get_all_package_versions): mock_get_all_package_versions.return_value = {"foo": "1.0"} safe_urlread.return_value = json.dumps({ "notices": [], "version": { "stable": "1.0.0" } }) assert options.set("system.admin-email", "*****@*****.**") assert options.set("beacon.anonymous", False) send_beacon() install_id = options.get("sentry:install-id") assert install_id and len(install_id) == 40 safe_urlopen.assert_called_once_with( BEACON_URL, json={ "install_id": install_id, "version": sentry.get_version(), "docker": sentry.is_docker(), "python_version": platform.python_version(), "data": { "organizations": 1, "users": 0, "projects": 1, "teams": 1, "events.24h": 0, }, "anonymous": False, "admin_email": "*****@*****.**", "packages": mock_get_all_package_versions.return_value, }, timeout=5, ) safe_urlread.assert_called_once_with(safe_urlopen.return_value) assert options.get("sentry:latest_version") == "1.0.0"
def _needs_upgrade(): version_configured = options.get('sentry:version-configured') if not version_configured: # If we were never previously upgraded (being a new install) # we want to force an upgrade, even if the values are set. return True smtp_disabled = not is_smtp_enabled() # Check all required options to see if they've been set for key in options.filter(flag=options.FLAG_REQUIRED): # Ignore mail.* keys if smtp is disabled if smtp_disabled and key.name[:5] == 'mail.': continue if not options.isset(key.name): return True if version_configured != sentry.get_version(): # Everything looks good, but version changed, so let's bump it options.set('sentry:version-configured', sentry.get_version()) return False
def _push(killswitch_name, infile, yes): """ Write back a killswitch into the DB. For a list of killswitches to write, use `sentry killswitches list`. For example: sentry killswitches pull store.load-shed-pipeline-projects file.txt <edit file.txt> sentry killswitches push store.load-shed-pipeline-projects file.txt """ from sentry import killswitches, options option_value = options.get(killswitch_name) edited_text = infile.read() try: new_option_value = killswitches.validate_user_input( killswitch_name, yaml.safe_load(edited_text) ) except ValueError as e: click.echo(f"Invalid data: {e}") raise click.Abort() if option_value == new_option_value: click.echo("No changes!", err=True) raise click.Abort() click.echo("Before:") click.echo(killswitches.print_conditions(killswitch_name, option_value)) click.echo("After:") click.echo(killswitches.print_conditions(killswitch_name, new_option_value)) if not yes: click.confirm( "Should the changes be applied?", default=False, show_default=True, abort=True ) options.set(killswitch_name, new_option_value)
def test_simple(self, safe_urlread, safe_urlopen, mock_get_all_package_versions): mock_get_all_package_versions.return_value = {'foo': '1.0'} safe_urlread.return_value = json.dumps({ 'notices': [], 'version': { 'stable': '1.0.0' }, }) assert options.set('system.admin-email', '*****@*****.**') assert options.set('beacon.anonymous', False) send_beacon() install_id = options.get('sentry:install-id') assert install_id and len(install_id) == 40 safe_urlopen.assert_called_once_with( BEACON_URL, json={ 'install_id': install_id, 'version': sentry.get_version(), 'docker': sentry.is_docker(), 'data': { 'organizations': 1, 'users': 0, 'projects': 1, 'teams': 1, 'events.24h': 0, }, 'anonymous': False, 'admin_email': '*****@*****.**', 'packages': mock_get_all_package_versions.return_value, }, timeout=5 ) safe_urlread.assert_called_once_with(safe_urlopen.return_value) assert options.get('sentry:latest_version') == '1.0.0'
def test_registration_valid(self, mock_record): options.set("auth.allow-registration", True) with self.feature("auth:register"): resp = self.client.post( self.path, { "username": "******", "password": "******", "name": "Foo Bar", "op": "register", }, ) assert resp.status_code == 302, (resp.context["register_form"].errors if resp.status_code == 200 else None) frontend_events = {"event_name": "Sign Up"} marketing_query = urlencode( {"frontend_events": json.dumps(frontend_events)}) assert marketing_query in resp.url user = User.objects.get( username="******") assert user.email == "*****@*****.**" assert user.check_password("foobar") assert user.name == "Foo Bar" assert not OrganizationMember.objects.filter(user=user).exists() signup_record = [ r for r in mock_record.call_args_list if r[0][0] == "user.signup" ] assert signup_record == [ mock.call( "user.signup", user_id=user.id, source="register-form", provider=None, referrer="in-app", ) ]
def put(self, request): # TODO(dcramer): this should validate options before saving them for k, v in six.iteritems(request.data): if v and isinstance(v, six.string_types): v = v.strip() try: option = options.lookup_key(k) except options.UnknownOption: # TODO(dcramer): unify API errors return Response( { "error": "unknown_option", "errorDetail": { "option": k } }, status=400) try: if not (option.flags & options.FLAG_ALLOW_EMPTY) and not v: options.delete(k) else: options.set(k, v) except TypeError as e: return Response( { "error": "invalid_type", "errorDetail": { "option": k, "message": six.text_type(e) }, }, status=400, ) # TODO(dcramer): this has nothing to do with configuring options and # should not be set here options.set("sentry:version-configured", sentry.get_version()) return Response(status=200)
def test_get_oldest_latest_for_environments(self): options.set('snuba.events-queries.enabled', True) project = self.create_project() min_ago = (timezone.now() - timedelta(minutes=1)).isoformat()[:19] self.store_event(data={ 'event_id': 'a' * 32, 'environment': 'production', 'timestamp': min_ago, 'fingerprint': ['group-1'] }, project_id=project.id) self.store_event(data={ 'event_id': 'b' * 32, 'environment': 'production', 'timestamp': min_ago, 'fingerprint': ['group-1'] }, project_id=project.id) self.store_event(data={ 'event_id': 'c' * 32, 'timestamp': min_ago, 'fingerprint': ['group-1'] }, project_id=project.id) group = Group.objects.first() assert group.get_latest_event_for_environments().event_id == 'c' * 32 assert group.get_latest_event_for_environments(['staging']) is None assert group.get_latest_event_for_environments(['production' ]).event_id == 'b' * 32 assert group.get_oldest_event_for_environments().event_id == 'a' * 32 assert group.get_oldest_event_for_environments( ['staging', 'production']).event_id == 'a' * 32 assert group.get_oldest_event_for_environments(['staging']) is None
def test_registration_subscribe_to_newsletter(self): options.set('auth.allow-registration', True) with self.feature('auth:register'): resp = self.client.post( self.path, { 'username': '******', 'password': '******', 'name': 'Foo Bar', 'op': 'register', 'subscribe': '1', }) assert resp.status_code == 302 user = User.objects.get( username='******') assert user.email == '*****@*****.**' assert user.check_password('foobar') assert user.name == 'Foo Bar' results = newsletter.get_subscriptions(user)['subscriptions'] assert len(results) == 1 assert results[0].list_id == newsletter.get_default_list_id() assert results[0].subscribed assert not results[0].verified
def test_pagination(self): # test with and without max-pre-snuba-candidates enabled prev_max_pre = options.get('snuba.search.max-pre-snuba-candidates') options.set('snuba.search.max-pre-snuba-candidates', None) try: results = self.backend.query(self.project, limit=1, sort_by='date') assert set(results) == set([self.group1]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) finally: options.set('snuba.search.max-pre-snuba-candidates', prev_max_pre) results = self.backend.query(self.project, limit=1, sort_by='date') assert set(results) == set([self.group1]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([])
def plugin_config(plugin, project, request): """ Configure the plugin site wide. Returns a tuple composed of a redirection boolean and the content to be displayed. """ NOTSET = object() plugin_key = plugin.get_conf_key() if project: form_class = plugin.project_conf_form template = plugin.project_conf_template else: form_class = plugin.site_conf_form template = plugin.site_conf_template test_results = None initials = plugin.get_form_initial(project) for field in form_class.base_fields: key = '%s:%s' % (plugin_key, field) if project: value = ProjectOption.objects.get_value(project, key, NOTSET) else: value = options.get(key) if value is not NOTSET: initials[field] = value form = form_class( request.POST if request.POST.get('plugin') == plugin.slug else None, initial=initials, prefix=plugin_key) if form.is_valid(): if 'action_test' in request.POST and plugin.is_testable(): try: test_results = plugin.test_configuration(project) except Exception as exc: if hasattr(exc, 'read') and callable(exc.read): test_results = '%s\n%s' % (exc, exc.read()) else: logging.exception('Plugin(%s) raised an error during test', plugin_key) test_results = 'There was an internal error with the Plugin' if not test_results: test_results = 'No errors returned' else: for field, value in form.cleaned_data.iteritems(): key = '%s:%s' % (plugin_key, field) if project: ProjectOption.objects.set_value(project, key, value) else: options.set(key, value) return ('redirect', None) # TODO(mattrobenolt): Reliably determine if a plugin is configured # if hasattr(plugin, 'is_configured'): # is_configured = plugin.is_configured(project) # else: # is_configured = True is_configured = True from django.template.loader import render_to_string return ('display', mark_safe( render_to_string( template, { 'form': form, 'request': request, 'plugin': plugin, 'plugin_description': plugin.get_description() or '', 'plugin_test_results': test_results, 'plugin_is_configured': is_configured, }, context_instance=RequestContext(request))))
def setUp(self): super(GroupEventsTest, self).setUp() self.min_ago = timezone.now() - timedelta(minutes=1) options.set('snuba.events-queries.enabled', True)
def test_registration_disabled(self): options.set("auth.allow-registration", True) with self.feature({"auth:register": False}): resp = self.client.get(self.path) assert resp.context["register_form"] is None
def test_multiple_orgs(self, mock_get_jwt): mock_get_jwt.return_value = "" project = self.project # force creation url = "/extensions/github/webhook/" secret = "b3002c3e321d4b7880360d397db2ccfd" options.set("github-app.webhook-secret", secret) Repository.objects.create( organization_id=project.organization.id, external_id="35129377", provider="integrations:github", name="baxterthehacker/public-repo", ) future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5) integration = Integration.objects.create( external_id="12345", provider="github", metadata={"access_token": "1234", "expires_at": future_expires.isoformat()}, ) integration.add_organization(project.organization, self.user) org2 = self.create_organization() project2 = self.create_project(organization=org2, name="bar") Repository.objects.create( organization_id=project2.organization.id, external_id="77", provider="integrations:github", name="another/repo", ) future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5) integration = Integration.objects.create( external_id="99", provider="github", metadata={"access_token": "1234", "expires_at": future_expires.isoformat()}, ) integration.add_organization(org2, self.user) response = self.client.post( path=url, data=PUSH_EVENT_EXAMPLE_INSTALLATION, content_type="application/json", HTTP_X_GITHUB_EVENT="push", HTTP_X_HUB_SIGNATURE="sha1=56a3df597e02adbc17fb617502c70e19d96a6136", HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()), ) assert response.status_code == 204 commit_list = list( Commit.objects.filter(organization_id=project.organization_id) .select_related("author") .order_by("-date_added") ) assert len(commit_list) == 2 commit_list = list( Commit.objects.filter(organization_id=org2.id) .select_related("author") .order_by("-date_added") ) assert len(commit_list) == 0
def test_anonymous_lookup(self): project = self.project # force creation url = "/extensions/github/webhook/" secret = "b3002c3e321d4b7880360d397db2ccfd" options.set("github-app.webhook-secret", secret) future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5) integration = Integration.objects.create( provider="github", external_id="12345", name="octocat", metadata={"access_token": "1234", "expires_at": future_expires.isoformat()}, ) integration.add_organization(project.organization, self.user) Repository.objects.create( organization_id=project.organization.id, external_id="35129377", provider="integrations:github", name="baxterthehacker/public-repo", ) CommitAuthor.objects.create( external_id="github:baxterthehacker", organization_id=project.organization_id, email="*****@*****.**", name="bàxterthehacker", ) response = self.client.post( path=url, data=PUSH_EVENT_EXAMPLE_INSTALLATION, content_type="application/json", HTTP_X_GITHUB_EVENT="push", HTTP_X_HUB_SIGNATURE="sha1=56a3df597e02adbc17fb617502c70e19d96a6136", HTTP_X_GITHUB_DELIVERY=six.text_type(uuid4()), ) assert response.status_code == 204 commit_list = list( Commit.objects.filter(organization_id=project.organization_id) .select_related("author") .order_by("-date_added") ) # should be skipping the #skipsentry commit assert len(commit_list) == 2 commit = commit_list[0] assert commit.key == "133d60480286590a610a0eb7352ff6e02b9674c4" assert commit.message == "Update README.md (àgain)" assert commit.author.name == "bàxterthehacker" assert commit.author.email == "*****@*****.**" assert commit.date_added == datetime(2015, 5, 5, 23, 45, 15, tzinfo=timezone.utc) commit = commit_list[1] assert commit.key == "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c" assert commit.message == "Update README.md" assert commit.author.name == "bàxterthehacker" assert commit.author.email == "*****@*****.**" assert commit.date_added == datetime(2015, 5, 5, 23, 40, 15, tzinfo=timezone.utc)
def setUp(self): super(GroupEventsTest, self).setUp() options.set('snuba.events-queries.enabled', False)
def tearDown(self): options.set('snuba.events-queries.enabled', True)
def send_beacon(): """ Send a Beacon to a remote server operated by the Sentry team. See the documentation for more details. """ from sentry import options from sentry.models import Broadcast, Organization, Project, Team, User if not settings.SENTRY_BEACON: logger.info('Not sending beacon (disabled)') return install_id = options.get('sentry:install-id') if not install_id: logger.info('Generated installation ID: %s', install_id) install_id = sha1(uuid4().hex).hexdigest() options.set('sentry:install-id', install_id) end = timezone.now() events_24h = tsdb.get_sums( model=tsdb.models.internal, keys=['events.total'], start=end - timedelta(hours=24), end=end, )['events.total'] payload = { 'install_id': install_id, 'version': sentry.get_version(), 'admin_email': settings.SENTRY_ADMIN_EMAIL, 'data': { # TODO(dcramer): we'd also like to get an idea about the throughput # of the system (i.e. events in 24h) 'users': User.objects.count(), 'projects': Project.objects.count(), 'teams': Team.objects.count(), 'organizations': Organization.objects.count(), 'events.24h': events_24h, } } # TODO(dcramer): relay the response 'notices' as admin broadcasts try: request = safe_urlopen(BEACON_URL, json=payload, timeout=5) response = safe_urlread(request) except Exception: logger.warning('Failed sending beacon', exc_info=True) return data = json.loads(response) if 'version' in data: options.set('sentry:latest_version', data['version']['stable']) if 'notices' in data: upstream_ids = set() for notice in data['notices']: upstream_ids.add(notice['id']) Broadcast.objects.create_or_update(upstream_id=notice['id'], defaults={ 'title': notice['title'], 'link': notice.get('link'), 'message': notice['message'], }) Broadcast.objects.filter(upstream_id__isnull=False, ).exclude( upstream_id__in=upstream_ids, ).update(is_active=False, )