screwdriver-coverage-sonar 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/commands.txt +3 -3
  2. package/index.js +287 -291
  3. package/package.json +6 -10
package/commands.txt CHANGED
@@ -1,13 +1,13 @@
1
1
  if [ ! -f sonar-project.properties ] && [ -z "$SD_SONAR_OPTS" ]; then echo "ERROR - No sonar project properties defined." && exit 0; fi
2
- if [ ! -z "$SD_PULL_REQUEST" ] && [ "$SD_SONAR_ENTERPRISE" = true ]; then export SD_SONAR_OPTS="${SD_SONAR_OPTS} -Dsonar.pullrequest.key=$SD_PULL_REQUEST -Dsonar.pullrequest.branch=${PR_BRANCH_NAME#*/} -Dsonar.pullrequest.base=${PR_BASE_BRANCH_NAME}"; fi
2
+ if [ ! -z "$SD_PULL_REQUEST" ] && [ "$SD_SONAR_ENTERPRISE" = true ]; then export SD_SONAR_OPTS="${SD_SONAR_OPTS} -Dsonar.pullrequest.key=$SD_PULL_REQUEST -Dsonar.pullrequest.branch='${PR_BRANCH_NAME#*/}' -Dsonar.pullrequest.base='${PR_BASE_BRANCH_NAME}'"; fi
3
3
  if curl --version > /dev/null 2>&1; then SD_CURL_CMD_WRAPPER="eval"; else SD_CURL_CMD_WRAPPER="sd-step exec --pkg-version 7.54.1 core/curl"; fi
4
4
  export SD_CURL_CMD_WRAPPER
5
- SONAR_TOKEN=`$SD_CURL_CMD_WRAPPER "curl -H \"Authorization: Bearer $SD_TOKEN\" $SD_SONAR_AUTH_URL"`
5
+ SONAR_TOKEN=`$SD_CURL_CMD_WRAPPER "curl -H \"Authorization: Bearer ${SD_TOKEN}\" \"${SD_SONAR_AUTH_URL}&selfSonarHost=${SD_SELF_SONAR_HOST}&selfSonarAdminToken=${SD_SELF_SONAR_ADMIN_TOKEN}\""`
6
6
  VERSION=`cat sonar-project.properties | awk -F= '/sonar.projectVersion=/{print $2}'`
7
7
  PROJECT_VERSION=${VERSION:='not provided'}
8
8
  SONAR_OPTIONS=`eval echo "$SD_SONAR_OPTS"`
9
9
  if echo $SCM_URL | grep git@ > /dev/null 2>&1; then export SONAR_SCM_URL=`echo $SCM_URL | sed -e 's/:/\//g' | sed -e 's/git@/https:\/\//g'`; else export SONAR_SCM_URL=${SCM_URL}; fi
10
- SCANNER_OPTION='"-Dsonar.host.url=$SD_SONAR_HOST" "-Dsonar.login=$SONAR_TOKEN" "-Dsonar.projectKey=$SD_SONAR_PROJECT_KEY" "-Dsonar.projectName=$SD_SONAR_PROJECT_NAME" "-Dsonar.projectVersion=$PROJECT_VERSION" "-Dsonar.links.scm=$SONAR_SCM_URL" "-Dsonar.links.ci=$SD_UI_URL/pipelines/$SD_PIPELINE_ID" "-Dsonar.scm.revision=$SD_BUILD_SHA" $SONAR_OPTIONS'
10
+ if [ ! -z "$SD_SELF_SONAR_HOST" ]; then SCANNER_OPTION='"-Dsonar.host.url=$SD_SELF_SONAR_HOST" "-Dsonar.login=$SONAR_TOKEN" "-Dsonar.projectKey=$SD_SONAR_PROJECT_KEY" "-Dsonar.projectName=$SD_SONAR_PROJECT_NAME" "-Dsonar.projectVersion=$PROJECT_VERSION" "-Dsonar.links.scm=$SONAR_SCM_URL" "-Dsonar.links.ci=$SD_UI_URL/pipelines/$SD_PIPELINE_ID" "-Dsonar.scm.revision=$SD_BUILD_SHA" $SONAR_OPTIONS'; else SCANNER_OPTION='"-Dsonar.host.url=$SD_SONAR_HOST" "-Dsonar.login=$SONAR_TOKEN" "-Dsonar.projectKey=$SD_SONAR_PROJECT_KEY" "-Dsonar.projectName=$SD_SONAR_PROJECT_NAME" "-Dsonar.projectVersion=$PROJECT_VERSION" "-Dsonar.links.scm=$SONAR_SCM_URL" "-Dsonar.links.ci=$SD_UI_URL/pipelines/$SD_PIPELINE_ID" "-Dsonar.scm.revision=$SD_BUILD_SHA" $SONAR_OPTIONS'; fi
11
11
  SCANNER_PATH="/opt/sd/sonar-scanner/bin/sonar-scanner"
