Skip to content

Commit

Permalink
Skip Sonarqube Analysis Kind (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
PeyGis authored Oct 5, 2023
1 parent fa36d10 commit 6cf8e2d
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 72 deletions.
3 changes: 2 additions & 1 deletion integrations/sonarqube/.port/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ configurations:
description: The SonarQube API token
- name: sonarOrganizationId
type: string
required: true
required: false
description: The SonarQube organization ID
- name: appHost
type: string
Expand All @@ -26,3 +26,4 @@ configurations:
type: string
required: false
description: The SonarQube URL
default: https://sonarcloud.io
13 changes: 10 additions & 3 deletions integrations/sonarqube/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- towncrier release notes start -->

# Port_Ocean 0.1.2 (2023-09-27)
# Sonarqube 0.1.3 (2023-10-04)

### Improvements

- Skip analysis resync for onpremise Sonarqube (#3)


# Sonarqube 0.1.2 (2023-09-27)

### Improvements

- Bumped ocean to version 0.3.1 (#1)


# 0.1.1 (2023-09-13)
# Sonarqube 0.1.1 (2023-09-13)

### Improvements

- Bumped ocean to 0.3.0 (#1)

# 0.1.0 (2023-08-09)
# Sonarqube 0.1.0 (2023-08-09)

### Features

Expand Down
9 changes: 4 additions & 5 deletions integrations/sonarqube/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sonarqube

Sonarqube project and code quality integration for Port using Port-Ocean Framework
Sonarqube project and code quality integration for Port using Port-Ocean Framework.

## Development Requirements

Expand All @@ -17,7 +17,7 @@ For more information about the installation visit the [Port Ocean helm chart](ht
# integration.secrets.sonarApiToken: The Sonarqube API token
# integration.config.appHost: The Sonarqube app host
# integration.config.sonarUrl: The url of the Sonarqube instance or server. If not specified, the default will be https://sonarcloud.io
# integration.config.sonarOrganizationId: The Sonarqube organization ID
# integration.config.sonarOrganizationId: The Sonarqube organization ID. This config variable is required for clients using Sonarcloud. Be sure to provide it in the helm installation script below.

helm upgrade --install my-sonarqube-integration port-labs/port-ocean \
--set port.clientId="CLIENT_ID" \
Expand All @@ -27,11 +27,10 @@ helm upgrade --install my-sonarqube-integration port-labs/port-ocean \
--set integration.type="sonarqube" \
--set integration.eventListener.type="POLLING" \
--set integration.secrets.sonarApiToken="token" \
--set integration.config.appHost="https://example.com" \
--set integration.config.sonarUrl="https://sonarcloud.io" \
--set integration.config.sonarOrganizationId="my-organization" \
```
## Supported Kinds
As of the latest version `(0.1.3)` of the Sonarqube integration, the analysis object kind is skipped when an on-premise Sonarqube server is being used.

### Project
This kind represents a Sonarqube project. Retrieves data from [Sonarqube components](https://next.sonarqube.com/sonarqube/web_api/api/components) and [Sonarqube measures](https://next.sonarqube.com/sonarqube/web_api/api/measures) and [Sonarque branches](https://next.sonarqube.com/sonarqube/web_api/api/project_branches)

Expand Down
28 changes: 0 additions & 28 deletions integrations/sonarqube/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ class SonarQubeClient:
def __init__(
self, base_url: str, api_key: str, organization_id: str, app_host: str
):
"""
Initialize SonarQubeClient
:param base_url: SonarQube base URL
:param api_key: SonarQube API key
:param organization_id: SonarQube organization ID
:param app_host: Application host URL
:param http_client: httpx.AsyncClient instance
"""
self.base_url = base_url or "https://sonarcloud.io"
self.api_key = api_key
self.organization_id = organization_id
Expand Down Expand Up @@ -53,15 +44,6 @@ async def send_api_request(
query_params: Optional[dict[str, Any]] = None,
json_data: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
"""
Sends an API request to SonarQube
:param endpoint: API endpoint URL
:param method: HTTP method (default: 'GET')
:param query_params: Query parameters (default: None)
:param json_data: JSON data to send in request body (default: None)
:return: Response JSON data
"""
try:
response = await self.http_client.request(
method=method,
Expand All @@ -86,16 +68,6 @@ async def send_paginated_api_request(
query_params: Optional[dict[str, Any]] = None,
json_data: Optional[dict[str, Any]] = None,
) -> list[dict[str, Any]]:
"""
Sends an API request to SonarQube
:param endpoint: API endpoint URL
:param data_key: Resource key to fetch
:param method: HTTP method (default: 'GET')
:param query_params: Query parameters (default: None)
:param json_data: JSON data to send in request body (default: None)
:return: Response JSON data
"""
try:
all_resources = [] # List to hold all fetched resources

Expand Down
4 changes: 2 additions & 2 deletions integrations/sonarqube/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Please copy this file to config.yaml file in the integration folder and edit it to your needs.
initializePortResources: true
port:
clientId: "{{ from env PORT_CLIENT_ID }}" # Can be loaded via environment variable: PORT_CLIENT_ID
clientSecret: "{{ from env PORT_CLIENT_SECRET }}" # Can be loaded via environment variable: PORT_CLIENT_SECRET
clientId: "{{ from env PORT_CLIENT_ID }}" # Can be loaded via environment variable: PORT_CLIENT_ID
clientSecret: "{{ from env PORT_CLIENT_SECRET }}" # Can be loaded via environment variable: PORT_CLIENT_SECRET
# The event listener to use for the integration service.
eventListener:
type: POLLING
Expand Down
73 changes: 41 additions & 32 deletions integrations/sonarqube/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,37 @@ class ObjectKind:
ANALYSIS = "analysis"


@ocean.on_resync(ObjectKind.PROJECTS)
async def on_project_resync(kind: str) -> list[dict[str, Any]]:
logger.info(f"Listing Sonarqube resource: {kind}")
sonar_client = SonarQubeClient(
def init_sonar_client() -> SonarQubeClient:
return SonarQubeClient(
ocean.integration_config.get("sonar_url", "https://sonarcloud.io"),
ocean.integration_config.get("sonar_api_token", ""),
ocean.integration_config["sonar_api_token"],
ocean.integration_config.get("sonar_organization_id", ""),
ocean.integration_config.get("app_host", ""),
)


def is_onpremise_deployment() -> bool:
return ocean.integration_config.get("sonar_url") != "https://sonarcloud.io"


def organization_key_missing_for_onpremise() -> bool:
return (
not ocean.integration_config.get("sonar_organization_id")
and is_onpremise_deployment()
)


@ocean.on_resync(ObjectKind.PROJECTS)
async def on_project_resync(kind: str) -> list[dict[str, Any]]:
logger.info(f"Listing Sonarqube resource: {kind}")
sonar_client = init_sonar_client()
return await sonar_client.get_all_projects()


@ocean.on_resync(ObjectKind.ISSUES)
async def on_issues_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
logger.info(f"Listing Sonarqube resource: {kind}")
sonar_client = SonarQubeClient(
ocean.integration_config.get("sonar_url", "https://sonarcloud.io"),
ocean.integration_config.get("sonar_api_token", ""),
ocean.integration_config.get("sonar_organization_id", ""),
ocean.integration_config.get("app_host", ""),
)
sonar_client = init_sonar_client()
async for issues_list in sonar_client.get_all_issues():
for issue in issues_list:
yield [issue]
Expand All @@ -40,12 +50,13 @@ async def on_issues_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
@ocean.on_resync(ObjectKind.ANALYSIS)
async def on_analysis_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
logger.info(f"Listing Sonarqube resource: {kind}")
sonar_client = SonarQubeClient(
ocean.integration_config.get("sonar_url", "https://sonarcloud.io"),
ocean.integration_config.get("sonar_api_token", ""),
ocean.integration_config.get("sonar_organization_id", ""),
ocean.integration_config.get("app_host", ""),
)
if is_onpremise_deployment():
logger.debug(
"Skipping resync because the integration does not support on-premise Sonarqube deployment"
)
return

sonar_client = init_sonar_client()

async for analyses_list in sonar_client.get_all_analyses():
for analysis_data in analyses_list:
Expand All @@ -57,35 +68,33 @@ async def handle_sonarqube_webhook(webhook_data: dict[str, Any]) -> None:
logger.info(
f"Processing Sonarqube webhook for event type: {webhook_data.get('project', {}).get('key')}"
)
sonar_client = SonarQubeClient(
ocean.integration_config.get("sonar_url", "https://sonarcloud.io"),
ocean.integration_config.get("sonar_api_token", ""),
ocean.integration_config.get("sonar_organization_id", ""),
ocean.integration_config.get("app_host", ""),
)
sonar_client = init_sonar_client()

project = await sonar_client.get_single_component(
webhook_data.get("project", {})
) ## making sure we're getting the right project details
project_data = await sonar_client.get_single_project(project)
analysis_data = await sonar_client.get_analysis_for_task(webhook_data=webhook_data)
issues_data = await sonar_client.get_issues_by_component(project)

await ocean.register_raw(ObjectKind.PROJECTS, [project_data])
await ocean.register_raw(ObjectKind.ANALYSIS, [analysis_data])
await ocean.register_raw(ObjectKind.ISSUES, issues_data)

if is_onpremise_deployment():
logger.debug(
"Skipping real-time update of analysis because the integration does not support on-premise Sonarqube deployment"
)
return

analysis_data = await sonar_client.get_analysis_for_task(webhook_data=webhook_data)
await ocean.register_raw(ObjectKind.ANALYSIS, [analysis_data])

logger.info("Webhook event processed")


@ocean.on_start()
async def on_start() -> None:
if organization_key_missing_for_onpremise():
logger.warning("Organization key is missing for an on-premise Sonarqube setup")
## We are making the real-time subscription of Sonar webhook events optional. That said, we only subscribe to webhook events when the user supplies the app_host config variable
if ocean.integration_config.get("app_host"):
sonar_client = SonarQubeClient(
ocean.integration_config.get("sonar_url", "https://sonarcloud.io"),
ocean.integration_config.get("sonar_api_token", ""),
ocean.integration_config.get("sonar_organization_id", ""),
ocean.integration_config.get("app_host", ""),
)
sonar_client = init_sonar_client()
await sonar_client.get_or_create_webhook_url()
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.2"
version = "0.1.3"
description = "SonarQube projects and code quality analysis integration"
authors = ["Port Team <[email protected]>"]

Expand Down

0 comments on commit 6cf8e2d

Please sign in to comment.