Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nit(scm): group functions by class they come from in main integration class #76240

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/sentry/integrations/bitbucket/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ def integration_name(self) -> str:
def get_client(self):
return BitbucketApiClient(integration=self.model)

@property
def username(self):
return self.model.name
# IntegrationInstallation methods

def error_message_from_json(self, data):
return data.get("error", {}).get("message", "unknown error")

# RepositoryIntegration methods

def get_repositories(self, query=None):
username = self.model.metadata.get("uuid", self.username)
if not query:
Expand Down Expand Up @@ -156,6 +156,12 @@ def extract_source_path_from_source_url(self, repo: Repository, url: str) -> str
_, _, source_path = url.partition("/")
return source_path

# Bitbucket only methods

@property
def username(self):
return self.model.name


class BitbucketIntegrationProvider(IntegrationProvider):
key = "bitbucket"
Expand Down
12 changes: 9 additions & 3 deletions src/sentry/integrations/bitbucket_server/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,13 @@ def get_client(self):
identity=self.default_identity,
)

@property
def username(self):
return self.model.name
# IntegrationInstallation methods

def error_message_from_json(self, data):
return data.get("error", {}).get("message", "unknown error")

# RepositoryIntegration methods

def get_repositories(self, query=None):
if not query:
resp = self.get_client().get_repos()
Expand Down Expand Up @@ -310,6 +310,12 @@ def extract_branch_from_source_url(self, repo: Repository, url: str) -> str:
def extract_source_path_from_source_url(self, repo: Repository, url: str) -> str:
raise IntegrationFeatureNotImplementedError

# Bitbucket Server only methods

@property
def username(self):
return self.model.name


class BitbucketServerIntegrationProvider(IntegrationProvider):
key = "bitbucket_server"
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/integrations/github/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def has_repo_access(self, repo: RpcRepository) -> bool:
return False
return True

# for derive code mappings (TODO: define in an ABC)
# for derive code mappings - TODO(cathy): define in an ABC
def get_trees_for_org(self, cache_seconds: int = 3600 * 24) -> dict[str, RepoTree]:
trees: dict[str, RepoTree] = {}
domain_name = self.model.metadata["domain_name"]
Expand All @@ -291,7 +291,7 @@ def get_trees_for_org(self, cache_seconds: int = 3600 * 24) -> dict[str, RepoTre

return trees

# TODO: define in issue ABC
# TODO(cathy): define in issue ABC
def search_issues(self, query: str) -> Mapping[str, Sequence[Mapping[str, Any]]]:
return self.get_client().search_issues(query)

Expand Down
22 changes: 13 additions & 9 deletions src/sentry/integrations/github_enterprise/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ def get_client(self):
org_integration_id=self.org_integration.id,
)

# IntegrationInstallation methods

def message_from_error(self, exc):
if isinstance(exc, ApiError):
message = API_ERRORS.get(exc.code)
if message is None:
message = exc.json.get("message", "unknown error") if exc.json else "unknown error"
return f"Error Communicating with GitHub Enterprise (HTTP {exc.code}): {message}"
else:
return ERR_INTERNAL

# RepositoryIntegration methods