12
12
  if [ -s ${SCANNER_PATH} ] && [ -x ${SCANNER_PATH} ]; then eval $SCANNER_PATH $SCANNER_OPTION || true; exit 0; fi
13
13
  if [ `uname` = 'Darwin' ]; then TARGET_OS='macosx'; else TARGET_OS='linux'; fi
package/index.js CHANGED
@@ -14,275 +14,6 @@ const CoverageBase = require('screwdriver-coverage-base');
14
14
  const COMMANDS = fs.readFileSync(path.join(__dirname, 'commands.txt'), 'utf8').trim();
15
15
  const DEFAULT_GIT_APP_NAME = 'Screwdriver Sonar PR Checks';
16
16
 
17
- let adminToken;
18
- let sonarHost;
19
- let sdCoverageAuthUrl;
20
- let sonarEnterprise;
21
- let sonarGitAppName;
22
-
23
- /**
24
- * Create a project in sonar
25
- * @method createProject
26
- * @param {String} projectKey Unique identifier for the project
27
- * @return {Promise} Project object if it gets created or empty object if already exists
28
- */
29
- function createProject(projectKey) {
30
- return request({
31
- method: 'POST',
32
- url: `${sonarHost}/api/projects/create?project=${projectKey}&name=${projectKey}`,
33
- username: adminToken
34
- }).catch(err => {
35
- if (err.statusCode === 400 && err.message.includes('already exists')) {
36
- return {};
37
- }
38
-
39
- throw new Error(`Failed to create project ${projectKey}: ${err.message}`);
40
- });
41
- }
42
-
43
- /**
44
- * Create a user in sonar
45
- * @method createUser
46
- * @param {String} username Username of the user
47
- * @param {String} password Password of the user
48
- * @return {Promise} User object if it gets created or empty object if already exists
49
- */
50
- function createUser(username, password) {
51
- return request({
52
- method: 'POST',
53
- url: `${sonarHost}/api/users/create?login=${username}&name=${username}&password=${password}`,
54
- username: adminToken
55
- }).catch(err => {
56
- if (err.statusCode === 400 && err.message.includes('already exists')) {
57
- return {};
58
- }
59
-
60
- throw new Error(`Failed to create user ${username}: ${err.message}`);
61
- });
62
- }
63
-
64
- /**
65
- * Configure Git App in SonarQube
66
- * @param {String} projectKey Sonar project key
67
- * @param {String} projectName Sonar project name
68
- * @return {Promise} Nothing if Git App configured successfully
69
- */
70
- function configureGitApp(projectKey, projectName) {
71
- const gitApp = sonarGitAppName;
72
- const gitAppEncoded = encodeURIComponent(gitApp);
73
- const componentId = encodeURIComponent(projectKey);
74
-
75
- // Check if binding exists
76
- return request({
77
- method: 'GET',
78
- url: `${sonarHost}/api/alm_settings/get_binding?project=${componentId}`,
79
- username: adminToken
80
- }).catch(() => {
81
- // if binding does not exist, add it
82
- logger.info(`Binding does not exist for Sonar project ${projectKey}, adding`);
83
-
84
- if (!sonarEnterprise || !projectName) {
85
- return Promise.resolve();
86
- }
87
-
88
- const parameters = `almSetting=${gitAppEncoded}&project=${componentId}&repository=${projectName}&summaryCommentEnabled=true`;
89
-
90
- return request({
91
- method: 'POST',
92
- url: `${sonarHost}/api/alm_settings/set_github_binding?${parameters}`,
93
- username: adminToken
94
- }).catch(error => {
95
- // if cannot configure app, do not throw err
96
- logger.error(`Failed to configure Git App ${gitApp} for Sonar project ${projectKey}: ${error.message}`);
97
-
98
- return Promise.resolve();
99
- });
100
- });
101
- }
102
-
103
- /**
104
- * Give specific user push access to the project
105
- * @method grantUserPermission
106
- * @param {String} username Username of the user
107
- * @param {String} projectKey Unique identifier for the project
108
- * @return {Promise} Nothing if permission granted
109
- */
110
- function grantUserPermission(username, projectKey) {
111
- // Always return 204 even with duplicate calls
112
- return request({
113
- method: 'POST',
114
- url: `${sonarHost}/api/permissions/add_user?login=${username}&permission=scan&projectKey=${projectKey}`,
115
- username: adminToken
116
- }).catch(err => {
117
- throw new Error(`Failed to grant user ${username} permission: ${err.message}`);
118
- });
119
- }
120
-
121
- /**
122
- * Generate an access token for the given user
123
- * @method generateToken
124
- * @param {String} username Username of the user
125
- * @return {Promise} Object with a token field
126
- */
127
- function generateToken(username) {
128
- const tokenName = uuidv4();
129
-
130
- return request({
131
- method: 'POST',
132
- url: `${sonarHost}/api/user_tokens/generate?login=${username}&name=${tokenName}`,
133
- username: adminToken
134
- }).catch(err => {
135
- throw new Error(`Failed to generate user ${username} token: ${err.message}`);
136
- });
137
- }
138
-
139
- /**
140
- * Get metrics for a project
141
- * @method getMetrics
142
- * @param {Object} config
143
- * @param {String} config.projectKey Sonar project key (job:jobId or pipeline:pipelineId)
144
- * @param {String} config.startTime Job start time
145
- * @param {String} config.endTime Job end time
146
- * @param {String} [config.prNum] Pull request number
147
- * @return {Promise} Object with coverage percentage and tests success percentage
148
- */
149
- function getMetrics({ projectKey, startTime, endTime, prNum, sonarEnterprise: enterpriseEnabled }) {
150
- const componentId = encodeURIComponent(projectKey);
151
- // get timezone offset (e.g. -0700) from 'Fri May 11 2018 15:25:37 GMT-0700 (PDT)'
152
- const timezoneOffset = new Date().toString().match(/GMT(.*?) /)[1];
153
- // Convert the time format from 2018-05-10T19:05:53.123Z to 2018-05-10T19:05:53-0700 as required by sonar
154
- const parsedStartTime = startTime.replace(/\.(.*)/, timezoneOffset);
155
- const parsedEndTime = endTime.replace(/\.(.*)/, timezoneOffset);
156
- const from = encodeURIComponent(parsedStartTime);
157
- const to = encodeURIComponent(parsedEndTime);
158
- let coverageUrl = `${sonarHost}/api/measures/search_history?component=${componentId}&metrics=tests,test_errors,test_failures,coverage&from=${from}&to=${to}&ps=1`;
159
-
160
- if (enterpriseEnabled && prNum) {
161
- coverageUrl = coverageUrl.concat(`&pullRequest=${prNum}`);
162
- }
163
-
164
- return request({
165
- method: 'GET',
166
- url: coverageUrl,
167
- username: adminToken
168
- })
169
- .then(result => {
170
- const measures = {};
171
-
172
- // measures in result is an array, covert it to an object with metric name as key
173
- (hoek.reach(result, 'body.measures') || []).forEach(measure => {
174
- measures[measure.metric] = measure;
175
- });
176
-
177
- const metrics = {
178
- coverage: hoek.reach(measures, 'coverage.history.0.value') || 'N/A',
179
- tests: 'N/A'
180
- };
181
- const zero = { default: 0 };
182
- const total = hoek.reach(measures, 'tests.history.0.value', { default: 'N/A' });
183
- const testErrors = hoek.reach(measures, 'test_errors.history.0.value', zero);
184
- const testFailures = hoek.reach(measures, 'test_failures.history.0.value', zero);
185
-
186
- if (!Number.isNaN(Number(total))) {
187
- const totalInt = parseInt(total, 10);
188
- const pass = totalInt - parseInt(testErrors, 10) - parseInt(testFailures, 10);
189
-
190
- metrics.tests = `${pass}/${totalInt}`;
191
- }
192
-
193
- return metrics;
194
- })
195
- .catch(err => {
196
- // if there is no coverage measurement target, 404 and 'Component key not found' are returned and this is not an error
197
- if (err.statusCode !== 404 || !/Component key '.*' not found/.test(err.message)) {
198
- // if cannot get coverage, do not throw err
199
- logger.error(
200
- `Failed to get coverage and tests percentage for Sonar project ${projectKey}: ${err.message}`
201
- );
202
- }
203
-
204
- return {
205
- tests: 'N/A',
206
- coverage: 'N/A'
207
- };
208
- });
209
- }
210
-
211
- /**
212
- * Determine Sonar project key, project name, and username based on:
213
- * - SonarQube edition
214
- * - job annotation
215
- * @method getProjectData
216
- * @param {Object} config
217
- * @param {Object} [config.scope] Coverage scope (pipeline or job)
218
- * @param {Boolean} config.enterpriseEnabled If enterprise is enabled
219
- * @param {String} config.jobId Screwdriver job ID
220
- * @param {String} config.jobName Screwdriver job name
221
- * @param {String} config.pipelineId Screwdriver pipeline ID
222
- * @param {String} config.pipelineName Screwdriver pipeline name
223
- * @param {String} [config.projectKey] Sonar project key
224
- * @param {String} [config.prNum] PR number
225
- * @param {String} [config.prParentJobId] Screwdriver PR parent job ID
226
- * @return {Object} Sonar project key, project name, and username
227
- */
228
- function getProjectData({
229
- scope,
230
- enterpriseEnabled,
231
- jobId: buildJobId,
232
- jobName: buildJobName,
233
- pipelineId,
234
- pipelineName,
235
- projectKey,
236
- prParentJobId,
237
- prNum
238
- }) {
239
- let jobId = buildJobId;
240
- let jobName = buildJobName;
241
-
242
- // Determine scope based on projectKey
243
- if (projectKey) {
244
- const [projectScope, id] = projectKey.split(':');
245
- const projectName = projectScope === 'pipeline' ? pipelineName : `${pipelineName}:${jobName}`;
246
- const username = `user-${projectScope}-${id}`;
247
-
248
- return {
249
- projectKey,
250
- username,
251
- projectName,
252
- projectScope
253
- };
254
- }
255
-
256
- // Use user-configured scope; otherwise figure out default scope: pipeline scope for enterprise edition, job scope for everything else
257
- const userScope = scope && scope !== 'undefined' ? scope : undefined;
258
- const coverageScope = userScope || (enterpriseEnabled ? 'pipeline' : 'job');
259
-
260
- if (coverageScope === 'pipeline') {
261
- return {
262
- projectKey: `pipeline:${pipelineId}`,
263
- projectName: pipelineName,
264
- username: `user-pipeline-${pipelineId}`,
265
- projectScope: coverageScope
266
- };
267
- }
268
-
269
- // Use prParentJobId as ID for PRs
270
- if (prNum && coverageScope === 'job' && enterpriseEnabled) {
271
- const prRegex = /^PR-(\d+)(?::([\w-]+))?$/;
272
- const prNameMatch = jobName.match(prRegex);
273
-
274
- jobId = prParentJobId;
275
- jobName = prNameMatch && prNameMatch.length > 1 ? prNameMatch[2] : jobName;
276
- }
277
-
278
- return {
279
- projectKey: `job:${jobId}`,
280
- projectName: `${pipelineName}:${jobName}`,
281
- username: `user-job-${jobId}`,
282
- projectScope: coverageScope
283
- };
284
- }
285
-
286
17
  class CoverageSonar extends CoverageBase {
287
18
  /**
288
19
  * Constructor
@@ -323,20 +54,283 @@ class CoverageSonar extends CoverageBase {
323
54
  'Invalid config for sonar coverage plugin'
324
55
  );
325
56
 
326
- sdCoverageAuthUrl = `${this.config.sdApiUrl}/v4/coverage/token`;
327
- adminToken = this.config.adminToken;
328
- sonarHost = this.config.sonarHost;
329
- sonarEnterprise = this.config.sonarEnterprise;
330
- sonarGitAppName = this.config.sonarGitAppName;
57
+ this.sdCoverageAuthUrl = `${config.sdApiUrl}/v4/coverage/token`;
58
+ this.adminToken = config.adminToken;
59
+ this.sonarHost = config.sonarHost;
60
+ this.sonarEnterprise = config.sonarEnterprise;
61
+ this.sonarGitAppName = config.sonarGitAppName;
331
62
 
332
- this.uploadCommands = COMMANDS.replace('$SD_SONAR_HOST', sonarHost)
333
- .replace('$SD_UI_URL', this.config.sdUiUrl)
334
- .replace('$SD_SONAR_ENTERPRISE', sonarEnterprise)
63
+ this.uploadCommands = COMMANDS.replace('$SD_SONAR_HOST', this.sonarHost)
64
+ .replace('$SD_UI_URL', config.sdUiUrl)
65
+ .replace('$SD_SONAR_ENTERPRISE', this.sonarEnterprise)
335
66
  .split('\n');
336
67
 
337
68
  this.uploadCommands[this.uploadCommands.length - 1] += ' || true';
338
69
  }
339
70
 
71
+ /**
72
+ * Create a project in sonar
73
+ * @method createProject
74
+ * @param {String} projectKey Unique identifier for the project
75
+ * @return {Promise} Project object if it gets created or empty object if already exists
76
+ */
77
+ createProject(projectKey) {
78
+ return request({
79
+ method: 'POST',
80
+ url: `${this.sonarHost}/api/projects/create?project=${projectKey}&name=${projectKey}`,
81
+ username: this.adminToken
82
+ }).catch(err => {
83
+ if (err.statusCode === 400 && err.message.includes('already exists')) {
84
+ return {};
85
+ }
86
+
87
+ throw new Error(`Failed to create project ${projectKey}: ${err.message}`);
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Create a user in sonar
93
+ * @method createUser
94
+ * @param {String} username Username of the user
95
+ * @param {String} password Password of the user
96
+ * @return {Promise} User object if it gets created or empty object if already exists
97
+ */
98
+ createUser(username, password) {
99
+ return request({
100
+ method: 'POST',
101
+ url: `${this.sonarHost}/api/users/create?login=${username}&name=${username}&password=${password}`,
102
+ username: this.adminToken
103
+ }).catch(err => {
104
+ if (err.statusCode === 400 && err.message.includes('already exists')) {
105
+ return {};
106
+ }
107
+
108
+ throw new Error(`Failed to create user ${username}: ${err.message}`);
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Configure Git App in SonarQube
114
+ * @param {String} projectKey Sonar project key
115
+ * @param {String} projectName Sonar project name
116
+ * @return {Promise} Nothing if Git App configured successfully
117
+ */
118
+ configureGitApp(projectKey, projectName) {
119
+ const gitApp = this.sonarGitAppName;
120
+ const gitAppEncoded = encodeURIComponent(gitApp);
121
+ const componentId = encodeURIComponent(projectKey);
122
+
123
+ // Check if binding exists
124
+ return request({
125
+ method: 'GET',
126
+ url: `${this.sonarHost}/api/alm_settings/get_binding?project=${componentId}`,
127
+ username: this.adminToken
128
+ }).catch(() => {
129
+ // if binding does not exist, add it
130
+ logger.info(`Binding does not exist for Sonar project ${projectKey}, adding`);
131
+
132
+ if (!this.sonarEnterprise || !projectName) {
133
+ return Promise.resolve();
134
+ }
135
+
136
+ const parameters = `almSetting=${gitAppEncoded}&project=${componentId}&repository=${projectName}&summaryCommentEnabled=true&monorepo=false`;
137
+
138
+ return request({
139
+ method: 'POST',
140
+ url: `${this.sonarHost}/api/alm_settings/set_github_binding?${parameters}`,
141
+ username: this.adminToken
142
+ }).catch(error => {
143
+ // if cannot configure app, do not throw err
144
+ logger.error(`Failed to configure Git App ${gitApp} for Sonar project ${projectKey}: ${error.message}`);
145
+
146
+ return Promise.resolve();
147
+ });
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Give specific user push access to the project
153
+ * @method grantUserPermission
154
+ * @param {String} username Username of the user
155
+ * @param {String} projectKey Unique identifier for the project
156
+ * @return {Promise} Nothing if permission granted
157
+ */
158
+ grantUserPermission(username, projectKey) {
159
+ // Always return 204 even with duplicate calls
160
+ return request({
161
+ method: 'POST',
162
+ url: `${this.sonarHost}/api/permissions/add_user?login=${username}&permission=scan&projectKey=${projectKey}`,
163
+ username: this.adminToken
164
+ }).catch(err => {
165
+ throw new Error(`Failed to grant user ${username} permission: ${err.message}`);
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Generate an access token for the given user
171
+ * @method generateToken
172
+ * @param {String} username Username of the user
173
+ * @return {Promise} Object with a token field
174
+ */
175
+ generateToken(username) {
176
+ const tokenName = uuidv4();
177
+
178
+ return request({
179
+ method: 'POST',
180
+ url: `${this.sonarHost}/api/user_tokens/generate?login=${username}&name=${tokenName}`,
181
+ username: this.adminToken
182
+ }).catch(err => {
183
+ throw new Error(`Failed to generate user ${username} token: ${err.message}`);
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Get metrics for a project
189
+ * @method getMetrics
190
+ * @param {Object} config
191
+ * @param {String} config.projectKey Sonar project key (job:jobId or pipeline:pipelineId)
192
+ * @param {String} config.startTime Job start time
193
+ * @param {String} config.endTime Job end time
194
+ * @param {String} [config.prNum] Pull request number
195
+ * @return {Promise} Object with coverage percentage and tests success percentage
196
+ */
197
+ getMetrics({ projectKey, startTime, endTime, prNum, sonarEnterprise: enterpriseEnabled }) {
198
+ const componentId = encodeURIComponent(projectKey);
199
+ // get timezone offset (e.g. -0700) from 'Fri May 11 2018 15:25:37 GMT-0700 (PDT)'
200
+ const timezoneOffset = new Date().toString().match(/GMT(.*?) /)[1];
201
+ // Convert the time format from 2018-05-10T19:05:53.123Z to 2018-05-10T19:05:53-0700 as required by sonar
202
+ const parsedStartTime = startTime.replace(/\.(.*)/, timezoneOffset);
203
+ const parsedEndTime = endTime.replace(/\.(.*)/, timezoneOffset);
204
+ const from = encodeURIComponent(parsedStartTime);
205
+ const to = encodeURIComponent(parsedEndTime);
206
+ let coverageUrl = `${this.sonarHost}/api/measures/search_history?component=${componentId}&metrics=tests,test_errors,test_failures,coverage&from=${from}&to=${to}&ps=1`;
207
+
208
+ if (enterpriseEnabled && prNum) {
209
+ coverageUrl = coverageUrl.concat(`&pullRequest=${prNum}`);
210
+ }
211
+
212
+ return request({
213
+ method: 'GET',
214
+ url: coverageUrl,
215
+ username: this.adminToken
216
+ })
217
+ .then(result => {
218
+ const measures = {};
219
+
220
+ // measures in result is an array, covert it to an object with metric name as key
221
+ (hoek.reach(result, 'body.measures') || []).forEach(measure => {
222
+ measures[measure.metric] = measure;
223
+ });
224
+
225
+ const metrics = {
226
+ coverage: hoek.reach(measures, 'coverage.history.0.value') || 'N/A',
227
+ tests: 'N/A'
228
+ };
229
+ const zero = { default: 0 };
230
+ const total = hoek.reach(measures, 'tests.history.0.value', { default: 'N/A' });
231
+ const testErrors = hoek.reach(measures, 'test_errors.history.0.value', zero);
232
+ const testFailures = hoek.reach(measures, 'test_failures.history.0.value', zero);
233
+
234
+ if (!Number.isNaN(Number(total))) {
235
+ const totalInt = parseInt(total, 10);
236
+ const pass = totalInt - parseInt(testErrors, 10) - parseInt(testFailures, 10);
237
+
238
+ metrics.tests = `${pass}/${totalInt}`;
239
+ }
240
+
241
+ return metrics;
242
+ })
243
+ .catch(err => {
244
+ // if there is no coverage measurement target, 404 and 'Component key not found' are returned and this is not an error
245
+ if (err.statusCode !== 404 || !/Component key '.*' not found/.test(err.message)) {
246
+ // if cannot get coverage, do not throw err
247
+ logger.error(
248
+ `Failed to get coverage and tests percentage for Sonar project ${projectKey}: ${err.message}`
249
+ );
250
+ }
251
+
252
+ return {
253
+ tests: 'N/A',
254
+ coverage: 'N/A'
255
+ };
256
+ });
257
+ }
258
+
259
+ /**
260
+ * Determine Sonar project key, project name, and username based on:
261
+ * - SonarQube edition
262
+ * - job annotation
263
+ * @method getProjectData
264
+ * @param {Object} config
265
+ * @param {Object} [config.scope] Coverage scope (pipeline or job)
266
+ * @param {Boolean} config.enterpriseEnabled If enterprise is enabled
267
+ * @param {String} config.jobId Screwdriver job ID
268
+ * @param {String} config.jobName Screwdriver job name
269
+ * @param {String} config.pipelineId Screwdriver pipeline ID
270
+ * @param {String} config.pipelineName Screwdriver pipeline name
271
+ * @param {String} [config.projectKey] Sonar project key
272
+ * @param {String} [config.prNum] PR number
273
+ * @param {String} [config.prParentJobId] Screwdriver PR parent job ID
274
+ * @return {Object} Sonar project key, project name, and username
275
+ */
276
+ getProjectData({
277
+ scope,
278
+ enterpriseEnabled,
279
+ jobId: buildJobId,
280
+ jobName: buildJobName,
281
+ pipelineId,
282
+ pipelineName,
283
+ projectKey,
284
+ prParentJobId,
285
+ prNum
286
+ }) {
287
+ let jobId = buildJobId;
288
+ let jobName = buildJobName;
289
+
290
+ // Determine scope based on projectKey
291
+ if (projectKey) {
292
+ const [projectScope, id] = projectKey.split(':');
293
+ const projectName = projectScope === 'pipeline' ? pipelineName : `${pipelineName}:${jobName}`;
294
+ const username = `user-${projectScope}-${id}`;
295
+
296
+ return {
297
+ projectKey,
298
+ username,
299
+ projectName,
300
+ projectScope
301
+ };
302
+ }
303
+
304
+ // Use user-configured scope; otherwise figure out default scope: pipeline scope for enterprise edition, job scope for everything else
305
+ const userScope = scope && scope !== 'undefined' ? scope : undefined;
306
+ const coverageScope = userScope || (enterpriseEnabled ? 'pipeline' : 'job');
307
+
308
+ if (coverageScope === 'pipeline') {
309
+ return {
310
+ projectKey: `pipeline:${pipelineId}`,
311
+ projectName: pipelineName,
312
+ username: `user-pipeline-${pipelineId}`,
313
+ projectScope: coverageScope
314
+ };
315
+ }
316
+
317
+ // Use prParentJobId as ID for PRs
318
+ if (prNum && coverageScope === 'job' && enterpriseEnabled) {
319
+ const prRegex = /^PR-(\d+)(?::([\w-]+))?$/;
320
+ const prNameMatch = jobName.match(prRegex);
321
+
322
+ jobId = prParentJobId;
323
+ jobName = prNameMatch && prNameMatch.length > 1 ? prNameMatch[2] : jobName;
324
+ }
325
+
326
+ return {
327
+ projectKey: `job:${jobId}`,
328
+ projectName: `${pipelineName}:${jobName}`,
329
+ username: `user-job-${jobId}`,
330
+ projectScope: coverageScope
331
+ };
332
+ }
333
+
340
334
  /**
341
335
  * Return an access token that build can use to talk to coverage server
342
336
  * @method getAccessToken
@@ -356,8 +350,8 @@ class CoverageSonar extends CoverageBase {
356
350
  let projectData = { username, projectKey, projectName };
357
351
 
358
352
  if (!username || !projectKey || !projectName || projectName.includes('undefined')) {
359
- projectData = getProjectData({
360
- enterpriseEnabled: sonarEnterprise,
353
+ projectData = this.getProjectData({
354
+ enterpriseEnabled: this.sonarEnterprise,
361
355
  jobId,
362
356
  jobName,
363
357
  prParentJobId,
@@ -370,11 +364,11 @@ class CoverageSonar extends CoverageBase {
370
364
 
371
365
  const password = uuidv4();
372
366
 
373
- return createProject(projectData.projectKey)
374
- .then(() => configureGitApp(projectData.projectKey, projectData.projectName))
375
- .then(() => createUser(projectData.username, password))
376
- .then(() => grantUserPermission(projectData.username, projectData.projectKey))
377
- .then(() => generateToken(projectData.username))
367
+ return this.createProject(projectData.projectKey)
368
+ .then(() => this.configureGitApp(projectData.projectKey, projectData.projectName))
369
+ .then(() => this.createUser(projectData.username, password))
370
+ .then(() => this.grantUserPermission(projectData.username, projectData.projectKey))
371
+ .then(() => this.generateToken(projectData.username))
378
372
  .then(res => res.body.token);
379
373
  }
380
374
 
@@ -410,8 +404,8 @@ class CoverageSonar extends CoverageBase {
410
404
  projectKey: coverageProjectKey,
411
405
  prParentJobId
412
406
  }) {
413
- const { projectScope, projectKey, projectName, username } = getProjectData({
414
- enterpriseEnabled: sonarEnterprise,
407
+ const { projectScope, projectKey, projectName, username } = this.getProjectData({
408
+ enterpriseEnabled: this.sonarEnterprise,
415
409
  jobId,
416
410
  pipelineId,
417
411
  scope,
@@ -424,9 +418,9 @@ class CoverageSonar extends CoverageBase {
424
418
 
425
419
  const infoObject = {
426
420
  envVars: {
427
- SD_SONAR_AUTH_URL: `${sdCoverageAuthUrl}?projectKey=${projectKey}&projectName=${projectName}&username=${username}&scope=${projectScope}`,
428
- SD_SONAR_HOST: sonarHost,
429
- SD_SONAR_ENTERPRISE: sonarEnterprise,
421
+ SD_SONAR_AUTH_URL: `${this.sdCoverageAuthUrl}?projectKey=${projectKey}&projectName=${projectName}&username=${username}&scope=${projectScope}`,
422
+ SD_SONAR_HOST: this.sonarHost,
423
+ SD_SONAR_ENTERPRISE: this.sonarEnterprise,
430
424
  SD_SONAR_PROJECT_KEY: projectKey,
431
425
  SD_SONAR_PROJECT_NAME: projectName
432
426
  }
@@ -434,10 +428,12 @@ class CoverageSonar extends CoverageBase {
434
428
 
435
429
  // Only get coverage percentage if the steps are finished
436
430
  if (projectKey && startTime && endTime) {
437
- return getMetrics({ projectKey, startTime, endTime, prNum, sonarEnterprise }).then(
431
+ const { sonarEnterprise } = this;
432
+
433
+ return this.getMetrics({ projectKey, startTime, endTime, prNum, sonarEnterprise }).then(
438
434
  ({ coverage, tests }) => {
439
435
  const componentId = encodeURIComponent(projectKey);
440
- let projectUrl = `${this.config.sonarHost}/dashboard?id=${componentId}`;
436
+ let projectUrl = `${this.sonarHost}/dashboard?id=${componentId}`;
441
437
 
442
438
  if (sonarEnterprise && prNum) {
443
439
  projectUrl = projectUrl.concat(`&pullRequest=${prNum}`);
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "screwdriver-coverage-sonar",
3
- "version": "3.3.2",
3
+ "version": "3.4.0",
4
4
  "description": "SonarQube implementation of the coverage-base class",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "pretest": "eslint .",
8
- "test": "nyc --report-dir ./artifacts/coverage --reporter=lcov mocha --reporter mocha-multi-reporters --reporter-options configFile=./mocha.config.json --recursive --timeout 4000 --retries 1 --exit --allow-uncaught true --color true",
9
- "semantic-release": "semantic-release pre && npm publish && semantic-release post"
8
+ "test": "nyc --report-dir ./artifacts/coverage --reporter=lcov mocha --reporter mocha-multi-reporters --reporter-options configFile=./mocha.config.json --recursive --timeout 4000 --retries 1 --exit --allow-uncaught true --color true"
10
9
  },
11
10
  "repository": {
12
11
  "type": "git",
13
- "url": "git@github.com:screwdriver-cd/coverage-sonar.git"
12
+ "url": "git+https://github.com/screwdriver-cd/coverage-sonar.git"
14
13
  },
15
14
  "homepage": "https://github.com/screwdriver-cd/coverage-sonar",
16
15
  "bugs": "https://github.com/screwdriver-cd/screwdriver/issues",
@@ -33,7 +32,7 @@
33
32
  "chai": "^4.2.0",
34
33
  "eslint": "^7.31.0",
35
34
  "eslint-config-screwdriver": "^5.0.1",
36
- "mocha": "^8.3.2",
35
+ "mocha": "^9.1.3",
37
36
  "mocha-multi-reporters": "^1.5.1",
38
37
  "mocha-sonarqube-reporter": "^1.0.2",
39
38
  "mockery": "^2.1.0",
@@ -49,9 +48,6 @@
49
48
  "uuid": "^3.4.0"
50
49
  },
51
50
  "release": {
52
- "debug": false,
53
- "verifyConditions": {
54
- "path": "./node_modules/semantic-release/src/lib/plugin-noop.js"
55
- }
51
+ "debug": false
56
52
  }
57
- }
53
+ }