def test_simple(self): project = self.create_project() self.login_as(user=self.user) plugins.get("webhooks").enable(project) url = reverse( "sentry-api-0-project-plugin-details", kwargs={ "organization_slug": project.organization.slug, "project_slug": project.slug, "plugin_id": "webhooks", }, ) audit = AuditLogEntry.objects.filter(target_object=project.id) assert not audit response = self.client.delete(url) audit = AuditLogEntry.objects.get(target_object=project.id) assert audit.event == 112 assert response.status_code == 204, (response.status_code, response.content) assert ProjectOption.objects.get(key="webhooks:enabled", project=project).value is False
def test_configured_multiple_projects(self): plugins.get("trello").set_option("key", "some_value", self.projectA) plugins.get("trello").set_option("key", "another_value", self.projectB) response = self.client.get(self.url) projectList = list( filter(lambda x: x["slug"] == "trello", response.data))[0]["projectList"] assert list( filter(lambda x: x["projectId"] == self.projectA.id, projectList))[0] == { "projectId": self.projectA.id, "projectSlug": self.projectA.slug, "projectName": self.projectA.name, "enabled": False, "configured": True, "projectPlatform": None, } assert list( filter(lambda x: x["projectId"] == self.projectB.id, projectList))[0] == { "projectId": self.projectB.id, "projectSlug": self.projectB.slug, "projectName": self.projectB.name, "enabled": False, "configured": True, "projectPlatform": "react", }
def test_disabled_proejct(self): plugins.get("trello").enable(self.projectA) plugins.get("trello").set_option("key", "some_value", self.projectA) self.projectA.status = 1 self.projectA.save() response = self.client.get(self.url) assert list(filter(lambda x: x["slug"] == "trello", response.data))[0]["projectList"] == []
def test_simple(self, test_configuration): plugins.get("webhooks").disable(self.project) self.get_success_response(self.project.organization.slug, self.project.slug, "webhooks") audit = AuditLogEntry.objects.get(target_object=self.project.id) assert audit.event == 110 assert ProjectOption.objects.get(key="webhooks:enabled", project=self.project).value is True audit.delete() # Testing the Plugin self.get_success_response(self.project.organization.slug, self.project.slug, "webhooks", **{"test": True}) test_configuration.assert_called_once_with(self.project) # Reset the plugin response = self.get_success_response(self.project.organization.slug, self.project.slug, "webhooks", **{"reset": True}) audit = AuditLogEntry.objects.get(target_object=self.project.id) test_configuration.assert_called_once_with(self.project) assert audit.event == 111 configs = response.data.get("config") for config in configs: assert config.get("value") is None
def setUp(self): self.projectA = self.create_project() self.projectB = self.create_project( organization=self.projectA.organization) plugins.get("webhooks").enable(self.projectA) plugins.get("mail").enable(self.projectB) self.login_as(user=self.user)
def test_simple(self): plugins.get("webhooks").enable(self.project) self.get_success_response(self.project.organization.slug, self.project.slug, "webhooks") audit = AuditLogEntry.objects.get(target_object=self.project.id) assert audit.event == 112 assert (ProjectOption.objects.get(key="webhooks:enabled", project=self.project).value is False)
def test_configured_and_enabled(self): plugins.get("trello").enable(self.projectA) plugins.get("trello").set_option("key", "some_value", self.projectA) response = self.client.get(self.url) assert filter(lambda x: x["slug"] == "trello", response.data)[0]["projectList"] == [ { "projectId": self.projectA.id, "projectSlug": self.projectA.slug, "projectName": self.projectA.name, "enabled": True, "configured": True, } ]
def get_provider_name(provider_type, provider_slug): """ The things that users think of as "integrations" are actually three different things: integrations, plugins, and sentryapps. A user requesting than an integration be installed only actually knows the "provider" they want and not what type they want. This function looks up the display name for the integration they want installed. :param provider_type: One of: "first_party", "plugin", or "sentry_app". :param provider_slug: The unique identifier for the provider. :return: The display name for the provider. :raises: ValueError if provider_type is not one of the three from above. :raises: RuntimeError if the provider is not found. """ try: if provider_type == "first_party": return integrations.get(provider_slug).name elif provider_type == "plugin": return plugins.get(provider_slug).title elif provider_type == "sentry_app": return SentryApp.objects.get(slug=provider_slug).name else: raise ValueError(f"Invalid providerType {provider_type}") except (KeyError, SentryApp.DoesNotExist): raise RuntimeError(f"Provider {provider_slug} not found")
def test_disable_plugin_when_fully_migrated(self): self._stub_github() project = Project.objects.create(organization_id=self.organization.id) plugin = plugins.get("github") plugin.enable(project) # Accessible to new Integration - mocked in _stub_github Repository.objects.create( organization_id=self.organization.id, name="Test-Organization/foo", url="https://github.com/Test-Organization/foo", provider="github", external_id="123", config={"name": "Test-Organization/foo"}, ) # Enabled before assert "github" in [p.slug for p in plugins.for_project(project)] with self.tasks(): self.assert_setup_flow() # Disabled after Integration installed assert "github" not in [p.slug for p in plugins.for_project(project)]
def test_get(self): project = self.create_project() issues = plugins.get("issuetrackingplugin2") with patch.object(issues, "is_hidden", return_value=True): self.login_as(user=self.user) url = reverse( "sentry-api-0-project-plugins", kwargs={ "organization_slug": project.organization.slug, "project_slug": project.slug, }, ) response = self.client.get(url) assert response.status_code == 200, (response.status_code, response.content) assert len(response.data) >= 9 auto_tag = response.data[0] assert auto_tag["name"] == "Auto Tag: Browsers" assert auto_tag["enabled"] is True assert auto_tag["isHidden"] is False self.assert_plugin_shape(auto_tag) issues = filter(lambda p: p["slug"] == "issuetrackingplugin2", response.data)[0] assert issues["name"] == "IssueTrackingPlugin2" assert issues["enabled"] is False assert issues["isHidden"] is True self.assert_plugin_shape(issues)
def test_simple(self, test_configuration): project = self.create_project() self.login_as(user=self.user) plugins.get("webhooks").disable(project) url = reverse( "sentry-api-0-project-plugin-details", kwargs={ "organization_slug": project.organization.slug, "project_slug": project.slug, "plugin_id": "webhooks", }, ) audit = AuditLogEntry.objects.filter(target_object=project.id) assert not audit response = self.client.post(url) audit = AuditLogEntry.objects.get(target_object=project.id) assert audit.event == 110 assert response.status_code == 201, (response.status_code, response.content) assert ProjectOption.objects.get(key="webhooks:enabled", project=project).value is True audit.delete() # Testing the Plugin response = self.client.post(url, {"test": True}) test_configuration.assert_called_once_with(project) assert response.status_code == 200, (response.status_code, response.content) # Reset the plugin response = self.client.post(url, {"reset": True}) audit = AuditLogEntry.objects.get(target_object=project.id) test_configuration.assert_called_once_with(project) assert audit.event == 111 assert response.status_code == 200, (response.status_code, response.content) configs = response.data.get("config") for config in configs: assert config.get("value") is None
def setUp(self): super(IssuePlugin2GroupActionTest, self).setUp() self.project = self.create_project() self.plugin_instance = plugins.get(slug="issuetrackingplugin2") min_ago = iso_format(before_now(minutes=1)) self.event = self.store_event( data={"timestamp": min_ago, "fingerprint": ["group-1"]}, project_id=self.project.id ) self.group = self.event.group
def setUp(self): self.organization = self.create_organization() self.user = self.create_user() self.user_2 = self.create_user() self.team = self.create_team(self.organization, members=[self.user, self.user_2]) self.project = self.create_project(organization=self.organization, teams=[self.team]) self.mail = plugins.get("mail")
def test_disable_for_all_projects(self): plugin = plugins.get("example") plugin.enable(self.project) assert plugin in plugins.for_project(self.project) self.migrator.disable_for_all_projects(plugin) assert plugin not in plugins.for_project(self.project)
def setUp(self): self.organization = self.create_organization() self.user = self.create_user() self.user_2 = self.create_user() self.team = self.create_team(self.organization, members=[self.user, self.user_2]) self.project = self.create_project(organization=self.organization, teams=[self.team]) self.project.flags.has_issue_alerts_targeting = False self.project.save() self.mail = plugins.get("mail")
def post(self, request, plugin_id, project_id, signature): try: project = Project.objects.get_from_cache(id=project_id) except Project.DoesNotExist: logger.warn( "release-webhook.invalid-project", extra={"project_id": project_id, "plugin_id": plugin_id}, ) return HttpResponse(status=404) logger.info( "release-webhook.incoming", extra={"project_id": project_id, "plugin_id": plugin_id} ) token = ProjectOption.objects.get_value(project, "sentry:release-token") if token is None: logger.warn( "release-webhook.missing-token", extra={"project_id": project_id, "plugin_id": plugin_id}, ) return HttpResponse(status=403) if not self.verify(plugin_id, project_id, token, signature): logger.warn( "release-webhook.invalid-signature", extra={"project_id": project_id, "plugin_id": plugin_id}, ) return HttpResponse(status=403) if plugin_id == "builtin": return self._handle_builtin(request, project) plugin = plugins.get(plugin_id) if not plugin.is_enabled(project): logger.warn( "release-webhook.plugin-disabled", extra={"project_id": project_id, "plugin_id": plugin_id}, ) return HttpResponse(status=403) cls = plugin.get_release_hook() hook = cls(project) try: hook.handle(request) except HookValidationError as exc: return HttpResponse( status=400, content=json.dumps({"error": six.text_type(exc)}), content_type="application/json", ) return HttpResponse(status=204)
def after(self, event, state): service = self.get_option("service") extra = {"event_id": event.event_id} if not service: self.logger.info("rules.fail.is_configured", extra=extra) return plugin = None app = None try: app = SentryApp.objects.get(slug=service) except SentryApp.DoesNotExist: pass if app: kwargs = {"sentry_app": app} metrics.incr("notifications.sent", instance=app.slug, skip_internal=False) yield self.future(notify_sentry_app, **kwargs) try: plugin = plugins.get(service) except KeyError: if not app: # If we can't find the sentry app OR plugin, # we've removed the plugin no need to error, just skip. extra["plugin"] = service self.logger.info("rules.fail.plugin_does_not_exist", extra=extra) return if plugin: if not plugin.is_enabled(self.project): extra["project_id"] = self.project.id self.logger.info("rules.fail.is_enabled", extra=extra) return group = event.group if not plugin.should_notify(group=group, event=event): extra["group_id"] = group.id self.logger.info("rule.fail.should_notify", extra=extra) return metrics.incr("notifications.sent", instance=plugin.slug, skip_internal=False) yield self.future(plugin.rule_notify)
def plugin_post_process_group(plugin_slug, event, **kwargs): """ Fires post processing hooks for a group. """ set_current_project(event.project_id) from sentry.plugins.base import plugins plugin = plugins.get(plugin_slug) safe_execute(plugin.post_process, event=event, group=event.group, expected_errors=(PluginError, ), **kwargs)
def plugin_post_process_group(plugin_slug, event, **kwargs): """ Fires post processing hooks for a group. """ with configure_scope() as scope: scope.set_tag("project", event.project_id) from sentry.plugins.base import plugins plugin = plugins.get(plugin_slug) safe_execute( plugin.post_process, event=event, group=event.group, expected_errors=(PluginError,), **kwargs )
def test_sort_by_slug(self): another = self.create_project(slug="another") plugins.get("trello").set_option("key", "some_value", self.projectA) plugins.get("trello").set_option("key", "some_value", self.projectB) plugins.get("trello").set_option("key", "some_value", another) url = self.url + "?plugins=trello" response = self.client.get(url) assert map(lambda x: x["projectSlug"], response.data[0]["projectList"]) == [ "another", "proj_a", "proj_b", ]
def handle(self, request, organization, project, group_id, slug): group = get_object_or_404(Group, pk=group_id, project=project) try: plugin = plugins.get(slug) except KeyError: raise Http404("Plugin not found") GroupMeta.objects.populate_cache([group]) response = plugin.get_view_response(request, group) if response: return response redirect = request.META.get("HTTP_REFERER", "") if not is_safe_url(redirect, allowed_hosts=(request.get_host(),)): redirect = f"/{organization.slug}/{group.project.slug}/" return HttpResponseRedirect(redirect)
def test_integrated(self): event = self.store_event(data={}, project_id=self.project.id) action_data = { "id": "sentry.rules.actions.notify_event.NotifyEventAction" } condition_data = { "id": "sentry.rules.conditions.every_event.EveryEventCondition" } Rule.objects.filter(project=event.project).delete() rule = Rule.objects.create(project=event.project, data={ "conditions": [condition_data], "actions": [action_data] }) rp = RuleProcessor( event, is_new=True, is_regression=True, is_new_group_environment=True, has_reappeared=True, ) results = list(rp.apply()) assert len(results) == 1 callback, futures = results[0] assert callback == plugins.get("mail").rule_notify assert len(futures) == 1 assert futures[0].rule == rule assert futures[0].kwargs == {} # should not apply twice due to default frequency results = list(rp.apply()) assert len(results) == 0 # now ensure that moving the last update backwards # in time causes the rule to trigger again GroupRuleStatus.objects.filter( rule=rule).update(last_active=timezone.now() - timedelta(minutes=Rule.DEFAULT_FREQUENCY + 1)) results = list(rp.apply()) assert len(results) == 1
def test_plugin_external_issue_annotation(self): group = self.create_group() GroupMeta.objects.create(group=group, key="trello:tid", value="134") plugins.get("trello").enable(group.project) plugins.get("trello").set_option("key", "some_value", group.project) plugins.get("trello").set_option("token", "another_value", group.project) self.login_as(user=self.user) url = f"/api/0/issues/{group.id}/" response = self.client.get(url, format="json") assert response.data["annotations"] == ['<a href="https://trello.com/c/134">Trello-134</a>']
def test_disable_plugin_when_fully_migrated(self): project = Project.objects.create(organization_id=self.organization.id) plugin = plugins.get("bitbucket") plugin.enable(project) # Accessible to new Integration Repository.objects.create( organization_id=self.organization.id, name="sentryuser/repo", url="https://bitbucket.org/sentryuser/repo", provider="bitbucket", external_id="123456", config={"name": "sentryuser/repo"}, ) self.client.post(self.path, data=self.data_from_bitbucket) integration = Integration.objects.get(provider=self.provider, external_id=self.client_key) responses.add( responses.GET, "https://api.bitbucket.org/2.0/repositories/sentryuser/repo/hooks", json={"values": [{ "description": "sentry-bitbucket-repo-hook" }]}, ) assert "bitbucket" in [p.slug for p in plugins.for_project(project)] with self.tasks(): BitbucketIntegrationProvider().post_install( integration, self.organization) assert "bitbucket" not in [ p.slug for p in plugins.for_project(project) ]
def get_provider_name(provider_type: str, provider_slug: str) -> str | None: """ The things that users think of as "integrations" are actually three different things: integrations, plugins, and sentryapps. A user requesting than an integration be installed only actually knows the "provider" they want and not what type they want. This function looks up the display name for the integration they want installed. :param provider_type: One of: "first_party", "plugin", or "sentry_app". :param provider_slug: The unique identifier for the provider. :return: The display name for the provider or None. """ if provider_type == "first_party": if integrations.exists(provider_slug): return integrations.get(provider_slug).name elif provider_type == "plugin": if plugins.exists(provider_slug): return plugins.get(provider_slug).title elif provider_type == "sentry_app": sentry_app = SentryApp.objects.filter(slug=provider_slug).first() if sentry_app: return sentry_app.name return None
def get(self, request, organization): """ List one or more plugin configurations, including a `projectList` for each plugin which contains all the projects that have that specific plugin both configured and enabled. - similar to the `OrganizationPluginsEndpoint`, and can eventually replace it :qparam plugins array[string]: an optional list of plugin ids (slugs) if you want specific plugins. If not set, will return configurations for all plugins. """ desired_plugins = [] for slug in request.GET.getlist("plugins") or (): # if the user request a plugin that doesn't exist, throw 404 try: desired_plugins.append(plugins.get(slug)) except KeyError: return Response({"detail": "Plugin %s not found" % slug}, status=404) # if no plugins were specified, grab all plugins but limit by those that have the ability to be configured if not desired_plugins: desired_plugins = list(plugins.plugin_that_can_be_configured()) # `keys_to_check` are the ProjectOption keys that tell us if a plugin is enabled (e.g. `plugin:enabled`) or are # configured properly, meaning they have the required information - plugin.required_field - needed for the # plugin to work (ex:`opsgenie:api_key`) keys_to_check = [] for plugin in desired_plugins: keys_to_check.append("%s:enabled" % plugin.slug) if plugin.required_field: keys_to_check.append("%s:%s" % (plugin.slug, plugin.required_field)) # Get all the project options for org that have truthy values project_options = ProjectOption.objects.filter( key__in=keys_to_check, project__organization=organization ).exclude(value__in=[False, ""]) """ This map stores info about whether a plugin is configured and/or enabled { "plugin_slug": { "project_id": { "enabled": True, "configured": False }, }, } """ info_by_plugin_project = {} for project_option in project_options: [slug, field] = project_option.key.split(":") project_id = project_option.project_id # first add to the set of all projects by plugin info_by_plugin_project.setdefault(slug, {}).setdefault( project_id, {"enabled": False, "configured": False} ) # next check if enabled if field == "enabled": info_by_plugin_project[slug][project_id]["enabled"] = True # if the projectoption is not the enable field, it's configuration field else: info_by_plugin_project[slug][project_id]["configured"] = True # get the IDs of all projects for found project options and grab them from the DB project_id_set = set([project_option.project_id for project_option in project_options]) projects = Project.objects.filter(id__in=project_id_set, status=ObjectStatus.VISIBLE) # create a key/value map of our projects project_map = {project.id: project for project in projects} # iterate through the desired plugins and serialize them serialized_plugins = [] for plugin in desired_plugins: serialized_plugin = serialize(plugin, request.user, PluginSerializer()) serialized_plugin["projectList"] = [] info_by_project = info_by_plugin_project.get(plugin.slug, {}) # iterate through the projects for project_id, plugin_info in six.iteritems(info_by_project): # if the project is being deleted if project_id not in project_map: continue project = project_map[project_id] # only include plugins which are configured if not plugin_info["configured"]: continue serialized_plugin["projectList"].append( { "projectId": project.id, "projectSlug": project.slug, "projectName": project.name, # TODO(steve): do we need? "enabled": plugin_info["enabled"], "configured": plugin_info["configured"], # TODO(steve): do we need? "projectPlatform": project.platform, } ) # sort by the projectSlug serialized_plugin["projectList"].sort(key=lambda x: x["projectSlug"]) serialized_plugins.append(serialized_plugin) return Response(serialized_plugins)
def test_does_not_disable_any_plugin(self): plugin = plugins.get("webhooks") plugin.enable(self.project) self.migrator.call() assert plugin in plugins.for_project(self.project)
def split_key(key): from sentry.plugins.base import plugins plugin_slug, _, project_id = key.split(":", 2) return plugins.get(plugin_slug), Project.objects.get(pk=project_id)
def _get_plugin(self, plugin_id): try: return plugins.get(plugin_id) except KeyError: raise ResourceDoesNotExist
def test_enabled_not_configured(self): plugins.get("webhooks").enable(self.projectA) response = self.client.get(self.url) assert filter(lambda x: x["slug"] == "webhooks", response.data)[0]["projectList"] == []