def get_repositories(self, query=None):
if not query:
return [
Expand Down Expand Up @@ -197,15 +210,6 @@ def has_repo_access(self, repo: RpcRepository) -> bool:
# TODO: define this, used to migrate repositories
return False

def message_from_error(self, exc):
if isinstance(exc, ApiError):
message = API_ERRORS.get(exc.code)
if message is None:
message = exc.json.get("message", "unknown error") if exc.json else "unknown error"
return f"Error Communicating with GitHub Enterprise (HTTP {exc.code}): {message}"
else:
return ERR_INTERNAL


class InstallationForm(forms.Form):
url = forms.CharField(
Expand Down
38 changes: 22 additions & 16 deletions src/sentry/integrations/gitlab/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ def __init__(self, *args, **kwargs):
def integration_name(self) -> str:
return "gitlab"

def get_group_id(self):
return self.model.metadata["group_id"]

def get_client(self):
if self.default_identity is None:
try:
Expand All @@ -115,6 +112,22 @@ def get_client(self):

return GitLabApiClient(self)

# IntegrationInstallation methods
def error_message_from_json(self, data):
"""
Extract error messages from gitlab API errors.
Generic errors come in the `error` key while validation errors
are generally in `message`.

See https://docs.gitlab.com/ee/api/#data-validation-and-error-reporting
"""
if "message" in data:
return data["message"]
if "error" in data:
return data["error"]

# RepositoryIntegration methods

def has_repo_access(self, repo: RpcRepository) -> bool:
# TODO: define this, used to migrate repositories
return False
Expand Down Expand Up @@ -148,28 +161,21 @@ def extract_source_path_from_source_url(self, repo: Repository, url: str) -> str
_, _, source_path = url.partition("/")
return source_path

# Gitlab only functions

def get_group_id(self):
return self.model.metadata["group_id"]

def search_projects(self, query):
client = self.get_client()
group_id = self.get_group_id()
return client.search_projects(group_id, query)

# TODO(cathy): define in issue ABC
def search_issues(self, project_id, query, iids):
client = self.get_client()
return client.search_project_issues(project_id, query, iids)

def error_message_from_json(self, data):
"""
Extract error messages from gitlab API errors.
Generic errors come in the `error` key while validation errors
are generally in `message`.

See https://docs.gitlab.com/ee/api/#data-validation-and-error-reporting
"""
if "message" in data:
return data["message"]
if "error" in data:
return data["error"]


class InstallationForm(forms.Form):
url = forms.CharField(
Expand Down
93 changes: 48 additions & 45 deletions src/sentry/integrations/vsts/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,47 +131,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
def integration_name(self) -> str:
return "vsts"

def all_repos_migrated(self) -> bool:
return not self.get_unmigratable_repositories()

def get_repositories(self, query: str | None = None) -> Sequence[Mapping[str, str]]:
try:
repos = self.get_client().get_repos()
except (ApiError, IdentityNotValid) as e:
raise IntegrationError(self.message_from_error(e))
data = []
for repo in repos["value"]:
data.append(
{
"name": "{}/{}".format(repo["project"]["name"], repo["name"]),
"identifier": repo["id"],
}
)
return data

def get_unmigratable_repositories(self) -> list[RpcRepository]:
repos = repository_service.get_repositories(
organization_id=self.organization_id, providers=["visualstudio"]
)
identifiers_to_exclude = {r["identifier"] for r in self.get_repositories()}
return [repo for repo in repos if repo.external_id not in identifiers_to_exclude]

def has_repo_access(self, repo: RpcRepository) -> bool:
client = self.get_client()
try:
# since we don't actually use webhooks for vsts commits,
# just verify repo access
client.get_repo(repo.config["name"], project=repo.config["project"])
except (ApiError, IdentityNotValid):
return False
return True

def get_client(self) -> VstsApiClient:
base_url = self.instance
if SiloMode.get_current_mode() != SiloMode.REGION:
if self.default_identity is None:
self.default_identity = self.get_default_identity()
self.check_domain_name(self.default_identity)
self._check_domain_name(self.default_identity)

if self.org_integration is None:
raise Exception("self.org_integration is not defined")
Expand All @@ -184,15 +149,7 @@ def get_client(self) -> VstsApiClient:
identity_id=self.org_integration.default_auth_id,
)

def check_domain_name(self, default_identity: RpcIdentity) -> None:
if re.match("^https://.+/$", self.model.metadata["domain_name"]):
return

base_url = VstsIntegrationProvider.get_base_url(
default_identity.data["access_token"], self.model.external_id
)
self.model.metadata["domain_name"] = base_url
self.model.save()
# IntegrationInstallation methods

def get_organization_config(self) -> Sequence[Mapping[str, Any]]:
client = self.get_client()
Expand Down Expand Up @@ -333,6 +290,40 @@ def get_config_data(self) -> Mapping[str, Any]:
config["sync_status_forward"] = sync_status_forward
return config

# RepositoryIntegration methods

def get_repositories(self, query: str | None = None) -> Sequence[Mapping[str, str]]:
try:
repos = self.get_client().get_repos()
except (ApiError, IdentityNotValid) as e:
raise IntegrationError(self.message_from_error(e))
data = []
for repo in repos["value"]:
data.append(
{
"name": "{}/{}".format(repo["project"]["name"], repo["name"]),
"identifier": repo["id"],
}
)
return data

def get_unmigratable_repositories(self) -> list[RpcRepository]:
repos = repository_service.get_repositories(
organization_id=self.organization_id, providers=["visualstudio"]
)
identifiers_to_exclude = {r["identifier"] for r in self.get_repositories()}
return [repo for repo in repos if repo.external_id not in identifiers_to_exclude]

def has_repo_access(self, repo: RpcRepository) -> bool:
client = self.get_client()
try:
# since we don't actually use webhooks for vsts commits,
# just verify repo access
client.get_repo(repo.config["name"], project=repo.config["project"])
except (ApiError, IdentityNotValid):
return False
return True

def source_url_matches(self, url: str) -> bool:
return url.startswith(self.model.metadata["domain_name"])

Expand Down Expand Up @@ -362,6 +353,18 @@ def extract_source_path_from_source_url(self, repo: Repository, url: str) -> str
return qs["path"][0].lstrip("/")
return ""

# Azure DevOps only methods

def _check_domain_name(self, default_identity: RpcIdentity) -> None:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is only used inside this class so i added an underscore

if re.match("^https://.+/$", self.model.metadata["domain_name"]):
return

base_url = VstsIntegrationProvider.get_base_url(
default_identity.data["access_token"], self.model.external_id
)
self.model.metadata["domain_name"] = base_url
self.model.save()

@property
def instance(self) -> str:
return self.model.metadata["domain_name"]
Expand Down
Loading