Skip to content

Commit 261717d

Browse files
Merge pull request #3 from ServiceNow/scratch/STRY56337017
STRY56337017: Code changes for custom task for GitLab Docker
2 parents 46f7413 + 86f807c commit 261717d

File tree

11 files changed

+237
-20
lines changed

11 files changed

+237
-20
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# app-devops-gitlab
2+
3+
New repository for developing a plugin to integrate between ITSM DevOps and GitLab pipelines
4+
5+
### Owners
6+
7+
> ramachandrarao.p
8+
9+
### How to build on Jenkins
10+
* Create a `pom.xml` file at the root level
11+
* Go to the [BT1 Service Catalog](https://buildtools1.service-now.com/nav_to.do?uri=%2Fcom.glideapp.servicecatalog_cat_item_view.do%3Fv%3D1%26sysparm_id%3D9dbd0c54db1acb403a3d5dd5ce961948%26sysparm_link_parent%3Dad2fecb72bfc310052f7c71317da157e%26sysparm_catalog%3De0d08b13c3330100c8b837659bba8fb4%26sysparm_catalog_view%3Dess%26sysparm_view%3Dess) to request a Jenkins job
12+
13+
Once the request is processed, a multi-branch job is created on https://buildmaster-hotel.devsnc.com and will build any branches that match [ServiceNow branch naming convention](https://buildtools1.service-now.com/kb_view_customer.do?sysparm_article=KB0528607).
14+
115
# CLI example using npm modules
216

317
## Build and install
@@ -75,7 +89,7 @@ package:
7589
image: servicenowdocker/sndevops-cli:0.35
7690
script:
7791
- sndevopscli create artifact -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}]'
78-
- sndevopscli create package -n "package-mame" -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}]
92+
- sndevopscli create package -n "package-name" -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}]
7993

8094
```
8195

@@ -116,3 +130,17 @@ ServiceNow DevOps Change:
116130

117131
```
118132

133+
**Example of sonar summary for ServiceNow via commandline**
134+
```yaml
135+
136+
stages:
137+
- DevOpsSonarStage
138+
139+
ServiceNow DevOps Sonar Scan Results:
140+
stage: DevOpsSonarStage
141+
image: servicenowdocker/sndevops-cli:0.35
142+
script:
143+
- sndevopscli create sonar -sonarUrl 'https://sonarcloud.io' -sonarProjectKey 'xxxxxxx'
144+
145+
```
146+

src/api/base/sndevopsApi.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,14 @@ class SndevopsApi {
7070
});;
7171
}
7272

73+
fetchBranchName() {
74+
let branchName = BaseEnv.CI_COMMIT_BRANCH || BaseEnv.CI_DEFAULT_BRANCH;
75+
if(BaseEnv.CI_PIPELINE_SOURCE == 'merge_request_event')
76+
branchName = BaseEnv.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
7377

78+
return branchName;
79+
}
80+
7481
}
7582

7683
module.exports = SndevopsApi
Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ class ChangeRequestManager extends SnDevopsApi {
3838
"projectId": BaseEnv.CI_PROJECT_ID,
3939
"jobId": BaseEnv.CI_JOB_ID,
4040
"pipelineName": BaseEnv.CI_PROJECT_TITLE,
41-
"jobName": BaseEnv.CI_JOB_NAME,
42-
"branchName": BaseEnv.CI_COMMIT_BRANCH
41+
"jobName": BaseEnv.CI_JOB_NAME
4342
};
4443

