Skip to content

Commit facca54

Browse files
authored
Lr snow std change (#719)
* 1.2.5 fix some CVEs Signed-off-by: lrochette <[email protected]> * 1.2.5 fix some CVEs Signed-off-by: lrochette <[email protected]> * Snow: standard change Signed-off-by: lrochette <[email protected]> * Snow: standard change Signed-off-by: lrochette <[email protected]> * missing comma Signed-off-by: lrochette <[email protected]> * misplaced comma Signed-off-by: lrochette <[email protected]> * chainging indentation Signed-off-by: lrochette <[email protected]> * missing s Signed-off-by: lrochette <[email protected]> * replacing empty by eq Signed-off-by: lrochette <[email protected]> * fix synax Signed-off-by: lrochette <[email protected]> --------- Signed-off-by: lrochette <[email protected]>
1 parent 4e1233e commit facca54

File tree

4 files changed

+145
-38
lines changed

4 files changed

+145
-38
lines changed

incubating/service-now/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v1.4.0 (Mar 28, 2025)
4+
### Added
5+
* ability to create a Standard Change passing the template name
6+
in the `STD_CR_TEMPLATE` variable.
7+
38
## [1.2.5] - 2025-01-02
49

510
### Fixed

incubating/service-now/README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ Example `codefresh.yml` build is below with required Arguments in place.
2222
| CR_ACTION | createCR | string | no | createCR, closeCR, updateCR | the operation to execute |
2323
| CR_CONFLICT_POLICY | ignore | string | no | ignore, wait, reject | What do when a schedule conflict arises |
2424
| CR_DATA | N/A | JSON string | no | JSON block | the JSON block to pass when opening, updating or closing a CR |
25+
| STD_CR_TEMPLATE | | string | no | | the name of a Standard template |
2526
| CR_SYSID | N/A | string | no | uuid | the sysid of the CR record as returned by the createCR action. USed to update or close a CR |
2627
| CR_CLOSE_CODE | successful | string | no | sucessful or any value accepted by the close_code field |
2728
| CR_CLOSE_NOTES | N/A | string | no | Any string accepted for the close_notes field |
2829

2930

30-
### codefresh.yml
31+
### codefresh.yaml
3132

3233
Codefresh build step to execute AWS CDK commands
3334

@@ -62,7 +63,7 @@ steps:
6263
echo END_DATE=\"$END_DATE\" >> ${{CF_VOLUME_PATH}}/env_vars_to_export
6364
6465
createCR:
65-
type: service-now
66+
type: service-now:1.4.0
6667
title: Create Service Now Change Request
6768
stage: deploy
6869
arguments:
@@ -73,7 +74,7 @@ steps:
7374
TOKEN: ${{CF_TOKEN}}
7475
CR_CONFLICT_POLICY: reject
7576
CR_DATA: >-
76-
{"short_description": "Globex deployment to QA",
77+
{"short_description": "Globex deployment to QA",
7778
"description": "Change for build ${{CF_BUILD_ID}}.\nThis change was created by the Codefresh plugin",
7879
"justification": "I do not need a justification\nMy app is awesome",
7980
"cmdb_ci":"tomcat",
@@ -91,7 +92,7 @@ steps:
9192
modifyCR:
9293
stage: deploy
9394
title: "Modify the implementation plan"
94-
type: service-now
95+
type: service-now:1.4.0
9596
fail_fast: false
9697
arguments:
9798
CR_ACTION: updateCR
@@ -120,7 +121,7 @@ steps:
120121
modifyTestPlan:
121122
stage: test
122123
title: "Modify the test plan"
123-
type: service-now
124+
type: service-now:1.4.0
124125
fail_fast: false
125126
arguments:
126127
CR_ACTION: updateCR
@@ -131,7 +132,7 @@ steps:
131132
CR_DATA: '{"test_plan":"The testing suit has passed."}'
132133

133134
closeCR:
134-
type: service-now
135+
type: service-now:1.4.0
135136
title: Close Service Now Change Request
136137
stage: post
137138
arguments:

incubating/service-now/lib/snow.py

+122-28
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,27 @@
33
import json
44
import requests
55
import logging
6+
import urllib.parse
67

78
API_NAMESPACE=409723
89
env_file_path = "/meta/env_vars_to_export"
910

11+
def exportVariable(name, value):
12+
if os.path.exists(env_file_path):
13+
file=open(env_file_path, "a")
14+
else:
15+
file=open("/tmp/env_vars_to_export", "a")
16+
file.write(f"{name}={value}\n")
17+
file.close()
18+
19+
def exportJson(name, json):
20+
if os.path.exists(env_file_path):
21+
json_file = open("/codefresh/volume/%s" %(name), "a")
22+
else:
23+
json_file = open("/tmp/%s" % (name), "a")
24+
json_file.write(json)
25+
json_file.close()
26+
1027
def getBaseUrl(instance):
1128
baseUrl = "%s/api" %(instance);
1229
logging.debug("baseUrl: " + baseUrl)
@@ -41,16 +58,9 @@ def processCreateChangeRequestResponse(response):
4158
logging.info(f" Change Request sys_id: {CR_SYSID}")
4259
logging.debug( " Change Request full answer:\n" + FULL_JSON)
4360

44-
if os.path.exists(env_file_path):
45-
env_file = open(env_file_path, "a")
46-
env_file.write(f"CR_NUMBER={CR_NUMBER}\n")
47-
env_file.write(f"CR_SYSID={CR_SYSID}\n")
48-
env_file.write("CR_FULL_JSON=/codefresh/volume/servicenow-cr.json\n")
49-
env_file.close()
50-
51-
json_file=open("/codefresh/volume/servicenow-cr.json", "w")
52-
json_file.write(FULL_JSON)
53-
json_file.close()
61+
exportVariable("CR_NUMBER", CR_NUMBER)
62+
exportVariable("CR_SYSID", CR_SYSID)
63+
exportVariable("CR_CREATE_JSON", FULL_JSON)
5464

5565
#
5666
# Call SNow REST API to create a new Change Request
@@ -80,6 +90,79 @@ def createChangeRequest(user, password, baseUrl, data):
8090
auth=(user, password))
8191
processCreateChangeRequestResponse(response=resp)
8292

93+
def processSearchStandardTemplateResponse(name, response):
94+
logging.info("Processing answer from Standard Template search")
95+
logging.debug("Template search returned code %s" % (response.status_code))
96+
if (response.status_code != 200 and response.status_code != 201):
97+
logging.critical("Standard Change Template for '%s' errored out with code %s", name, response.status_code)
98+
logging.critical("%s" + response.text)
99+
sys.exit(response.status_code)
100+
data=response.json()
101+
logging.debug("Full JSON answer: %s", data)
102+
103+
if len(data["result"]) ==0 :
104+
logging.critical("Standard Change Template '%s' was not found", name)
105+
sys.exit(1)
106+
107+
logging.info("Standard template search successful")
108+
STD_SYSID=data["result"][0]["sys_id"]
109+
return STD_SYSID
110+
111+
def processCreateStandardChangeRequestResponse(response):
112+
logging.info("Processing answer from standard CR creation REST call")
113+
logging.debug("Change Request returned code %s" % (response.status_code))
114+
if (response.status_code != 200 and response.status_code != 201):
115+
logging.critical("Change Request creation failed with code %s", response.status_code)
116+
logging.critical("%s", response.text)
117+
sys.exit(response.status_code)
118+
119+
logging.info("Change Request creation successful")
120+
data=response.json()
121+
FULL_JSON=json.dumps(data, indent=2)
122+
CR_NUMBER=data["result"]["number"]["value"]
123+
CR_SYSID=data["result"]["sys_id"]["value"]
124+
exportVariable("CR_NUMBER", CR_NUMBER)
125+
exportVariable("CR_SYSID", CR_SYSID)
126+
exportVariable("CR_CREATE_JSON", FULL_JSON)
127+
return CR_NUMBER
128+
129+
# Call SNow REST API to create a new Standard Change Request
130+
# Fields required are pasted in the data
131+
def createStandardChangeRequest(user, password, baseUrl, data, standardName):
132+
logging.info("Creating a new Standard Change Request using '%s' template", standardName)
133+
encodedName=urllib.parse.quote_plus(standardName)
134+
135+
url="%s/now/table/std_change_record_producer?sysparm_query=sys_name=%s" % (baseUrl, encodedName)
136+
137+
logging.debug("Standard Change URL %s:",url)
138+
resp=requests.get(url,
139+
headers = {"content-type":"application/json"},
140+
auth=(user, password))
141+
sysid=processSearchStandardTemplateResponse(name=standardName, response=resp)
142+
logging.info("Template found: %s", sysid)
143+
144+
if (bool(data)):
145+
crBody=json.loads(data)
146+
logging.debug("Data: %s", data)
147+
else:
148+
crBody= {}
149+
logging.debug(" Data: None")
150+
crBody["cf_build_id"] = os.getenv('CF_BUILD_ID')
151+
152+
153+
url="%s/sn_chg_rest/change/standard/%s" % (baseUrl, sysid)
154+
155+
logging.debug("URL %s:",url)
156+
logging.debug("User: %s", user)
157+
logging.debug("Body: %s", crBody)
158+
159+
resp=requests.post(url,
160+
json = crBody,
161+
headers = {"content-type":"application/json"},
162+
auth=(user, password))
163+
return processCreateStandardChangeRequestResponse(response=resp)
164+
165+
83166
def processModifyChangeRequestResponse(response, action):
84167

85168
logging.debug("Processing answer from CR %s REST call" %(action))
@@ -97,24 +180,17 @@ def processModifyChangeRequestResponse(response, action):
97180
FULL_JSON=json.dumps(data, indent=2)
98181

99182
if (action == "close" ):
100-
jsonVar="CR_CLOSE_FULL_JSON"
101-
jsonFileName="/codefresh/volume/servicenow-cr-close.json"
183+
exportVariable("CR_CLOSE_FULL_JSON", "/codefresh/volume/servicenow-cr-close.json")
184+
exportJson("servicenow-cr-close.json", FULL_JSON)
102185
elif (action == "update" ):
103-
jsonVar="CR_UPDATE_FULL_JSON"
104-
jsonFileName="/codefresh/volume/servicenow-cr-update.json"
186+
exportVariable("CR_UPDATE_FULL_JSON", "/codefresh/volume/servicenow-cr-update.json")
187+
exportJson("servicenow-cr-update.json", FULL_JSON)
105188
else:
106189
print("ERROR: action unknown. Should not be here. Error should have been caught earlier")
107-
if os.path.exists(env_file_path):
108-
env_file = open(env_file_path, "a")
109-
env_file.write(f"{jsonVar}=/codefresh/volume/servicenow-cr-close.json\n")
110-
env_file.write(f"CR_NUMBER={CR_NUMBER}\n")
111-
env_file.write(f"CR_SYSID={CR_SYSID}\n")
112190

113-
env_file.close()
191+
exportVariable("CR_NUMBER", CR_NUMBER)
192+
exportVariable("CR_SYSID", CR_SYSID)
114193

115-
json_file=open("/codefresh/volume/servicenow-cr-close.json", "w")
116-
json_file.write(FULL_JSON)
117-
json_file.close()
118194

119195
# Call SNow REST API to close a CR
120196
# Fields required are pasted in the data
@@ -196,6 +272,14 @@ def checkToken(token):
196272
logging.error("FATAL: TOKEN is not defined.")
197273
sys.exit(1)
198274

275+
def checkUser(username):
276+
logging.debug("Entering checkUser: ")
277+
logging.debug(" CR_USER: %s" % (username))
278+
279+
if ( username == None ):
280+
logging.error("FATAL: CR_USER is not defined.")
281+
sys.exit(1)
282+
199283
def checkConflictPolicy(policy):
200284
logging.debug("Entering checkConflictPolicy: ")
201285
logging.debug(" CR_CONFLICT_POLICY: %s" % (policy))
@@ -214,6 +298,7 @@ def main():
214298
PASSWORD = os.getenv('SN_PASSWORD')
215299
INSTANCE = os.getenv('SN_INSTANCE')
216300
DATA = os.getenv('CR_DATA')
301+
STD_NAME = os.getenv('STD_CR_TEMPLATE')
217302
DEBUG = True if os.getenv('DEBUG', "false").lower() == "true" else False
218303
TOKEN = os.getenv('TOKEN')
219304
POLICY = os.getenv('CR_CONFLICT_POLICY')
@@ -230,17 +315,26 @@ def main():
230315
logging.debug(f" DATA: {DATA}")
231316
logging.debug(" SYSID: %s" % (os.getenv('CR_SYSID')))
232317

318+
checkUser(USER)
233319

234320
if ACTION == "createcr":
235321
# Used only later in the callback but eant to check for error early
236322
checkToken(TOKEN)
237323
checkConflictPolicy(POLICY)
238324

239-
createChangeRequest(user=USER,
240-
password=PASSWORD,
241-
baseUrl=getBaseUrl(instance=INSTANCE),
242-
data=DATA
243-
)
325+
if STD_NAME:
326+
cr_number=createStandardChangeRequest(user=USER,
327+
standardName=STD_NAME,
328+
password=PASSWORD,
329+
baseUrl=getBaseUrl(instance=INSTANCE),
330+
data=DATA
331+
)
332+
else:
333+
createChangeRequest(user=USER,
334+
password=PASSWORD,
335+
baseUrl=getBaseUrl(instance=INSTANCE),
336+
data=DATA
337+
)
244338
elif ACTION == "callback":
245339
callback(user=USER,
246340
password=PASSWORD,

incubating/service-now/step.yaml

+11-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ kind: step-type
22
version: '1.0'
33
metadata:
44
name: service-now
5-
version: 1.2.5
5+
version: 1.4.0
66
isPublic: true
77
description: Integration with ServiceNow Change Management
88
sources:
@@ -86,7 +86,7 @@ spec:
8686
},
8787
"SN_IMAGE_VERSION": {
8888
"type": "string",
89-
"default": "1.2.5",
89+
"default": "1.4.0",
9090
"description": "Version of the ServiceNow image to use, Docker image tag."
9191
},
9292
"SN_INSTANCE": {
@@ -114,6 +114,10 @@ spec:
114114
"type": "string",
115115
"description": "The body to create the CR. Need to include all the fields required for your Change Management implementation."
116116
},
117+
"STD_CR_TEMPLATE": {
118+
"type": "string",
119+
"description": "name of a Standard Change template. Using this parameter will open a Standard Change (pre-approved) instead of a normal one."
120+
},
117121
"CR_CONFLICT_POLICY": {
118122
"type": "string",
119123
"description": "Policy to exectute in case of schedule conflict. Accepted values are ignore (no check is done), wait (pipeline will wait until the conflict is resolved) or reject ServiceNow flow returns a deny answer",
@@ -209,15 +213,18 @@ spec:
209213
codefresh create annotation workflow ${{CF_BUILD_ID}} CR_NUMBER=${{CR_NUMBER}}
210214
codefresh create annotation workflow ${{CF_BUILD_ID}} CR_SYSID=${{CR_SYSID}}
211215
cf_export annotation_CF_OUTPUT_URL="[[.Arguments.SN_INSTANCE]]/nav_to.do?uri=%2Fchange_request.do%3Fsys_id%3D$CR_SYSID"
216+
217+
[[ if eq .Arguments.STD_CR_TEMPLATE "" ]]
212218
callback:
213219
name: invoke scripted REST API to have ServiceNow callback Codefresh when CR is approved/rejected
214220
title: ServiceNow callback setup
215221
image: '[[.Arguments.SN_IMAGE]]:[[.Arguments.SN_IMAGE_VERSION]]'
216222
environment:
217-
[[ range $key, $val := .Arguments ]]
223+
[[ range $key, $val := .Arguments ]]
218224
- '[[ $key ]]=[[ $val ]]'
219-
[[- end ]]
225+
[[- end ]]
220226
- ACTION=callback
227+
[[ end ]]
221228
[[ end ]]
222229
delimiters:
223230
left: '[['

0 commit comments

Comments
 (0)