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

[Integration][SonarQube] Added support for querying projects via the GA API #1146

Merged
merged 72 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
7ddf910
Added logs to sonarqube integration
lordsarcastic Nov 15, 2024
a38a15b
Bumped integration version
lordsarcastic Nov 15, 2024
f82b31f
Ran formatting
lordsarcastic Nov 15, 2024
02b5051
Make use of internal API optional
lordsarcastic Nov 18, 2024
81a4be9
Updated changelog
lordsarcastic Nov 18, 2024
1e635db
Made fixes based on comment
lordsarcastic Nov 19, 2024
cabc3d9
Made fixes based on comment
lordsarcastic Nov 19, 2024
3333924
Chore: Added logs
lordsarcastic Nov 19, 2024
7d9adc2
Made default vlaue for enpoint in get_components
lordsarcastic Nov 19, 2024
5073d7f
Added pagination to projects
mk-armah Nov 20, 2024
8f0babc
Merge branch 'main' into add-sonarqube-logs
mk-armah Nov 20, 2024
a075851
added more logs
mk-armah Nov 20, 2024
3152725
Merge branch 'add-sonarqube-logs' of https://github.com/port-labs/oce…
mk-armah Nov 20, 2024
8793c85
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Nov 20, 2024
c4e167b
Feature: replaced all internal calls with GA calls
lordsarcastic Nov 20, 2024
230c288
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Nov 20, 2024
91fe625
Chore: Bumped integration version
lordsarcastic Nov 20, 2024
6145e0f
Implemented all tests
lordsarcastic Nov 22, 2024
86ab531
Chore: Fixed comments
lordsarcastic Nov 22, 2024
64d0ec4
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Nov 22, 2024
3722134
Bumped integration version
lordsarcastic Nov 22, 2024
84532d9
Merge branch 'main' into add-sonarqube-logs
PeyGis Nov 25, 2024
fca1bb4
Merge branch 'main' into add-sonarqube-logs
PeyGis Nov 25, 2024
e046aea
Chore: Implemented different kind for ga projects
lordsarcastic Nov 27, 2024
2e9d816
Merge branch 'add-sonarqube-logs' of github.com:port-labs/ocean into …
lordsarcastic Nov 27, 2024
d71570f
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Nov 27, 2024
942a423
Chore: Increased test count
lordsarcastic Nov 29, 2024
2829f18
Removed project kind mapping from defaults
lordsarcastic Nov 29, 2024
f63ff6b
Merge branch 'main' into add-sonarqube-logs
PeyGis Dec 2, 2024
1df9dd1
Update integrations/sonarqube/.port/resources/port-app-config.yaml
PeyGis Dec 2, 2024
5a05a96
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 3, 2024
07e5d4d
Mocks event context for required tests
shariff-6 Dec 3, 2024
a1cc4d8
Merge branch 'main' of https://github.com/port-labs/ocean into add-so…
shariff-6 Dec 3, 2024
d874bc9
Chore: Try extra fix for failing tests
lordsarcastic Dec 3, 2024
2fe448e
Merge branch 'add-sonarqube-logs' of github.com:port-labs/ocean into …
lordsarcastic Dec 3, 2024
901d001
Chore: Fixed all tests
lordsarcastic Dec 3, 2024
02ee6b8
Chore: Remove failing full sync test
lordsarcastic Dec 4, 2024
168c716
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 4, 2024
361e38b
Chore: bumped version
lordsarcastic Dec 4, 2024
5bbbc22
Chore: Bumped ocean dependency version
lordsarcastic Dec 4, 2024
0e239a8
Chore: uPdate poetry lock file
lordsarcastic Dec 4, 2024
d0926a4
Chore: Fix organisaztion id absense in off premise
lordsarcastic Dec 4, 2024
24d6e21
chore: fixed failing tests
lordsarcastic Dec 4, 2024
00fda07
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 4, 2024
b3cf433
Chore: bumped integration version
lordsarcastic Dec 4, 2024
39d973d
Merge branch 'main' into add-sonarqube-logs
PeyGis Dec 9, 2024
ced640b
Fix: fixed selectors bug
lordsarcastic Dec 9, 2024
a653079
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 9, 2024
e458efb
optimize fetching of projects in issues and analysis
PeyGis Dec 10, 2024
3794ae5
lint code
PeyGis Dec 10, 2024
c441707
Merge branch 'main' into add-sonarqube-logs
PeyGis Dec 10, 2024
f3ac6ba
Merge branch 'add-sonarqube-logs' of https://github.com/port-labs/oce…
PeyGis Dec 10, 2024
d407786
Merge branch 'main' into add-sonarqube-logs
PeyGis Dec 10, 2024
8ea3896
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 10, 2024
c7334e9
Bumped integration version
lordsarcastic Dec 10, 2024
7fa0861
Chore: Made fixes to integration
lordsarcastic Dec 11, 2024
02c0dce
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 11, 2024
17a0848
Merge branch 'main' into add-sonarqube-logs
Tankilevitch Dec 12, 2024
7a52d72
Chore: Changed ga_projects to projects_ga
lordsarcastic Dec 16, 2024
ec55488
Merge branch 'add-sonarqube-logs' of github.com:port-labs/ocean into …
lordsarcastic Dec 16, 2024
ef2676a
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 16, 2024
e66965f
Restored projection for component projects kind
lordsarcastic Dec 16, 2024
a37f756
Merge remote-tracking branch 'origin/main' into add-sonarqube-logs
lordsarcastic Dec 18, 2024
e673d75
Chore: Added layer of to ensure consistency
lordsarcastic Dec 18, 2024
b8b5014
Chore: Fixed comments
lordsarcastic Dec 18, 2024
84ff365
Chore: Added alias for qualifier
lordsarcastic Dec 19, 2024
3456ca3
Fixed pagination bug
lordsarcastic Dec 20, 2024
0e1d1e4
Added changelog
lordsarcastic Dec 20, 2024
d348db5
Merge branch 'main' into add-sonarqube-logs
Tankilevitch Dec 22, 2024
f7a6b4e
Merge branch 'main' into add-sonarqube-logs
lordsarcastic Dec 23, 2024
cdcd1b6
Fixed changelog
lordsarcastic Dec 23, 2024
fe758d4
Bump integration version
lordsarcastic Dec 23, 2024
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
9 changes: 9 additions & 0 deletions integrations/sonarqube/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- towncrier release notes start -->