4544
//ChangeAttributes are optional
@@ -147,12 +146,15 @@ class ChangeRequestManager extends SnDevopsApi {
147146
}
148147
throw new Error(errMsg);
149148
}
150-
151-
if(status) {
152-
var result = response.data.result;
153-
if (result && result.status ==="Success" && result.changeControl) {
154-
console.log('\n \x1b[1m\x1b[36m' + 'Change created successfully.' + '\x1b[0m\x1b[0m');
155-
}
149+
}
150+
151+
if(status) {
152+
var result = response.data.result;
153+
if (result && result.status == "Success") {
154+
if(result.message)
155+
console.log('\n \x1b[1m\x1b[36m' + result.message + '\x1b[0m\x1b[0m');
156+
else
157+
console.log('\n \x1b[1m\x1b[36m' + "The job is under change control. A callback request is created and polling has been started to retrieve the change info." + '\x1b[0m\x1b[0m');
156158
}
157159
}
158160
} catch(error) {
@@ -348,13 +350,14 @@ class ChangeRequestManager extends SnDevopsApi {
348350

349351
_getRequestBodyForChangeCreation(changePayload) {
350352
this.validateChangePayload(changePayload);
353+
let branchName = this.fetchBranchName();
351354
return {
352355
"toolId": this.toolId,
353356
"stageName": changePayload.jobName,
354357
"buildNumber": changePayload.buildNumber,
355358
"callbackURL": changePayload.apiPath + "/projects/" + changePayload.projectId +"/jobs/" + changePayload.jobId,
356-
"isMultiBranch": "false",
357-
"branchName": changePayload.branchName,
359+
"isMultiBranch": "true",
360+
"branchName": branchName,
358361
"changeRequestDetails": changePayload.changeRequestDetails,
359362
"action": "customChange",
360363
"pipelineName": changePayload.pipelineName,

src/api/package/packageManager.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const SnDevopsApi = require('../base/sndevopsApi.js')
2+
3+
const API_PACKAGE_PATH = 'api/sn_devops/devops/package/registration';
4+
const BaseEnv = require('../../common/baseEnv.js')
5+
6+
7+
class PackageManager extends SnDevopsApi {
8+
9+
createPackage(packageName, artifacts) {
10+
11+
let url = new URL(API_PACKAGE_PATH, this.url);
12+
url.searchParams.append("orchestrationToolId", this.toolId)
13+
14+
console.log("Computed ServiceNow url " + url.toString())
15+
16+
let artifactJson = JSON.parse(artifacts);
17+
let payload = this._getRequestBodyForPackageRegistration(packageName, artifactJson)
18+
19+
this.post(url.toString(), payload, this._getAuthHeaderWithToken());
20+
21+
}
22+
23+
_getRequestBodyForPackageRegistration(packageName, artifacts) {
24+
let requestPayload = {
25+
"name": packageName,
26+
"pipelineName": BaseEnv.CI_PROJECT_PATH,
27+
"stageName": BaseEnv.CI_JOB_NAME,
28+
"taskExecutionNumber": BaseEnv.CI_JOB_ID,
29+
"orchestrationTask": {
30+
"orchestrationTaskURL": BaseEnv.CI_PIPELINE_URL,
31+
"orchestrationTaskName": BaseEnv.CI_JOB_NAME,
32+
"branchName": BaseEnv.CI_COMMIT_BRANCH,
33+
"toolId": this.toolId,
34+
},
35+
"artifacts": [
36+
{
37+
"name": artifacts[0].name,
38+
"pipelineName": BaseEnv.CI_PROJECT_PATH,
39+
"repositoryName": artifacts[0].repositoryName,
40+
"stageName": BaseEnv.CI_JOB_NAME,
41+
"taskExecutionNumber": BaseEnv.CI_JOB_ID,
42+
"semanticVersion": artifacts[0].semanticVersion,
43+
"version": artifacts[0].version
44+
}
45+
]
46+
}
47+
return requestPayload;
48+
49+
}
50+
51+
}
52+
53+
module.exports = PackageManager;

src/api/sonar/sonarRegistration.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const SnDevopsApi = require('../base/sndevopsApi.js')
2+
const axios = require('axios');
3+
const API_SONAR_PATH = 'api/sn_devops/devops/tool/softwarequality';
4+
const BaseEnv = require('../../common/baseEnv.js')
5+
6+
7+
class SonarRegistrationManager extends SnDevopsApi {
8+
9+
async createSonarSummary(sonarProjectKey, sonarUrl) {
10+
let sonarPayload; //Payload contains information necessary for fetching sonar summary
11+
let endpoint;
12+
let httpHeaders;
13+
let response;
14+
15+
//Mandatory field validation check
16+
if(!sonarUrl) {
17+
console.error("sonarUrl is a required field. Enter the correct sonarUrl and try again.");
18+
process.exit(1);
19+
}
20+
if(!sonarProjectKey) {
21+
console.error("sonarProjectKey is a required field. Enter the correct sonarProjectKey and try again.");
22+
process.exit(1);
23+
}
24+
25+
sonarPayload = {
26+
"sonarProjectKey": sonarProjectKey,
27+
"sonarUrl": sonarUrl,
28+
};
29+
30+
endpoint = new URL(API_SONAR_PATH, this.url);
31+
endpoint.searchParams.append("toolId", this.toolId)
32+
console.log("Computed ServiceNow url " + endpoint.toString());
33+
34+
let payload = this._getRequestBodyForSonarSummary(sonarPayload);
35+
console.log("Payload to fetch sonar summary = " + JSON.stringify(payload));
36+
37+
const defaultHeadersForToken = {
38+
'Content-Type': 'application/json',
39+
'Accept': 'application/json',
40+
'Authorization': 'sn_devops.DevOpsToken ' + this.toolId + ":" + this.token
41+
};
42+
httpHeaders = { headers: defaultHeadersForToken };
43+
44+
try {
45+
response = await axios.post(endpoint.toString(), JSON.stringify(payload), httpHeaders);
46+
} catch (e) {
47+
if (e.message.includes('ECONNREFUSED') || e.message.includes('ENOTFOUND') || e.message.includes('405')) {
48+
console.error('ServiceNow Instance URL is NOT valid. Enter the correct the URL and try again.');
49+
process.exit(1);
50+
51+
} else if (e.message.includes('401')) {
52+
console.error('The SNOW_TOKEN and SNOW_TOOLID are incorrect. Verify that the GitLab project level variables are configured.');
53+
process.exit(1);
54+
55+
} else if(e.message.includes('400') || e.message.includes('404')){
56+
let errMsg = '[ServiceNow DevOps] Register Sonar Scan Summaries are not Successful. ';
57+
let errMsgSuffix = ' Provide valid inputs and verify that the GitLab project level variables are configured.';
58+
let responseData = e.response.data;
59+
60+
if (responseData && responseData.result && responseData.result.errorMessage) {
61+
errMsg = errMsg + responseData.result.errorMessage + errMsgSuffix;
62+
console.error(errMsg);
63+
process.exit(1);
64+
}
65+
else if (responseData && responseData.result && responseData.result.details && responseData.result.details.errors) {
66+
let errors = responseData.result.details.errors;
67+
for (var index in errors) {
68+
errMsg = errMsg + errors[index].message + errMsgSuffix;
69+
}
70+
console.error(errMsg);
71+
process.exit(1);
72+
}
73+
74+
} else {
75+
console.error('ServiceNow DevOps Event to register Sonar Scan Summaries is not created. Please check ServiceNow logs for more details.');
76+
process.exit(1);
77+
78+
}
79+
}
80+
}
81+
82+
_getRequestBodyForSonarSummary(sonarPayload) {
83+
let branchName = this.fetchBranchName();
84+
return {
85+
"sonarProjectKey": sonarPayload.sonarProjectKey,
86+
"sonarUrl": sonarPayload.sonarUrl,
87+
"repositoryName": BaseEnv.CI_PROJECT_TITLE,
88+
"pipelineName": BaseEnv.CI_PROJECT_TITLE,
89+
"buildNumber": BaseEnv.CI_JOB_ID,
90+
"stageName": BaseEnv.CI_JOB_NAME,
91+
"branchName": branchName,
92+
"gitLabProjectId": BaseEnv.CI_PROJECT_ID,
93+
"toolId": this.toolId
94+
};
95+
96+
}
97+
98+
}
99+
100+
module.exports = SonarRegistrationManager;

src/commands/base.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module.exports = class Base{
55

66
addDefaultOptions(command){
77

8-
command.option("-t, --toolId <toolId>", "Tool ID, if not passed via environmentaal variable")
8+
command.option("-t, --toolId <toolId>", "Tool ID, if not passed via environmental variable")
99
.option("--token <token>", "Token ID, if not passed via environmental variable")
1010
.option("-u --url <nowUrl>", "Service Now Url, if not passed via environmental variable")
1111

src/commands/create.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const Base = require('./base');
55
const ChangeRequest = require('./entity/changeRequest')
66
const Package = require('./entity/package')
77
const Artifact = require('./entity/artifact');
8+
const SonarRegistration = require('./entity/sonarRegistration');
89

910
module.exports = class Create extends Base{
1011

@@ -17,9 +18,7 @@ module.exports = class Create extends Base{
1718
new ChangeRequest().create(createCommand);
1819
new Package().create(createCommand);
1920
new Artifact().create(createCommand);
20-
21+
new SonarRegistration().create(createCommand);
2122
}
22-
23-
2423

2524
}

src/commands/entity/changeRequest.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Base = require("../base");
2-
const changeRequest = require('../../api/artifact/changeRequest.js');
2+
const ChangeRequestManager = require('../../api/change/changeRequest.js');
33

44
const COMMAND_NAME = 'change'
55

@@ -11,10 +11,10 @@ module.exports = class ChangeRequest extends Base {
1111
const command = createCommand.command(COMMAND_NAME);
1212
this.addDefaultOptions(command);
1313

14-
command.option('-p , --payload <changePayload>', "Change payload in JSON format");
14+
command.option('-p , --payload <changeAttributesPayload>', "Change Attributes payload in JSON format");
1515
command.action((options) => {
1616
console.log('Calling Change Control API to create change....');
17-
new changeRequest(options.url,options.token,options.toolId).createChange(options.payload);
17+
new ChangeRequestManager(options.url,options.token,options.toolId).createChange(options.payload);
1818
})
1919
}
2020

src/commands/entity/package.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Base = require("../base");
2-
const ArtifactManager = require("../../api/artifact/artifactManager")
2+
const PackageManager = require("../../api/package/packageManager")
33
const COMMAND_NAME = 'package'
44

55
module.exports = class Package extends Base {
@@ -14,7 +14,7 @@ module.exports = class Package extends Base {
1414

1515
command.action((options) => {
1616
console.log("Create Artifacts action called " )
17-
new ArtifactManager(options.url,options.token,options.toolId).createPackage(options.name, options.artifacts)
17+
new PackageManager(options.url,options.token,options.toolId).createPackage(options.name, options.artifacts)
1818

1919
})
2020
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const Base = require("../base");
2+
const SonarRegistrationManager = require('../../api/sonar/sonarRegistration.js');
3+
4+
const COMMAND_NAME = 'sonar'
5+
6+
module.exports = class SonarRegistration extends Base {
7+
constructor(){
8+
super()
9+
}
10+
11+
create(createCommand) {
12+
13+
const command = createCommand.command(COMMAND_NAME);
14+
this.addDefaultOptions(command);
15+
16+
command.requiredOption('-sonarProjectKey, --sonarProjectKey <sonarProjectKey>', 'Sonar Project Key')
17+
.requiredOption('-sonarUrl, --sonarUrl <sonarUrl>', 'Sonar URL');
18+
19+
command.action((options) => {
20+
console.log("Sonar registartion action called " )
21+
new SonarRegistrationManager(options.url,options.token,options.toolId).createSonarSummary(options.sonarProjectKey, options.sonarUrl)
22+
})
23+
}
24+
25+
}

0 commit comments

Comments
 (0)