SonarQube
通过我们的 SonarQube 集成(由Ocean 提供) ,您可以根据您的映射和定义,从 SonarQube 账户将 "项目"、"问题 "和 "分析 "导入 Port。
常见被用于情况
- 映射 SonarQube 组织环境中的 "项目"、"问题 "和 "分析"。
- 实时观察对象更改(创建/更新/删除),并自动将更改应用到 Port 中的实体。
- 使用自助操作创建/删除 SonarQube 对象。
先决条件
To install the integration, you need a Kubernetes cluster that the integration's container chart will be deployed to.
Please make sure that you have kubectl
and helm
installed on your machine, and that your kubectl
CLI is connected to the Kubernetes cluster where you plan to install the integration.
安装
从以下安装方法中选择一种:
- Real Time & Always On
- Scheduled
使用该安装选项意味着集成将能使用 webhook 实时更新 Port。
本表总结了安装时可用的参数,请在下面的脚本中按自己的需要进行设置,然后复制并在终端运行:
Parameter | Description | Example | Required |
---|---|---|---|
port.clientId | Your port client id (How to get the credentials) | ✅ | |
port.clientSecret | Your port client secret (How to get the credentials) | ✅ | |
integration.secrets.sonarApiToken | The SonarQube API token | ✅ | |
integration.config.sonarOrganizationId | The SonarQube organization Key (Not required when using on-prem sonarqube instance) | myOrganization | ✅ |
integration.config.appHost | A URL bounded to the integration container that can be accessed by sonarqube. When used the integration will create webhooks on top of sonarqube to listen to any live changes in the data | https://my-ocean-integration.com | ❌ |
integration.config.sonarUrl | Required if using On-Prem, Your SonarQube instance URL | https://my-sonar-cloud-instance.com | ❌ |
Advanced configuration
Parameter | Description |
---|---|
integration.eventListener.type | The event listener type. Read more about event listeners |
integration.type | The integration to be installed |
scheduledResyncInterval | The number of minutes between each resync. When not set the integration will resync for each event listener resync event. Read more about scheduledResyncInterval |
initializePortResources | Default true, When set to true the integration will create default blueprints and the port App config Mapping. Read more about initializePortResources |
- Helm
- ArgoCD
To install the integration using Helm, run the following command:
helm repo add --force-update port-labs https://port-labs.github.io/helm-charts
helm upgrade --install my-sonarqube-integration port-labs/port-ocean \
--set port.clientId="PORT_CLIENT_ID" \
--set port.clientSecret="PORT_CLIENT_SECRET" \
--set initializePortResources=true \
--set scheduledResyncInterval=120 \
--set integration.identifier="my-sonarqube-integration" \
--set integration.type="sonarqube" \
--set integration.eventListener.type="POLLING" \
--set integration.secrets.sonarApiToken="MY_API_TOKEN" \
--set integration.config.sonarOrganizationId="MY_ORG_KEY"
To install the integration using ArgoCD, follow these steps:
- 在你的 git 仓库的
argocd/my-ocean-sonarqube-integration
中创建一个values.yaml
文件,内容如下:
MY_ORG_KEY
和 MY_API_TOKEN
的占位符。initializePortResources: true
scheduledResyncInterval: 120
integration:
identifier: my-ocean-sonarqube-integration
type: sonarqube
eventListener:
type: POLLING
config:
sonarOrganizationId: MY_ORG_KEY
secrets:
sonarApiToken: MY_API_TOKEN
2.创建以下 "my-ocean-sonarqube-integration.yaml "配置清单,安装 "my-ocean-sonarqube-integration "ArgoCD应用程序:
YOUR_PORT_CLIENT_ID``YOUR_PORT_CLIENT_SECRET
和 YOUR_GIT_REPO_URL
的占位符。多种来源的 ArgoCD 文档可在here 上找到。
ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-ocean-sonarqube-integration
namespace: argocd
spec:
destination:
namespace: my-ocean-sonarqube-integration
server: https://kubernetes.default.svc
project: default
sources:
- repoURL: 'https://port-labs.github.io/helm-charts/'
chart: port-ocean
targetRevision: 0.1.14
helm:
valueFiles:
- $values/argocd/my-ocean-sonarqube-integration/values.yaml
parameters:
- name: port.clientId
value: YOUR_PORT_CLIENT_ID
- name: port.clientSecret
value: YOUR_PORT_CLIENT_SECRET
- repoURL: YOUR_GIT_REPO_URL
targetRevision: main
ref: values
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
3.使用 kubectl
配置应用程序清单:
kubectl apply -f my-ocean-sonarqube-integration.yaml
- GitHub
- Jenkins
- Azure Devops
This workflow will run the SonarQube integration once and then exit, this is useful for scheduled ingestion of data.
确保配置以下Github Secrets :
Parameter | Description | Required |
---|---|---|
OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN | The SonarQube API token | ✅ |
OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID | The SonarQube organization Key (Not required when using on-prem sonarqube instance) | ✅ |
OCEAN__INTEGRATION__CONFIG__SONAR_URL | Required if using On-Prem, Your SonarQube instance URL | ❌ |
OCEAN__PORT__CLIENT_ID | Your port client id (How to get the credentials) | ✅ |
OCEAN__PORT__CLIENT_SECRET | Your port client secret (How to get the credentials) | ✅ |
OCEAN__INITIALIZE_PORT_RESOURCES | Default true, When set to true the integration will create default blueprints and the port App config Mapping. Read more about initializePortResources | ❌ |
OCEAN__INTEGRATION__IDENTIFIER | The identifier of the integration that will be installed | ✅ |
下面是 sonarqube-integration.yml
工作流程文件的示例:
name: SonarQube Exporter Workflow
# This workflow responsible for running SonarQube exporter.
on:
workflow_dispatch:
jobs:
run-integration:
runs-on: ubuntu-latest
steps:
- name: Run Sonarqube Integration
run: |
# Set Docker image and run the container
integration_type="sonarqube"
version="latest"
image_name="ghcr.io/port-labs/port-ocean-$integration_type:$version"
docker run -i --rm --platform=linux/amd64 \
-e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \
-e OCEAN__INITIALIZE_PORT_RESOURCES=true \
-e OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN=${{ secrets.OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN }} \
-e OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID=${{ secrets.OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID }} \
-e OCEAN__INTEGRATION__CONFIG__SONAR_URL=${{ secrets.OCEAN__INTEGRATION__CONFIG__SONAR_URL }} \
-e OCEAN__PORT__CLIENT_ID=${{ secrets.OCEAN__PORT__CLIENT_ID }} \
-e OCEAN__PORT__CLIENT_SECRET=${{ secrets.OCEAN__PORT__CLIENT_SECRET }} \
$image_name
exit $?
This pipeline will run the SonarQube integration once and then exit, this is useful for scheduled ingestion of data.
请确保配置以下Jenkins Credentials的 "Secret Text "类型:
Parameter | Description | Required |
---|---|---|
OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN | The SonarQube API token | ✅ |
OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID | The SonarQube organization Key (Not required when using on-prem sonarqube instance) | ✅ |
OCEAN__INTEGRATION__CONFIG__SONAR_URL | Required if using On-Prem, Your SonarQube instance URL | ❌ |
OCEAN__PORT__CLIENT_ID | Your port client id (How to get the credentials) | ✅ |
OCEAN__PORT__CLIENT_SECRET | Your port client secret (How to get the credentials) | ✅ |
OCEAN__INITIALIZE_PORT_RESOURCES | Default true, When set to true the integration will create default blueprints and the port App config Mapping. Read more about initializePortResources | ❌ |
OCEAN__INTEGRATION__IDENTIFIER | The identifier of the integration that will be installed | ✅ |
下面是 Jenkinsfile
groovy Pipelines 文件的示例:
pipeline {
agent any
stages {
stage('Run SonarQube Integration') {
steps {
script {
withCredentials([
string(credentialsId: 'OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN', variable: 'OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN'),
string(credentialsId: 'OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID', variable: 'OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID'),
string(credentialsId: 'OCEAN__PORT__CLIENT_ID', variable: 'OCEAN__PORT__CLIENT_ID'),
string(credentialsId: 'OCEAN__PORT__CLIENT_SECRET', variable: 'OCEAN__PORT__CLIENT_SECRET'),
]) {
sh('''
#Set Docker image and run the container
integration_type="sonarqube"
version="latest"
image_name="ghcr.io/port-labs/port-ocean-${integration_type}:${version}"
docker run -i --rm --platform=linux/amd64 \
-e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \
-e OCEAN__INITIALIZE_PORT_RESOURCES=true \
-e OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN=$OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN \
-e OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID=$OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID \
-e OCEAN__PORT__CLIENT_ID=$OCEAN__PORT__CLIENT_ID \
-e OCEAN__PORT__CLIENT_SECRET=$OCEAN__PORT__CLIENT_SECRET \
$image_name
exit $?
''')
}
}
}
}
}
}
This pipeline will run the SonarQube integration once and then exit, this is useful for scheduled ingestion of data.
确保使用Azure Devops variable groups 配置以下变量。将它们添加到名为 port-ocean-credentials
的变量组中:
Parameter | Description | Required |
---|---|---|
OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN | The SonarQube API token | ✅ |
OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID | The SonarQube organization Key (Not required when using on-prem sonarqube instance) | ✅ |
OCEAN__INTEGRATION__CONFIG__SONAR_URL | Required if using On-Prem, Your SonarQube instance URL | ❌ |
OCEAN__PORT__CLIENT_ID | Your port client id (How to get the credentials) | ✅ |
OCEAN__PORT__CLIENT_SECRET | Your port client secret (How to get the credentials) | ✅ |
OCEAN__INITIALIZE_PORT_RESOURCES | Default true, When set to true the integration will create default blueprints and the port App config Mapping. Read more about initializePortResources | ❌ |
OCEAN__INTEGRATION__IDENTIFIER | The identifier of the integration that will be installed | ✅ |
下面是 sonar-integration.yml
Pipelines 文件的示例:
trigger:
- main
pool:
vmImage: "ubuntu-latest"
variables:
- group: port-ocean-credentials
steps:
- script: |
echo Add other tasks to build, test, and deploy your project.
# Set Docker image and run the container
integration_type="sonarqube"
version="latest"
image_name="ghcr.io/port-labs/port-ocean-$integration_type:$version"
docker run -i --rm \
-e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \
-e OCEAN__INITIALIZE_PORT_RESOURCES=true \
-e OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN=${OCEAN__INTEGRATION__CONFIG__SONAR_API_TOKEN} \
-e OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID=${OCEAN__INTEGRATION__CONFIG__SONAR_ORGANIZATION_ID} \
-e OCEAN__INTEGRATION__CONFIG__SONAR_URL=${OCEAN__INTEGRATION__CONFIG__SONAR_URL} \
-e OCEAN__PORT__CLIENT_ID=${OCEAN__PORT__CLIENT_ID} \
-e OCEAN__PORT__CLIENT_SECRET=${OCEAN__PORT__CLIENT_SECRET} \
$image_name
exit $?
displayName: 'Ingest SonarQube Data into Port'
有关代理或自签名证书等高级配置,click here 。
接收 SonarQube 对象
SonarQube 集成使用 YAML 配置来描述将数据加载到开发者门户的过程。
下面是配置中的一个示例片段,演示了从 SonarQube 获取 "项目 "数据的过程:
resources:
- kind: projects
selector:
query: "true"
port:
entity:
mappings:
blueprint: '"sonarQubeProject"'
identifier: .key
title: .name
properties:
organization: .organization
link: .__link
lastAnalysisStatus: .__branch.status.qualityGateStatus
lastAnalysisDate: .__branch.analysisDate
numberOfBugs: .__measures[]? | select(.metric == "bugs") | .value
numberOfCodeSmells: .__measures[]? | select(.metric == "code_smells") | .value
numberOfVulnerabilities: .__measures[]? | select(.metric == "vulnerabilities") | .value
numberOfHotSpots: .__measures[]? | select(.metric == "security_hotspots") | .value
numberOfDuplications: .__measures[]? | select(.metric == "duplicated_files") | .value
coverage: .__measures[]? | select(.metric == "coverage") | .value
mainBranch: .__branch.name
tags: .tags
该集成利用JQ JSON processor 对 SonarQube 的 API 事件中的现有字段和 Values 进行选择、修改、连接、转换和其他操作。
Configuration structure
The integration configuration determines which resources will be queried from SonarQube, and which entities and properties will be created in Port.
The following resources can be used to map data from SonarQube, it is possible to reference any field that appears in the API responses linked below for the mapping configuration.
Project
- represents a SonarQube project. Retrieves data fromcomponents
,measures
, andbranches
.Issue
Analysis
- represents a SonarQube analysis and latest activity.
The current version of the Sonarqube integration does not support the analysis
kind for clients using on-premise
Sonarqube installation.
- The root key of the integration configuration is the
resources
key:
resources:
- kind: ONE_OF_THE_SUPPORTED_KINDS
selector:
...
- The
kind
key is a specifier for a SonarQube object:
resources:
- kind: ONE_OF_THE_SUPPORTED_KINDS
selector:
...
- The
selector
and thequery
keys allow you to filter which objects of the specifiedkind
will be ingested into your software catalog:
resources:
- kind: { props.customKind }
selector:
query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped.
port:
- The
port
,entity
and themappings
keys are used to map the SonarQube object fields to Port entities. To create multiple mappings of the same kind, you can add another item in theresources
array;
resources:
- kind: projects
selector:
query: "true"
port:
entity:
mappings:
blueprint: '"sonarQubeProject"'
identifier: .key
title: .name
properties:
organization: .organization
link: .__link
lastAnalysisStatus: .__branch.status.qualityGateStatus
lastAnalysisDate: .__branch.analysisDate
numberOfBugs: .__measures[]? | select(.metric == "bugs") | .value
numberOfCodeSmells: .__measures[]? | select(.metric == "code_smells") | .value
numberOfVulnerabilities: .__measures[]? | select(.metric == "vulnerabilities") | .value
numberOfHotSpots: .__measures[]? | select(.metric == "security_hotspots") | .value
numberOfDuplications: .__measures[]? | select(.metric == "duplicated_files") | .value
coverage: .__measures[]? | select(.metric == "coverage") | .value
mainBranch: .__branch.name
tags: .tags
- kind: projects # In this instance project is mapped again with a different filter
selector:
query: '.name == "MyProjectName"'
port:
entity:
mappings: ...
Note the value of the blueprint
key - if you want to use a hardcoded string, you need to encapsulate it in 2 sets of
quotes, for example use a pair of single-quotes ('
) and then another pair of double-quotes ("
)
Ingest data into Port
To ingest SonarQube objects using the integration configuration, you can follow the steps below:
- Go to the DevPortal Builder page.
- Select a blueprint you want to ingest using SonarQube.
- Choose the Ingest Data option from the menu.
- Select SonarQube under the Code quality & security providers category.
- Modify the configuration according to your needs.
- Click
Resync
.
示例
蓝图和相关集成配置示例:
项目
Projects blueprint
{
"identifier": "sonarQubeProject",
"title": "SonarQube Project",
"icon": "sonarqube",
"schema": {
"properties": {
"organization": {
"type": "string",
"title": "Organization",
"icon": "TwoUsers"
},
"link": {
"type": "string",
"format": "url",
"title": "Link",
"icon": "Link"
},
"lastAnalysisDate": {
"type": "string",
"format": "date-time",
"icon": "Clock",
"title": "Last Analysis Date"
},
"numberOfBugs": {
"type": "number",
"title": "Number Of Bugs"
},
"numberOfCodeSmells": {
"type": "number",
"title": "Number Of CodeSmells"
},
"numberOfVulnerabilities": {
"type": "number",
"title": "Number Of Vulnerabilities"
},
"numberOfHotSpots": {
"type": "number",
"title": "Number Of HotSpots"
},
"numberOfDuplications": {
"type": "number",
"title": "Number Of Duplications"
},
"coverage": {
"type": "number",
"title": "Coverage"
},
"mainBranch": {
"type": "string",
"icon": "Git",
"title": "Main Branch"
},
"tags": {
"type": "array",
"title": "Tags"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: projects
selector:
query: "true"
port:
entity:
mappings:
blueprint: '"sonarQubeProject"'
identifier: .key
title: .name
properties:
organization: .organization
link: .__link
lastAnalysisStatus: .__branch.status.qualityGateStatus
lastAnalysisDate: .__branch.analysisDate
numberOfBugs: .__measures[]? | select(.metric == "bugs") | .value
numberOfCodeSmells: .__measures[]? | select(.metric == "code_smells") | .value
numberOfVulnerabilities: .__measures[]? | select(.metric == "vulnerabilities") | .value
numberOfHotSpots: .__measures[]? | select(.metric == "security_hotspots") | .value
numberOfDuplications: .__measures[]? | select(.metric == "duplicated_files") | .value
coverage: .__measures[]? | select(.metric == "coverage") | .value
mainBranch: .__branch.name
tags: .tags
问题
Issue blueprint
{
"identifier": "sonarQubeIssue",
"title": "SonarQube Issue",
"icon": "sonarqube",
"schema": {
"properties": {
"type": {
"type": "string",
"title": "Type",
"enum": ["CODE_SMELL", "BUG", "VULNERABILITY"]
},
"severity": {
"type": "string",
"title": "Severity",
"enum": ["MAJOR", "INFO", "MINOR", "CRITICAL", "BLOCKER"],
"enumColors": {
"MAJOR": "orange",
"INFO": "green",
"CRITICAL": "red",
"BLOCKER": "red",
"MINOR": "yellow"
}
},
"link": {
"type": "string",
"format": "url",
"icon": "Link",
"title": "Link"
},
"status": {
"type": "string",
"title": "Status",
"enum": ["OPEN", "CLOSED", "RESOLVED", "REOPENED", "CONFIRMED"]
},
"assignees": {
"title": "Assignees",
"type": "string",
"icon": "TwoUsers"
},
"tags": {
"type": "array",
"title": "Tags"
},
"createdAt": {
"type": "string",
"format": "date-time",
"title": "Created At"
}
}
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {
"sonarQubeProject": {
"target": "sonarQubeProject",
"required": false,
"title": "SonarQube Project",
"many": false
}
}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: issues
selector:
query: "true"
port:
entity:
mappings:
blueprint: '"sonarQubeIssue"'
identifier: .key
title: .message
properties:
type: .type
severity: .severity
link: .__link
status: .status
assignees: .assignee
tags: .tags
createdAt: .creationDate
relations:
sonarQubeProject: .project
分析
Analysis blueprint
{
"identifier": "sonarQubeAnalysis",
"title": "SonarQube Analysis",
"icon": "sonarqube",
"schema": {
"properties": {
"branch": {
"type": "string",
"title": "Branch",
"icon": "GitVersion"
},
"fixedIssues": {
"type": "number",
"title": "Fixed Issues"
},
"newIssues": {
"type": "number",
"title": "New Issues"
},
"coverage": {
"title": "Coverage",
"type": "number"
},
"duplications": {
"type": "number",
"title": "Duplications"
},
"createdAt": {
"type": "string",
"format": "date-time",
"title": "Created At"
}
}
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {
"sonarQubeProject": {
"target": "sonarQubeProject",
"required": false,
"title": "SonarQube Project",
"many": false
}
}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: analysis
selector:
query: "true"
port:
entity:
mappings:
blueprint: '"sonarQubeAnalysis"'
identifier: .analysisId
title: .__commit.message // .analysisId
properties:
branch: .__branchName
fixedIssues: .measures.violations_fixed
newIssues: .measures.violations_added
coverage: .measures.coverage_change
duplications: .measures.duplicated_lines_density_change
createdAt: .__analysisDate
relations:
sonarQubeProject: .__project
让我们来测试一下
本节包括为保证质量而扫描代码库时 SonarQube 的响应数据示例。 此外,还包括根据上一节提供的 Ocean 配置从重新同步事件中创建的实体。
有效载荷
下 面是 SonarQube 提供的有效载荷结构示例:
Project response data
{
"organization": "peygis",
"key": "PeyGis_Chatbot_For_Social_Media_Transaction",
"name": "Chatbot_For_Social_Media_Transaction",
"isFavorite": true,
"tags": [],
"visibility": "public",
"eligibilityStatus": "COMPLETED",
"eligible": true,
"isNew": false,
"analysisDateAllBranches": "2023-09-09T03:03:20+0200",
"__measures": [
{
"metric": "bugs",
"value": "6",
"bestValue": false
},
{
"metric": "code_smells",
"value": "216",
"bestValue": false
},
{
"metric": "duplicated_files",
"value": "2",
"bestValue": false
},
{
"metric": "vulnerabilities",
"value": "1",
"bestValue": false
},
{
"metric": "security_hotspots",
"value": "8",
"bestValue": false
}
],
"__branch": {
"name": "master",
"isMain": true,
"type": "LONG",
"status": {
"qualityGateStatus": "ERROR",
"bugs": 6,
"vulnerabilities": 1,
"codeSmells": 216
},
"analysisDate": "2023-09-07T14:38:41+0200",
"commit": {
"sha": "5b01b6dcb200df0bfd1c66df65be30f9ea5423d8",
"author": {
"name": "Username",
"login": "Username@github",
"avatar": "9df2ac1caa70b0a67ff0561f7d0363e5"
},
"date": "2023-09-07T14:38:36+0200",
"message": "Merge pull request #21 from PeyGis/test-sonar"
}
},
"__link": "https://sonarcloud.io/project/overview?id=PeyGis_Chatbot_For_Social_Media_Transaction"
}
Issue response data
{
"key": "AYhnRlhI0rLhE5EBPGHW",
"rule": "xml:S1135",
"severity": "INFO",
"component": "PeyGis_Chatbot_For_Social_Media_Transaction:node_modules/json-schema/draft-zyp-json-schema-04.xml",
"project": "PeyGis_Chatbot_For_Social_Media_Transaction",
"line": 313,
"hash": "8346d5371c3d1b0d1d57937c7b967090",
"textRange": {
"startLine": 313,
"endLine": 313,
"startOffset": 3,
"endOffset": 56
},
"flows": [],
"status": "OPEN",
"message": "Complete the task associated to this \"TODO\" comment.",
"effort": "0min",
"debt": "0min",
"assignee": "Username@github",
"author": "[email protected]",
"tags": [],
"creationDate": "2018-04-06T02:44:46+0200",
"updateDate": "2023-05-29T13:30:14+0200",
"type": "CODE_SMELL",
"organization": "peygis",
"cleanCodeAttribute": "COMPLETE",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "MAINTAINABILITY",
"severity": "LOW"
}
],
"__link": "https://sonarcloud.io/project/issues?open=AYhnRlhI0rLhE5EBPGHW&id=PeyGis_Chatbot_For_Social_Media_Transaction"
}
Analysis response data
{
"analysisId": "AYpvptJNv89mE9ClYP-q",
"firstAnalysis": false,
"measures": {
"violations_added": "0",
"violations_fixed": "0",
"coverage_change": "0.0",
"duplicated_lines_density_change": "0.0",
"ncloc_change": "0"
},
"branch": {
"analysisDate": "2023-09-07T12:38:41.279Z",
"isMain": true,
"name": "master",
"commit": {
"sha": "5b01b6dcb200df0bfd1c66df65be30f9ea5423d8",
"author": {
"avatar": "9df2ac1caa70b0a67ff0561f7d0363e5",
"login": "Username@github",
"name": "Username"
},
"date": "2023-09-07T12:38:36Z",
"message": "Merge pull request #21 from PeyGis/test-sonar"
},
"type": "LONG",
"status": {
"qualityGateStatus": "ERROR"
}
},
"__branchName": "master",
"__analysisDate": "2023-09-07T12:38:41.279Z",
"__commit": {
"sha": "5b01b6dcb200df0bfd1c66df65be30f9ea5423d8",
"author": {
"avatar": "9df2ac1caa70b0a67ff0561f7d0363e5",
"login": "Username@github",
"name": "Username"
},
"date": "2023-09-07T12:38:36Z",
"message": "Merge pull request #21 from PeyGis/test-sonar"
},
"__project": "PeyGis_Chatbot_For_Social_Media_Transaction"
}
映射结果
结合样本有效载荷和 Ocean 配置,可生成以下 Port 实体:
Project entity in Port
{
"identifier": "PeyGis_Chatbot_For_Social_Media_Transaction",
"title": "Chatbot_For_Social_Media_Transaction",
"blueprint": "sonarQubeProject",
"team": [],
"properties": {
"organization": "peygis",
"link": "https://sonarcloud.io/project/overview?id=PeyGis_Chatbot_For_Social_Media_Transaction",
"lastAnalysisDate": "2023-09-07T12:38:41.000Z",
"numberOfBugs": 6,
"numberOfCodeSmells": 216,
"numberOfVulnerabilities": 1,
"numberOfHotSpots": 8,
"numberOfDuplications": 2,
"mainBranch": "master",
"tags": []
},
"relations": {},
"icon": "sonarqube"
}
Issue entity in Port
{
"identifier": "AYhnRlhI0rLhE5EBPGHW",
"title": "Complete the task associated to this \"TODO\" comment.",
"blueprint": "sonarQubeIssue",
"team": [],
"properties": {
"type": "CODE_SMELL",
"severity": "INFO",
"link": "https://sonarcloud.io/project/issues?open=AYhnRlhI0rLhE5EBPGHW&id=PeyGis_Chatbot_For_Social_Media_Transaction",
"status": "OPEN",
"assignees": "Username@github",
"tags": [],
"createdAt": "2018-04-06T00:44:46.000Z"
},
"relations": {
"sonarQubeProject": "PeyGis_Chatbot_For_Social_Media_Transaction"
},
"icon": "sonarqube"
}
Analysis entity in Port
{
"identifier": "AYpvptJNv89mE9ClYP-q",
"title": "Merge pull request #21 from PeyGis/test-sonar",
"blueprint": "sonarQubeAnalysis",
"team": [],
"properties": {
"branch": "master",
"fixedIssues": 0,
"newIssues": 0,
"coverage": 0,
"duplications": 0,
"createdAt": "2023-09-07T12:38:41.279Z"
},
"relations": {
"sonarQubeProject": "PeyGis_Chatbot_For_Social_Media_Transaction"
},
"icon": "sonarqube"
}
通过 webhook 进行替代安装
虽然上述海洋集成是推荐的安装方法,但您可能更喜欢使用 webhook 从 SonarQube 引用数据。 如果是这样,请使用以下说明:
Webhook installation (click to expand)
在本示例中,您将在SonarQube's SonarCloud 和 Port 之间创建一个 webhook 集成,用于接收 SonarQube 代码质量 "分析 "实体。
Port configuration
创建以下蓝图定义:
SonarQube analysis blueprint
{
"identifier": "sonarCloudAnalysis",
"description": "This blueprint represents a SonarCloud Analysis in our software catalog",
"title": "SonarCloud Analysis",
"icon": "sonarqube",
"schema": {
"properties": {
"serverUrl": {
"type": "string",
"format": "url",
"title": "Server URL"
},
"projectName": {
"type": "string",
"title": "Project name"
},
"projectUrl": {
"type": "string",
"format": "url",
"title": "Project URL"
},
"branchName": {
"type": "string",
"title": "Branch Name"
},
"branchType": {
"type": "string",
"title": "Branch Type"
},
"branchUrl": {
"type": "string",
"format": "url",
"title": "Branch URL"
},
"qualityGateName": {
"type": "string",
"title": "Quality Gate Name"
},
"qualityGateStatus": {
"type": "string",
"title": "Quality Gate Status",
"description": "General status of quality checks"
},
"qualityGateConditions": {
"type": "array",
"items": {
"type": "object"
},
"title": "Quality Gate Conditions",
"description": "Conditions of the qaulity gate"
},
"status": {
"type": "string",
"title": "General Status"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
创建以下 webhook 配置using Port's UI :
SonarQube analysis webhook configuration
- 基本信息 选项卡 - 填写以下详细信息:
1.title:
SonarQube mapper
; 2.标识符 :sonarqube_mapper
; 3.Description :将 SonarQube 警报映射到 Port
的 webhook 配置; 4.图标 :sonarqube
; - 集成配置选项卡 - 填写以下JQ映射:
[
{
"blueprint": "sonarCloudAnalysis",
"entity": {
"identifier": ".body.taskId",
"title": ".body.project.name + \"-\" + .body.taskId",
"properties": {
"serverUrl": ".body.serverUrl",
"status": ".body.status",
"projectName": ".body.project.name",
"projectUrl": ".body.project.url",
"branchName": ".body.branch.name",
"branchType": ".body.branch.type",
"branchUrl": ".body.branch.url",
"qualityGateName": ".body.qualityGate.name",
"qualityGateStatus": ".body.qualityGate.status",
"qualityGateConditions": ".body.qualityGate.conditions"
}
}
}
]
3.向下滚动到 高级设置,输入以下详细信息:
- secret:
WEBHOOK_SECRET
; 2.签名头名称:x-sonar-webhook-hmac-sha256
; 3.签名算法: 从下拉选项中选择sha256
; 4.点击页面底部的保存。 记住将WEBHOOK_SECRET
替换为您在 SonarCloud 中创建 webhook 时指定的真实secret。
Create a webhook in SonarCloud
- 进入SonarCloud ,选择要配置 webhook 的项目;
- 点击页面左下角的管理,然后选择Webhooks;
- 点击创建
- 输入以下详细信息:
Name
- 被用于一个有意义的名称,如 Port Webhook;URL
- 输入您在创建 webhook 配置后收到的url
密钥的值;Secret
- 输入您在创建 webhook 时指定的 secret 值; 5.单击页面底部的创建。
完成!您运行的任何新分析(例如,对新 PR 或对 PR 的更改)都将触发 webhook 事件,SonarCloud 将把该事件发送到 Port 提供的 webhook URL。 Port 将根据映射解析事件,并相应地更新目录实体。
Let's Test It
本节包括当扫描代码库以保证质量时从 SonarQube 发送的 webhook 事件示例。 此外,还包括根据上一节提供的 webhook 配置从事件中创建的实体。
Payload
下面是扫描 SonarQube 资源库时发送到 webhook URL 的有 效载荷结构示例:
Webhook event payload
{
"serverUrl": "https://sonarcloud.io",
"taskId": "AYi_1w1fcGD_RU1S5-r_",
"status": "SUCCESS",
"analysedAt": "2023-06-15T16:15:05+0000",
"revision": "575718d8287cd09630ff0ff9aa4bb8570ea4ef29",
"changedAt": "2023-06-15T16:15:05+0000",
"project": {
"key": "Username_Test_Python_App",
"name": "Test_Python_App",
"url": "https://sonarcloud.io/dashboard?id=Username_Test_Python_App"
},
"branch": {
"name": "master",
"type": "LONG",
"isMain": true,
"url": "https://sonarcloud.io/dashboard?id=Username_Test_Python_App"
},
"qualityGate": {
"name": "My Quality Gate",
"status": "ERROR",
"conditions": [
{
"metric": "code_smells",
"operator": "GREATER_THAN",
"value": "217",
"status": "ERROR",
"errorThreshold": "5"
},
{
"metric": "ncloc",
"operator": "GREATER_THAN",
"value": "8435",
"status": "ERROR",
"errorThreshold": "20"
},
{
"metric": "new_branch_coverage",
"operator": "LESS_THAN",
"status": "NO_VALUE",
"errorThreshold": "1"
},
{
"metric": "new_sqale_debt_ratio",
"operator": "GREATER_THAN",
"value": "1.0303030303030303",
"status": "OK",
"errorThreshold": "5"
},
{
"metric": "new_violations",
"operator": "GREATER_THAN",
"value": "3",
"status": "ERROR",
"errorThreshold": "1"
}
]
},
"properties": {}
}
Mapping Result
结合示例有效载荷和 webhook 配置可生成以下 Port 实体:
{
"identifier": "AYi_1w1fcGD_RU1S5-r_",
"title": "Test_Python_App-AYi_1w1fcGD_RU1S5-r_",
"blueprint": "sonarCloudAnalysis",
"properties": {
"serverUrl": "https://sonarcloud.io",
"status": "SUCCESS",
"projectName": "Test_Python_App",
"projectUrl": "https://sonarcloud.io/dashboard?id=Username_Test_Python_App",
"branchName": "master",
"branchType": "LONG",
"branchUrl": "https://sonarcloud.io/dashboard?id=Username_Test_Python_App",
"qualityGateName": "My Quality Gate",
"qualityGateStatus": "ERROR",
"qualityGateConditions": [
{
"metric": "code_smells",
"operator": "GREATER_THAN",
"value": "217",
"status": "ERROR",
"errorThreshold": "5"
},
{
"metric": "ncloc",
"operator": "GREATER_THAN",
"value": "8435",
"status": "ERROR",
"errorThreshold": "20"
},
{
"metric": "new_branch_coverage",
"operator": "LESS_THAN",
"status": "NO_VALUE",
"errorThreshold": "1"
},
{
"metric": "new_sqale_debt_ratio",
"operator": "GREATER_THAN",
"value": "1.0303030303030303",
"status": "OK",
"errorThreshold": "5"
},
{
"metric": "new_violations",
"operator": "GREATER_THAN",
"value": "3",
"status": "ERROR",
"errorThreshold": "1"
}
]
},
"relations": {}
}