## 0.1.111 (2024-11-18)


### Improvements

- Increased logs presence in integration (0.1.111)
- Replaced calls to internal API for projects to GA version, making internal API use optional (0.1.111)


## 0.1.110 (2024-11-12)


Expand Down
63 changes: 47 additions & 16 deletions integrations/sonarqube/client.py
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def turn_sequence_to_chunks(


class Endpoints:
PROJECTS = "components/search_projects"
PROJECTS_INTERNAL = "components/search_projects"
PROJECTS = "projects/search"
WEBHOOKS = "webhooks"
MEASURES = "measures/component"
BRANCHES = "project_branches/list"
Expand Down Expand Up @@ -95,8 +96,11 @@ async def send_api_request(
query_params: Optional[dict[str, Any]] = None,
json_data: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
_headers = {**self.http_client.headers, "Authorization": "Bearer <masked>"}
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
logger.debug(
f"Sending API request to {method} {endpoint} with query params: {query_params}"
f"Sending {method} request to {endpoint} with headers:"
f" {_headers} and json data: {json_data}"
f" with query params: {query_params}"
)
try:
response = await self.http_client.request(
Expand Down Expand Up @@ -125,10 +129,13 @@ async def send_paginated_api_request(
query_params = query_params or {}
query_params["ps"] = PAGE_SIZE
all_resources = [] # List to hold all fetched resources
_headers = {**self.http_client.headers, "Authorization": "Bearer <masked>"}

try:
logger.debug(
f"Sending API request to {method} {endpoint} with query params: {query_params}"
f"Sending {method} request to {endpoint} with headers:"
f" {_headers} and json data: {json_data}"
f" with query params: {query_params}"
)

while True:
Expand All @@ -140,7 +147,12 @@ async def send_paginated_api_request(
)
response.raise_for_status()
response_json = response.json()
logger.debug(
f"Received response with status code: {response.status_code}"
f" and response: {response_json}"
)
resource = response_json.get(data_key, [])
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
logger.debug(f"Received {len(resource)} resources")
all_resources.extend(resource)

# Check for paging information and decide whether to fetch more pages
Expand All @@ -150,6 +162,7 @@ async def send_paginated_api_request(

query_params["p"] = paging_info["pageIndex"] + 1

logger.debug(f"Total resources fetched: {len(all_resources)}")
return all_resources

except httpx.HTTPStatusError as e:
Expand All @@ -175,7 +188,9 @@ async def send_paginated_api_request(
raise

async def get_components(
self, api_query_params: Optional[dict[str, Any]] = None
self,
endpoint: str = Endpoints.PROJECTS,
api_query_params: Optional[dict[str, Any]] = None,
) -> list[dict[str, Any]]:
"""
Retrieve all components from SonarQube organization.
Expand Down Expand Up @@ -206,7 +221,7 @@ async def get_components(

try:
response = await self.send_paginated_api_request(
endpoint=Endpoints.PROJECTS,
endpoint=endpoint,
data_key="components",
query_params=query_params,
)
Expand Down Expand Up @@ -288,13 +303,29 @@ async def get_all_projects(self) -> AsyncGenerator[list[dict[str, Any]], None]:
:return (list[Any]): A list containing projects data for your organization.
"""
logger.info(f"Fetching all projects in organization: {self.organization_id}")
self.metrics = cast(
SonarQubeProjectResourceConfig, event.resource_config
).selector.metrics
components = await self.get_components()
for component in components:
project_data = await self.get_single_project(project=component)
yield [project_data]
selector = cast(SonarQubeProjectResourceConfig, event.resource_config).selector
self.metrics = selector.metrics

all_projects = {}
for project in await self.send_paginated_api_request(
endpoint=Endpoints.PROJECTS, data_key="components"
):
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
project_key = project.get("key")
all_projects[project_key] = project

if selector.use_internal_api:
# usint the internal API to fetch more data
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
components = await self.get_components(Endpoints.PROJECTS_INTERNAL)
for component in components:
all_projects[component["key"]] = {
**all_projects.get(component["key"], {}),
**component,
}

yield [
await self.get_single_project(component)
for component in all_projects.values()
]
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved

lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
async def get_all_issues(self) -> AsyncGenerator[list[dict[str, Any]], None]:
"""
Expand All @@ -313,7 +344,7 @@ async def get_all_issues(self) -> AsyncGenerator[list[dict[str, Any]], None]:
)

components = await self.get_components(
api_query_params=project_api_query_params
endpoint=Endpoints.PROJECTS, api_query_params=project_api_query_params
)
for component in components:
response = await self.get_issues_by_component(
Expand Down Expand Up @@ -366,7 +397,7 @@ async def get_all_sonarcloud_analyses(

:return (list[Any]): A list containing analysis data for all components.
"""
components = await self.get_components()
components = await self.get_components(Endpoints.PROJECTS)

for component in components:
response = await self.get_analysis_by_project(component=component)
Expand Down Expand Up @@ -492,7 +523,7 @@ async def get_measures_for_all_pull_requests(
async def get_all_sonarqube_analyses(
self,
) -> AsyncGenerator[list[dict[str, Any]], None]:
components = await self.get_components()
components = await self.get_components(Endpoints.PROJECTS)
for component in components:
analysis_data = await self.get_measures_for_all_pull_requests(
project_key=component["key"]
Expand Down Expand Up @@ -575,7 +606,7 @@ async def get_or_create_webhook_url(self) -> None:
logger.info(f"Subscribing to webhooks in organization: {self.organization_id}")
webhook_endpoint = Endpoints.WEBHOOKS
invoke_url = f"{self.app_host}/integration/webhook"
projects = await self.get_components()
projects = await self.get_components(Endpoints.PROJECTS)

# Iterate over projects and add webhook
webhooks_to_create = []
Expand Down
5 changes: 5 additions & 0 deletions integrations/sonarqube/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ def default_metrics() -> list[str]:
metrics: list[str] = Field(
description="List of metric keys", default=default_metrics()
)
use_internal_api: bool = Field(
alias="useInternalApi",
description="Use internal API to fetch more data",
default=False,
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
)

kind: Literal["projects"]
selector: SonarQubeProjectSelector
Expand Down
32 changes: 32 additions & 0 deletions integrations/sonarqube/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,66 @@ def init_sonar_client() -> SonarQubeClient:
async def on_project_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
logger.info(f"Listing Sonarqube resource: {kind}")
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved

lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
fetched_projects = False

async for project_list in sonar_client.get_all_projects():
logger.info(f"Received project batch of size: {len(project_list)}")
yield project_list
fetched_projects = True

if not fetched_projects:
logger.error("No projects found in Sonarqube")
raise RuntimeError(
"No projects found in Sonarqube, failing the resync to avoid data loss"
)


@ocean.on_resync(ObjectKind.ISSUES)
async def on_issues_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
fetched_issues = False
async for issues_list in sonar_client.get_all_issues():
logger.info(f"Received issues batch of size: {len(issues_list)}")
yield issues_list
fetched_issues = True

if not fetched_issues:
logger.error("No issues found in Sonarqube")
raise RuntimeError(
"No issues found in Sonarqube, failing the resync to avoid data loss"
)


@ocean.on_resync(ObjectKind.ANALYSIS)
@ocean.on_resync(ObjectKind.SASS_ANALYSIS)
async def on_saas_analysis_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
fetched_analyses = False
if not ocean.integration_config["sonar_is_on_premise"]:
logger.info("Sonar is not on-premise, processing SonarCloud on saas analysis")
lordsarcastic marked this conversation as resolved.
Show resolved Hide resolved
async for analyses_list in sonar_client.get_all_sonarcloud_analyses():
logger.info(f"Received analysis batch of size: {len(analyses_list)}")
yield analyses_list
fetched_analyses = True

if not fetched_analyses:
logger.error("No analysis found in Sonarqube")
raise RuntimeError(
"No analysis found in Sonarqube, failing the resync to avoid data loss"
)


@ocean.on_resync(ObjectKind.ONPREM_ANALYSIS)
async def on_onprem_analysis_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
if ocean.integration_config["sonar_is_on_premise"]:
logger.info("Sonar is on-premise, processing on-premise SonarQube analysis")
async for analyses_list in sonar_client.get_all_sonarqube_analyses():
logger.info(f"Received analysis batch of size: {len(analyses_list)}")
yield analyses_list


@ocean.on_resync(ObjectKind.PORTFOLIOS)
async def on_portfolio_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
async for portfolio_list in sonar_client.get_all_portfolios():
logger.info(f"Received portfolio batch of size: {len(portfolio_list)}")
yield portfolio_list


Expand Down
2 changes: 1 addition & 1 deletion integrations/sonarqube/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "sonarqube"
version = "0.1.110"
version = "0.1.111"
description = "SonarQube projects and code quality analysis integration"
authors = ["Port Team <[email protected]>"]

Expand Down