def update_organization_config(self, data): # data = {"project_mappings": [[sentry_project_id, vercel_project_id]]} vercel_client = self.get_client() config = self.org_integration.config new_mappings = data["project_mappings"] old_mappings = config.get("project_mappings") or [] for mapping in new_mappings: # skip any mappings that already exist if mapping in old_mappings: continue [sentry_project_id, vercel_project_id] = mapping sentry_project = Project.objects.get(id=sentry_project_id) enabled_dsn = ProjectKey.get_default(project=sentry_project) if not enabled_dsn: raise IntegrationError("You must have an enabled DSN to continue!") source_code_provider = self.get_source_code_provider(vercel_client, vercel_project_id) if not source_code_provider: raise IntegrationError( "You must connect your Vercel project to a Git repository to continue!" ) sentry_project_dsn = enabled_dsn.get_dsn(public=True) uuid = uuid4().hex sentry_app_installation = SentryAppInstallationForProvider.objects.get( organization=sentry_project.organization.id, provider="vercel" ) sentry_auth_token = sentry_app_installation.get_token( self.organization_id, provider="vercel" ) secret_names = [ "SENTRY_ORG_%s" % uuid, "SENTRY_PROJECT_%s" % uuid, "NEXT_PUBLIC_SENTRY_DSN_%s" % uuid, "SENTRY_AUTH_TOKEN_%s" % uuid, ] values = [ sentry_project.organization.slug, sentry_project.slug, sentry_project_dsn, sentry_auth_token, ] env_var_names = [ "SENTRY_ORG", "SENTRY_PROJECT", "NEXT_PUBLIC_SENTRY_DSN", "SENTRY_AUTH_TOKEN", "VERCEL_%s_COMMIT_SHA" % source_code_provider.upper(), ] secrets = [] for name, val in zip(secret_names, values): secrets.append(self.create_secret(vercel_client, vercel_project_id, name, val)) secrets.append("") for secret, env_var in zip(secrets, env_var_names): self.create_env_var(vercel_client, vercel_project_id, env_var, secret) config.update(data) self.org_integration.update(config=config)
def project_key(self): from sentry.models import ProjectKey if not settings.SENTRY_PROJECT: return None key = None try: if settings.SENTRY_PROJECT_KEY is not None: key = ProjectKey.objects.get( id=settings.SENTRY_PROJECT_KEY, project=settings.SENTRY_PROJECT, ) else: key = ProjectKey.get_default(settings.SENTRY_PROJECT) except Exception as exc: # if the relation fails to query or is missing completely, lets handle # it gracefully self.error_logger.warn('internal-error.unable-to-fetch-project', extra={ 'project_id': settings.SENTRY_PROJECT, 'project_key': settings.SENTRY_PROJECT_KEY, 'error_message': six.text_type(exc), }) if key is None: self.error_logger.warn('internal-error.no-project-available', extra={ 'project_id': settings.SENTRY_PROJECT, 'project_key': settings.SENTRY_PROJECT_KEY, }) return key
def get_project_key(): from sentry.models import ProjectKey if not settings.SENTRY_PROJECT: return None key = None try: if settings.SENTRY_PROJECT_KEY is not None: key = ProjectKey.objects.get(id=settings.SENTRY_PROJECT_KEY, project=settings.SENTRY_PROJECT) else: key = ProjectKey.get_default(settings.SENTRY_PROJECT) except Exception as exc: # if the relation fails to query or is missing completely, lets handle # it gracefully sdk_logger.warning( "internal-error.unable-to-fetch-project", extra={ "project_id": settings.SENTRY_PROJECT, "project_key": settings.SENTRY_PROJECT_KEY, "error_message": str(exc), }, ) if key is None: sdk_logger.warning( "internal-error.no-project-available", extra={ "project_id": settings.SENTRY_PROJECT, "project_key": settings.SENTRY_PROJECT_KEY, }, ) return key
def get_dsn_for_project(organization_id, project_id): try: project = Project.objects.get(organization_id=organization_id, id=project_id) except Project.DoesNotExist: raise IntegrationError("No valid project") enabled_dsn = ProjectKey.get_default(project=project) if not enabled_dsn: raise IntegrationError("Project does not have DSN enabled") return enabled_dsn.get_dsn(public=True)
def send(self, **kwargs): # Report the issue to an upstream Sentry if active # NOTE: we don't want to check self.is_enabled() like normal, since # is_enabled behavior is overridden in this class. We explicitly # want to check if the remote is active. if self.remote.is_active(): from sentry import options # Append some extra tags that are useful for remote reporting super_kwargs = copy.deepcopy(kwargs) super_kwargs['tags']['install-id'] = options.get( 'sentry:install-id') super(SentryInternalClient, self).send(**super_kwargs) if not is_current_event_safe(): return # These imports all need to be internal to this function as this class # is set up by django while still parsing LOGGING settings and we # cannot import this stuff until settings are finalized. from sentry.models import ProjectKey from sentry.web.api import StoreView from django.test import RequestFactory key = None if settings.SENTRY_PROJECT_KEY is not None: key = ProjectKey.objects.filter( id=settings.SENTRY_PROJECT_KEY, project=settings.SENTRY_PROJECT).first() if key is None: key = ProjectKey.get_default(settings.SENTRY_PROJECT) if key is None: return client_string = 'raven-python/%s' % (raven.VERSION, ) headers = { 'HTTP_X_SENTRY_AUTH': get_auth_header( protocol=self.protocol_version, timestamp=time.time(), client=client_string, api_key=key.public_key, api_secret=key.secret_key, ), 'HTTP_CONTENT_ENCODING': self.get_content_encoding(), } self.request_factory = self.request_factory or RequestFactory() request = self.request_factory.post( '/api/store', data=self.encode(kwargs), content_type='application/octet-stream', **headers) StoreView.as_view()( request, project_id=six.text_type(settings.SENTRY_PROJECT), )
def get(self, request, project): data = options.get('sentry:docs') project_key = ProjectKey.get_default(project) context = { 'platforms': data['platforms'], } if project_key: context['dsn'] = project_key.dsn_private context['dsnPublic'] = project_key.dsn_public return Response(context)
def send(self, **kwargs): # Report the issue to an upstream Sentry if active # NOTE: we don't want to check self.is_enabled() like normal, since # is_enabled behavior is overridden in this class. We explicitly # want to check if the remote is active. if self.remote.is_active(): from sentry import options # Append some extra tags that are useful for remote reporting super_kwargs = copy.deepcopy(kwargs) super_kwargs['tags']['install-id'] = options.get('sentry:install-id') super(SentryInternalClient, self).send(**super_kwargs) if not is_current_event_safe(): return # These imports all need to be internal to this function as this class # is set up by django while still parsing LOGGING settings and we # cannot import this stuff until settings are finalized. from sentry.models import ProjectKey from sentry.web.api import StoreView from django.test import RequestFactory key = None if settings.SENTRY_PROJECT_KEY is not None: key = ProjectKey.objects.filter( id=settings.SENTRY_PROJECT_KEY, project=settings.SENTRY_PROJECT).first() if key is None: key = ProjectKey.get_default(settings.SENTRY_PROJECT) if key is None: return client_string = 'raven-python/%s' % (raven.VERSION,) headers = { 'HTTP_X_SENTRY_AUTH': get_auth_header( protocol=self.protocol_version, timestamp=time.time(), client=client_string, api_key=key.public_key, api_secret=key.secret_key, ), 'HTTP_CONTENT_ENCODING': self.get_content_encoding(), } self.request_factory = self.request_factory or RequestFactory() request = self.request_factory.post( '/api/store', data=self.encode(kwargs), content_type='application/octet-stream', **headers ) StoreView.as_view()( request, project_id=six.text_type(settings.SENTRY_PROJECT), )
def get(self, request, project, platform): data = load_doc(platform) if not data: raise ResourceDoesNotExist project_key = ProjectKey.get_default(project) return Response({ 'id': data['id'], 'name': data['name'], 'html': replace_keys(data['html'], project_key), 'link': data['link'], })
def get(self, request, project, platform): data = load_doc(platform) if not data: raise ResourceDoesNotExist project_key = ProjectKey.get_default(project) return Response({ "id": data["id"], "name": data["name"], "html": replace_keys(data["html"], project_key), "link": data["link"], })
def dispatch(self, request, pipeline): organization = pipeline.organization # TODO: make project selection part of flow project = Project.objects.filter(organization=organization, platform="node-awslambda").first() if not project: raise IntegrationError("No valid project") enabled_dsn = ProjectKey.get_default(project=project) if not enabled_dsn: raise IntegrationError("Project does not have DSN enabled") sentry_project_dsn = enabled_dsn.get_dsn(public=True) arn = pipeline.fetch_state("arn") aws_external_id = pipeline.fetch_state("aws_external_id") lambda_client = gen_aws_lambda_client(arn, aws_external_id) lambda_functions = filter( lambda x: x.get("Runtime") in SUPPORTED_RUNTIMES, lambda_client.list_functions()["Functions"], ) for function in lambda_functions: name = function["FunctionName"] # TODO: load existing layers and environment and append to them try: lambda_client.update_function_configuration( FunctionName=name, Layers=[options.get("aws-lambda.node-layer-arn")], Environment={ "Variables": { "NODE_OPTIONS": "-r @sentry/serverless/dist/auto", "SENTRY_DSN": sentry_project_dsn, "SENTRY_TRACES_SAMPLE_RATE": "1.0", } }, ) except Exception as e: logger.info( "update_function_configuration.error", extra={ "organization_id": organization.id, "lambda_name": name, "arn": arn, "error": six.text_type(e), }, ) return pipeline.next_step()
def get(self, request, project): data = load_doc('_platforms') if data is None: raise RuntimeError('Docs not built') project_key = ProjectKey.get_default(project) context = { 'platforms': data['platforms'], } if project_key: context['dsn'] = project_key.dsn_private context['dsnPublic'] = project_key.dsn_public return Response(context)
def update_organization_config(self, data): # data = {"project_mappings": [[sentry_project_id, vercel_project_id]]} vercel_client = self.get_client() config = self.org_integration.config new_mappings = data["project_mappings"] old_mappings = config.get("project_mappings") or [] for mapping in new_mappings: # skip any mappings that already exist if mapping in old_mappings: continue [sentry_project_id, vercel_project_id] = mapping sentry_project = Project.objects.get(id=sentry_project_id) enabled_dsn = ProjectKey.get_default(project=sentry_project) if not enabled_dsn: raise IntegrationError( "You must have an enabled DSN to continue!") sentry_project_dsn = enabled_dsn.get_dsn(public=True) org_secret = self.create_secret(vercel_client, vercel_project_id, "SENTRY_ORG", sentry_project.organization.slug) project_secret = self.create_secret( vercel_client, vercel_project_id, "SENTRY_PROJECT_%s" % sentry_project_id, sentry_project.slug, ) dsn_secret = self.create_secret( vercel_client, vercel_project_id, "NEXT_PUBLIC_SENTRY_DSN_%s" % sentry_project_id, sentry_project_dsn, ) self.create_env_var(vercel_client, vercel_project_id, "SENTRY_ORG", org_secret) self.create_env_var(vercel_client, vercel_project_id, "SENTRY_PROJECT", project_secret) self.create_env_var(vercel_client, vercel_project_id, "NEXT_PUBLIC_SENTRY_DSN", dsn_secret) config.update(data) self.org_integration.update(config=config)
def test_upgrade_org_config_no_dsn(self): """Test that the function doesn't progress if there is no active DSN""" with self.tasks(): self.assert_setup_flow() project_id = self.project.id org = self.organization data = { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } integration = Integration.objects.get(provider=self.provider.key) installation = integration.get_installation(org.id) dsn = ProjectKey.get_default(project=Project.objects.get(id=project_id)) dsn.update(id=dsn.id, status=ProjectKeyStatus.INACTIVE) with self.assertRaises(ValidationError): installation.update_organization_config(data)
def update_organization_config(self, data): # data = {"project_mappings": [[sentry_project_id, vercel_project_id]]} metadata = self.model.metadata vercel_client = VercelClient(metadata["access_token"], metadata.get("team_id")) config = self.org_integration.config [sentry_project_id, vercel_project_id] = data["project_mappings"][ -1] # TODO: update this to work in the case where a project is removed sentry_project = Project.objects.get(id=sentry_project_id) enabled_dsn = ProjectKey.get_default(project=sentry_project) if not enabled_dsn: raise IntegrationError("You must have an enabled DSN to continue!") sentry_project_dsn = enabled_dsn.get_dsn(public=True) org_secret = self.create_secret(vercel_client, vercel_project_id, "SENTRY_ORG", sentry_project.organization.slug) project_secret = self.create_secret( vercel_client, vercel_project_id, "SENTRY_PROJECT_%s" % sentry_project_id, sentry_project.slug, ) dsn_secret = self.create_secret( vercel_client, vercel_project_id, "NEXT_PUBLIC_SENTRY_DSN_%s" % sentry_project_id, sentry_project_dsn, ) self.create_env_var(vercel_client, vercel_project_id, "SENTRY_ORG", org_secret) self.create_env_var(vercel_client, vercel_project_id, "SENTRY_PROJECT", project_secret) self.create_env_var(vercel_client, vercel_project_id, "NEXT_PUBLIC_SENTRY_DSN", dsn_secret) config.update(data) self.org_integration.update(config=config)
def test_update_org_config_vars_exist(self): """Test the case wherein the secret and env vars already exist""" with self.tasks(): self.assert_setup_flow() org = self.organization project_id = self.project.id enabled_dsn = ProjectKey.get_default(project=Project.objects.get( id=project_id)).get_dsn(public=True) sentry_auth_token = SentryAppInstallationForProvider.get_token( org.id, "vercel") env_var_map = { "SENTRY_ORG": { "type": "encrypted", "value": org.slug }, "SENTRY_PROJECT": { "type": "encrypted", "value": self.project.slug }, "SENTRY_DSN": { "type": "encrypted", "value": enabled_dsn }, "SENTRY_AUTH_TOKEN": { "type": "secret", "value": sentry_auth_token }, "VERCEL_GIT_COMMIT_SHA": { "type": "system", "value": "VERCEL_GIT_COMMIT_SHA" }, } # mock get_project API call responses.add( responses.GET, "https://api.vercel.com/v1/projects/%s" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "link": { "type": "github" }, "framework": "gatsby" }, ) # mock update env vars count = 0 for env_var, details in env_var_map.items(): # mock try to create env var responses.add( responses.POST, "https://api.vercel.com/v7/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={"error": { "code": "ENV_ALREADY_EXISTS" }}, status=400, ) # mock get env var responses.add( responses.GET, "https://api.vercel.com/v7/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={"envs": [{ "id": count, "key": env_var }]}, ) # mock update env var responses.add( responses.PATCH, "https://api.vercel.com/v7/projects/%s/env/%s" % ("Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", count), json={ "key": env_var, "value": details["value"], "target": ["production"], "type": details["type"], }, ) count += 1 data = { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } integration = Integration.objects.get(provider=self.provider.key) installation = integration.get_installation(org.id) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == {} installation.update_organization_config(data) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } req_params = json.loads(responses.calls[5].request.body) assert req_params["key"] == "SENTRY_ORG" assert req_params["value"] == org.slug assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[8].request.body) assert req_params["key"] == "SENTRY_PROJECT" assert req_params["value"] == self.project.slug assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[11].request.body) assert req_params["key"] == "SENTRY_DSN" assert req_params["value"] == enabled_dsn assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[14].request.body) assert req_params["key"] == "SENTRY_AUTH_TOKEN" assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[17].request.body) assert req_params["key"] == "VERCEL_GIT_COMMIT_SHA" assert req_params["value"] == "VERCEL_GIT_COMMIT_SHA" assert req_params["target"] == ["production"] assert req_params["type"] == "system"
def test_update_organization_config(self): """Test that Vercel environment variables are created""" with self.tasks(): self.assert_setup_flow() org = self.organization project_id = self.project.id enabled_dsn = ProjectKey.get_default(project=Project.objects.get( id=project_id)).get_dsn(public=True) sentry_auth_token = SentryAppInstallationForProvider.get_token( org.id, "vercel") env_var_map = { "SENTRY_ORG": { "type": "encrypted", "value": org.slug }, "SENTRY_PROJECT": { "type": "encrypted", "value": self.project.slug }, "SENTRY_DSN": { "type": "encrypted", "value": enabled_dsn }, "SENTRY_AUTH_TOKEN": { "type": "encrypted", "value": sentry_auth_token }, "VERCEL_GIT_COMMIT_SHA": { "type": "system", "value": "VERCEL_GIT_COMMIT_SHA" }, } # mock get_project API call responses.add( responses.GET, "https://api.vercel.com/v1/projects/%s" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "link": { "type": "github" }, "framework": "nextjs" }, ) # mock create the env vars for env_var, details in env_var_map.items(): responses.add( responses.POST, "https://api.vercel.com/v7/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "key": env_var, "value": details["value"], "target": ["production"], "type": details["type"], }, ) integration = Integration.objects.get(provider=self.provider.key) installation = integration.get_installation(org.id) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == {} data = { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } installation.update_organization_config(data) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } # assert the env vars were created correctly req_params = json.loads(responses.calls[5].request.body) assert req_params["key"] == "SENTRY_ORG" assert req_params["value"] == org.slug assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[6].request.body) assert req_params["key"] == "SENTRY_PROJECT" assert req_params["value"] == self.project.slug assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[7].request.body) assert req_params["key"] == "NEXT_PUBLIC_SENTRY_DSN" assert req_params["value"] == enabled_dsn assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[8].request.body) assert req_params["key"] == "SENTRY_AUTH_TOKEN" assert req_params["target"] == ["production"] assert req_params["type"] == "encrypted" req_params = json.loads(responses.calls[9].request.body) assert req_params["key"] == "VERCEL_GIT_COMMIT_SHA" assert req_params["value"] == "VERCEL_GIT_COMMIT_SHA" assert req_params["target"] == ["production"] assert req_params["type"] == "system"
def dispatch(self, request, pipeline): if "finish_pipeline" in request.GET: return pipeline.finish_pipeline() organization = pipeline.organization arn = pipeline.fetch_state("arn") region = parse_arn(arn)["region"] # the layer ARN has to be located within a specific region node_layer_arn = get_aws_node_arn(region) project_id = pipeline.fetch_state("project_id") aws_external_id = pipeline.fetch_state("aws_external_id") enabled_lambdas = pipeline.fetch_state("enabled_lambdas") try: project = Project.objects.get(organization=organization, id=project_id) except Project.DoesNotExist: raise IntegrationError("No valid project") enabled_dsn = ProjectKey.get_default(project=project) if not enabled_dsn: raise IntegrationError("Project does not have DSN enabled") sentry_project_dsn = enabled_dsn.get_dsn(public=True) lambda_client = gen_aws_client(arn, aws_external_id) lambda_functions = filter( lambda x: x.get("Runtime") in SUPPORTED_RUNTIMES, lambda_client.list_functions()["Functions"], ) lambda_functions.sort(key=lambda x: x["FunctionName"].lower()) failures = [] for function in lambda_functions: name = function["FunctionName"] # check to see if the user wants to enable this function if not enabled_lambdas.get(name): continue try: # update the env variables env_variables = function.get("Environment", {}).get("Variables", {}) env_variables.update({ "NODE_OPTIONS": "-r @sentry/serverless/dist/auto", "SENTRY_DSN": sentry_project_dsn, "SENTRY_TRACES_SAMPLE_RATE": "1.0", }) # find the sentry layer and update it or insert new layer to end layers = function.get("Layers", []) sentry_layer_index = get_index_of_sentry_layer( layers, node_layer_arn) if sentry_layer_index > -1: layers[sentry_layer_index] = node_layer_arn else: layers.append(node_layer_arn) lambda_client.update_function_configuration( FunctionName=name, Layers=layers, Environment={"Variables": env_variables}, ) except Exception as e: failures.append(function) logger.info( "update_function_configuration.error", extra={ "organization_id": organization.id, "lambda_name": name, "arn": arn, "error": six.text_type(e), }, ) # if we have failures, show them to the user # otherwise, finish if failures: return self.render_react_view(request, "awsLambdaFailureDetails", {"lambdaFunctionFailures": failures}) else: return pipeline.finish_pipeline()
def dispatch(self, request, pipeline): if "finish_pipeline" in request.GET: return pipeline.finish_pipeline() organization = pipeline.organization arn = pipeline.fetch_state("arn") project_id = pipeline.fetch_state("project_id") aws_external_id = pipeline.fetch_state("aws_external_id") enabled_lambdas = pipeline.fetch_state("enabled_lambdas") try: project = Project.objects.get(organization=organization, id=project_id) except Project.DoesNotExist: raise IntegrationError("No valid project") enabled_dsn = ProjectKey.get_default(project=project) if not enabled_dsn: raise IntegrationError("Project does not have DSN enabled") sentry_project_dsn = enabled_dsn.get_dsn(public=True) lambda_client = gen_aws_lambda_client(arn, aws_external_id) lambda_functions = filter( lambda x: x.get("Runtime") in SUPPORTED_RUNTIMES, lambda_client.list_functions()["Functions"], ) lambda_functions.sort(key=lambda x: x["FunctionName"].lower()) failures = [] for function in lambda_functions: name = function["FunctionName"] # check to see if the user wants to enable this function if not enabled_lambdas.get(name): continue # TODO: load existing layers and environment and append to them try: lambda_client.update_function_configuration( FunctionName=name, Layers=[options.get("aws-lambda.node-layer-arn")], Environment={ "Variables": { "NODE_OPTIONS": "-r @sentry/serverless/dist/auto", "SENTRY_DSN": sentry_project_dsn, "SENTRY_TRACES_SAMPLE_RATE": "1.0", } }, ) except Exception as e: failures.append(function) logger.info( "update_function_configuration.error", extra={ "organization_id": organization.id, "lambda_name": name, "arn": arn, "error": six.text_type(e), }, ) # if we have failures, show them to the user # otherwise, finish if failures: return self.render_react_view(request, "awsLambdaFailureDetails", {"lambdaFunctionFailures": failures}) else: return pipeline.finish_pipeline()
def test_update_organization_config(self): """Test that Vercel environment variables are created""" with self.tasks(): self.assert_setup_flow() uuid = self.mock_uuid4.hex secret_names = [ "sentry_org_%s" % uuid, "sentry_project_%s" % uuid, "next_public_sentry_dsn_%s" % uuid, "sentry_auth_token_%s" % uuid, ] responses.add( responses.GET, "https://api.vercel.com/v1/projects/%s" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "link": { "type": "github" }, "framework": "nextjs" }, ) for i, name in enumerate(secret_names): responses.add( responses.POST, "https://api.vercel.com/v2/now/secrets", json={"uid": "sec_%s" % i}, ) # mock get envs for all responses.add( responses.GET, "https://api.vercel.com/v5/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "envs": [], "pagination": { "count": 0 }, }, ) for i, name in enumerate(secret_names): responses.add( responses.POST, "https://api.vercel.com/v4/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "value": "sec_%s" % i, "target": "production", "key": name }, ) responses.add( responses.POST, "https://api.vercel.com/v4/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "value": "", "target": "production", "key": "VERCEL_GITHUB_COMMIT_SHA" }, ) org = self.organization project_id = self.project.id data = { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } enabled_dsn = ProjectKey.get_default(project=Project.objects.get( id=project_id)).get_dsn(public=True) sentry_auth_token = SentryAppInstallationForProvider.objects.get( organization=org.id, provider="vercel") sentry_auth_token = sentry_auth_token.sentry_app_installation.api_token.token integration = Integration.objects.get(provider=self.provider.key) installation = integration.get_installation(org.id) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == {} with patch("sentry.integrations.vercel.integration.uuid4", new=self.mock_uuid4): installation.update_organization_config(data) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } req_params = json.loads(responses.calls[7].request.body) assert req_params["name"] == "SENTRY_ORG_%s" % uuid assert req_params["value"] == org.slug req_params = json.loads(responses.calls[8].request.body) assert req_params["name"] == "SENTRY_PROJECT_%s" % uuid assert req_params["value"] == self.project.slug req_params = json.loads(responses.calls[9].request.body) assert req_params["name"] == "NEXT_PUBLIC_SENTRY_DSN_%s" % uuid assert req_params["value"] == enabled_dsn req_params = json.loads(responses.calls[10].request.body) assert req_params["name"] == "SENTRY_AUTH_TOKEN_%s" % uuid assert req_params["value"] == sentry_auth_token req_params = json.loads(responses.calls[12].request.body) assert req_params["key"] == "SENTRY_ORG" assert req_params["value"] == "sec_0" assert req_params["target"] == "production" req_params = json.loads(responses.calls[14].request.body) assert req_params["key"] == "SENTRY_PROJECT" assert req_params["value"] == "sec_1" assert req_params["target"] == "production" req_params = json.loads(responses.calls[16].request.body) assert req_params["key"] == "NEXT_PUBLIC_SENTRY_DSN" assert req_params["value"] == "sec_2" assert req_params["target"] == "production" req_params = json.loads(responses.calls[18].request.body) assert req_params["key"] == "SENTRY_AUTH_TOKEN" assert req_params["value"] == "sec_3" assert req_params["target"] == "production" req_params = json.loads(responses.calls[20].request.body) assert req_params["key"] == "VERCEL_GITHUB_COMMIT_SHA" assert req_params["value"] == "" assert req_params["target"] == "production"
def test_python_lambda_setup_layer_success(self, mock_gen_aws_client, mock_get_supported_functions): mock_client = Mock() mock_gen_aws_client.return_value = mock_client mock_client.update_function_configuration = MagicMock() mock_client.describe_account = MagicMock( return_value={"Account": { "Name": "my_name" }}) mock_get_supported_functions.return_value = [{ "FunctionName": "lambdaA", "Handler": "lambda_handler.test_handler", "Runtime": "python3.6", "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA", }] aws_external_id = "12-323" self.pipeline.state.step_index = 2 self.pipeline.state.data = { "region": region, "account_number": account_number, "aws_external_id": aws_external_id, "project_id": self.projectA.id, } sentry_project_dsn = ProjectKey.get_default( project=self.projectA).get_dsn(public=True) resp = self.client.post( self.setup_path, data={"lambdaA": True}, format="json", HTTP_ACCEPT="application/json", headers={ "Content-Type": "application/json", "Accept": "application/json" }, ) assert resp.status_code == 200 mock_client.update_function_configuration.assert_called_once_with( FunctionName="lambdaA", Layers=["arn:aws:lambda:us-east-2:1234:layer:my-python-layer:34"], Environment={ "Variables": { "SENTRY_INITIAL_HANDLER": "lambda_handler.test_handler", "SENTRY_DSN": sentry_project_dsn, "SENTRY_TRACES_SAMPLE_RATE": "1.0", } }, Handler= "sentry_sdk.integrations.init_serverless_sdk.sentry_lambda_handler", ) integration = Integration.objects.get(provider=self.provider.key) assert integration.name == "my_name us-east-2" assert integration.external_id == "599817902985-us-east-2" assert integration.metadata == { "region": region, "account_number": account_number, "aws_external_id": aws_external_id, } assert OrganizationIntegration.objects.filter( integration=integration, organization=self.organization)
def sentry_dsn(self): return ProjectKey.get_default(project=self.project).get_dsn( public=True)
def test_update_organization_config(self): """Test that Vercel environment variables are created""" with self.tasks(): self.assert_setup_flow() project_id = self.project.id secret_names = [ "sentry_org", "sentry_project_%s" % project_id, "next_public_sentry_dsn_%s" % project_id, ] for i, name in enumerate(secret_names): responses.add(responses.GET, "https://api.vercel.com/v3/now/secrets/%s" % name, status=404) responses.add( responses.POST, "https://api.vercel.com/v2/now/secrets", json={"uid": "sec_%s" % i}, ) # mock get envs for all responses.add( responses.GET, "https://api.vercel.com/v5/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={"envs": []}, ) for i, name in enumerate(secret_names): responses.add( responses.POST, "https://api.vercel.com/v4/projects/%s/env" % "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H", json={ "value": "sec_%s" % i, "target": "production", "key": name }, ) org = self.organization data = { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } enabled_dsn = ProjectKey.get_default(project=Project.objects.get( id=project_id)).get_dsn(public=True) integration = Integration.objects.get(provider=self.provider.key) installation = integration.get_installation(org.id) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == {} installation.update_organization_config(data) org_integration = OrganizationIntegration.objects.get( organization_id=org.id, integration_id=integration.id) assert org_integration.config == { "project_mappings": [[project_id, "Qme9NXBpguaRxcXssZ1NWHVaM98MAL6PHDXUs1jPrgiM8H"]] } req_params = json.loads(responses.calls[5].request.body) assert req_params["name"] == "SENTRY_ORG" assert req_params["value"] == org.slug req_params = json.loads(responses.calls[7].request.body) assert req_params["name"] == "SENTRY_PROJECT_%s" % project_id assert req_params["value"] == self.project.slug req_params = json.loads(responses.calls[9].request.body) assert req_params["name"] == "NEXT_PUBLIC_SENTRY_DSN_%s" % project_id assert req_params["value"] == enabled_dsn req_params = json.loads(responses.calls[11].request.body) assert req_params["key"] == "SENTRY_ORG" assert req_params["value"] == "sec_0" assert req_params["target"] == "production" req_params = json.loads(responses.calls[13].request.body) assert req_params["key"] == "SENTRY_PROJECT" assert req_params["value"] == "sec_1" assert req_params["target"] == "production" req_params = json.loads(responses.calls[15].request.body) assert req_params["key"] == "NEXT_PUBLIC_SENTRY_DSN" assert req_params["value"] == "sec_2" assert req_params["target"] == "production"
def test_lambda_setup_layer_success(self, mock_gen_aws_client, mock_get_supported_functions): mock_client = Mock() mock_gen_aws_client.return_value = mock_client mock_client.update_function_configuration = MagicMock() mock_client.describe_account = MagicMock( return_value={"Account": { "Name": "my_name" }}) mock_get_supported_functions.return_value = [ { "FunctionName": "lambdaA", "Runtime": "nodejs12.x", "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA", }, { "FunctionName": "lambdaB", "Runtime": "nodejs10.x", "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB", }, ] aws_external_id = "12-323" self.pipeline.state.step_index = 2 self.pipeline.state.data = { "arn": arn, "aws_external_id": aws_external_id, "project_id": self.projectA.id, } sentry_project_dsn = ProjectKey.get_default( project=self.projectA).get_dsn(public=True) resp = self.client.post( self.setup_path, {"lambdaB": True}, format="json", HTTP_ACCEPT="application/json", headers={ "Content-Type": "application/json", "Accept": "application/json" }, ) assert resp.status_code == 200 mock_client.update_function_configuration.assert_called_with( FunctionName="lambdaB", Layers=["arn:aws:lambda:us-east-2:1234:layer:my-layer:3"], Environment={ "Variables": { "NODE_OPTIONS": "-r @sentry/serverless/dist/auto", "SENTRY_DSN": sentry_project_dsn, "SENTRY_TRACES_SAMPLE_RATE": "1.0", } }, ) integration = Integration.objects.get(provider=self.provider.key) assert integration.name == "my_name us-east-2" assert integration.external_id == "599817902985-us-east-2" assert integration.metadata == { "arn": arn, "aws_external_id": aws_external_id } assert OrganizationIntegration.objects.filter( integration=integration, organization=self.organization)
def update_organization_config(self, data): # data = {"project_mappings": [[sentry_project_id, vercel_project_id]]} vercel_client = self.get_client() config = self.org_integration.config try: new_mappings = data["project_mappings"] except KeyError: raise ValidationError("Failed to update configuration.") old_mappings = config.get("project_mappings") or [] for mapping in new_mappings: # skip any mappings that already exist if mapping in old_mappings: continue [sentry_project_id, vercel_project_id] = mapping sentry_project = Project.objects.get(id=sentry_project_id) enabled_dsn = ProjectKey.get_default(project=sentry_project) if not enabled_dsn: raise ValidationError({ "project_mappings": ["You must have an enabled DSN to continue!"] }) sentry_project_dsn = enabled_dsn.get_dsn(public=True) vercel_project = vercel_client.get_project(vercel_project_id) source_code_provider = vercel_project.get("link", {}).get("type") if not source_code_provider: raise ValidationError({ "project_mappings": [ "You must connect your Vercel project to a Git repository to continue!" ] }) is_next_js = vercel_project.get("framework") == "nextjs" dsn_env_name = "NEXT_PUBLIC_SENTRY_DSN" if is_next_js else "SENTRY_DSN" sentry_auth_token = SentryAppInstallationToken.objects.get_token( sentry_project.organization.id, "vercel", ) env_var_map = { "SENTRY_ORG": { "type": "encrypted", "value": sentry_project.organization.slug }, "SENTRY_PROJECT": { "type": "encrypted", "value": sentry_project.slug }, dsn_env_name: { "type": "encrypted", "value": sentry_project_dsn }, "SENTRY_AUTH_TOKEN": { "type": "encrypted", "value": sentry_auth_token, }, "VERCEL_GIT_COMMIT_SHA": { "type": "system", "value": "VERCEL_GIT_COMMIT_SHA" }, } for env_var, details in env_var_map.items(): self.create_env_var(vercel_client, vercel_project_id, env_var, details["value"], details["type"]) config.update(data) self.org_integration.update(config=config)