Skip to content

Lr snow std change #719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions incubating/service-now/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v1.4.0 (Mar 28, 2025)
### Added
* ability to create a Standard Change passing the template name
in the `STD_CR_TEMPLATE` variable.

## [1.2.5] - 2025-01-02

### Fixed
Expand Down
13 changes: 7 additions & 6 deletions incubating/service-now/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ Example `codefresh.yml` build is below with required Arguments in place.
| CR_ACTION | createCR | string | no | createCR, closeCR, updateCR | the operation to execute |
| CR_CONFLICT_POLICY | ignore | string | no | ignore, wait, reject | What do when a schedule conflict arises |
| CR_DATA | N/A | JSON string | no | JSON block | the JSON block to pass when opening, updating or closing a CR |
| STD_CR_TEMPLATE | | string | no | | the name of a Standard template |
| 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 |
| CR_CLOSE_CODE | successful | string | no | sucessful or any value accepted by the close_code field |
| CR_CLOSE_NOTES | N/A | string | no | Any string accepted for the close_notes field |


### codefresh.yml
### codefresh.yaml

Codefresh build step to execute AWS CDK commands

Expand Down Expand Up @@ -62,7 +63,7 @@ steps:
echo END_DATE=\"$END_DATE\" >> ${{CF_VOLUME_PATH}}/env_vars_to_export

createCR:
type: service-now
type: service-now:1.4.0
title: Create Service Now Change Request
stage: deploy
arguments:
Expand All @@ -73,7 +74,7 @@ steps:
TOKEN: ${{CF_TOKEN}}
CR_CONFLICT_POLICY: reject
CR_DATA: >-
{"short_description": "Globex deployment to QA",
{"short_description": "Globex deployment to QA",
"description": "Change for build ${{CF_BUILD_ID}}.\nThis change was created by the Codefresh plugin",
"justification": "I do not need a justification\nMy app is awesome",
"cmdb_ci":"tomcat",
Expand All @@ -91,7 +92,7 @@ steps:
modifyCR:
stage: deploy
title: "Modify the implementation plan"
type: service-now
type: service-now:1.4.0
fail_fast: false
arguments:
CR_ACTION: updateCR
Expand Down Expand Up @@ -120,7 +121,7 @@ steps:
modifyTestPlan:
stage: test
title: "Modify the test plan"
type: service-now
type: service-now:1.4.0
fail_fast: false
arguments:
CR_ACTION: updateCR
Expand All @@ -131,7 +132,7 @@ steps:
CR_DATA: '{"test_plan":"The testing suit has passed."}'

closeCR:
type: service-now
type: service-now:1.4.0
title: Close Service Now Change Request
stage: post
arguments:
Expand Down
150 changes: 122 additions & 28 deletions incubating/service-now/lib/snow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@
import json
import requests
import logging
import urllib.parse

API_NAMESPACE=409723
env_file_path = "/meta/env_vars_to_export"

def exportVariable(name, value):
if os.path.exists(env_file_path):
file=open(env_file_path, "a")
else:
file=open("/tmp/env_vars_to_export", "a")
file.write(f"{name}={value}\n")
file.close()

def exportJson(name, json):
if os.path.exists(env_file_path):
json_file = open("/codefresh/volume/%s" %(name), "a")
else:
json_file = open("/tmp/%s" % (name), "a")
json_file.write(json)
json_file.close()

def getBaseUrl(instance):
baseUrl = "%s/api" %(instance);
logging.debug("baseUrl: " + baseUrl)
Expand Down Expand Up @@ -41,16 +58,9 @@ def processCreateChangeRequestResponse(response):
logging.info(f" Change Request sys_id: {CR_SYSID}")
logging.debug( " Change Request full answer:\n" + FULL_JSON)

if os.path.exists(env_file_path):
env_file = open(env_file_path, "a")
env_file.write(f"CR_NUMBER={CR_NUMBER}\n")
env_file.write(f"CR_SYSID={CR_SYSID}\n")
env_file.write("CR_FULL_JSON=/codefresh/volume/servicenow-cr.json\n")
env_file.close()

json_file=open("/codefresh/volume/servicenow-cr.json", "w")
json_file.write(FULL_JSON)
json_file.close()
exportVariable("CR_NUMBER", CR_NUMBER)
exportVariable("CR_SYSID", CR_SYSID)
exportVariable("CR_CREATE_JSON", FULL_JSON)

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

def processSearchStandardTemplateResponse(name, response):
logging.info("Processing answer from Standard Template search")
logging.debug("Template search returned code %s" % (response.status_code))
if (response.status_code != 200 and response.status_code != 201):
logging.critical("Standard Change Template for '%s' errored out with code %s", name, response.status_code)
logging.critical("%s" + response.text)
sys.exit(response.status_code)
data=response.json()
logging.debug("Full JSON answer: %s", data)

if len(data["result"]) ==0 :
logging.critical("Standard Change Template '%s' was not found", name)
sys.exit(1)

logging.info("Standard template search successful")
STD_SYSID=data["result"][0]["sys_id"]
return STD_SYSID

def processCreateStandardChangeRequestResponse(response):
logging.info("Processing answer from standard CR creation REST call")
logging.debug("Change Request returned code %s" % (response.status_code))
if (response.status_code != 200 and response.status_code != 201):
logging.critical("Change Request creation failed with code %s", response.status_code)
logging.critical("%s", response.text)
sys.exit(response.status_code)

logging.info("Change Request creation successful")
data=response.json()
FULL_JSON=json.dumps(data, indent=2)
CR_NUMBER=data["result"]["number"]["value"]
CR_SYSID=data["result"]["sys_id"]["value"]
exportVariable("CR_NUMBER", CR_NUMBER)
exportVariable("CR_SYSID", CR_SYSID)
exportVariable("CR_CREATE_JSON", FULL_JSON)
return CR_NUMBER

# Call SNow REST API to create a new Standard Change Request
# Fields required are pasted in the data
def createStandardChangeRequest(user, password, baseUrl, data, standardName):
logging.info("Creating a new Standard Change Request using '%s' template", standardName)
encodedName=urllib.parse.quote_plus(standardName)

url="%s/now/table/std_change_record_producer?sysparm_query=sys_name=%s" % (baseUrl, encodedName)

logging.debug("Standard Change URL %s:",url)
resp=requests.get(url,
headers = {"content-type":"application/json"},
auth=(user, password))
sysid=processSearchStandardTemplateResponse(name=standardName, response=resp)
logging.info("Template found: %s", sysid)

if (bool(data)):
crBody=json.loads(data)
logging.debug("Data: %s", data)
else:
crBody= {}
logging.debug(" Data: None")
crBody["cf_build_id"] = os.getenv('CF_BUILD_ID')


url="%s/sn_chg_rest/change/standard/%s" % (baseUrl, sysid)

logging.debug("URL %s:",url)
logging.debug("User: %s", user)
logging.debug("Body: %s", crBody)

resp=requests.post(url,
json = crBody,
headers = {"content-type":"application/json"},
auth=(user, password))
return processCreateStandardChangeRequestResponse(response=resp)


def processModifyChangeRequestResponse(response, action):

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

if (action == "close" ):
jsonVar="CR_CLOSE_FULL_JSON"
jsonFileName="/codefresh/volume/servicenow-cr-close.json"
exportVariable("CR_CLOSE_FULL_JSON", "/codefresh/volume/servicenow-cr-close.json")
exportJson("servicenow-cr-close.json", FULL_JSON)
elif (action == "update" ):
jsonVar="CR_UPDATE_FULL_JSON"
jsonFileName="/codefresh/volume/servicenow-cr-update.json"
exportVariable("CR_UPDATE_FULL_JSON", "/codefresh/volume/servicenow-cr-update.json")
exportJson("servicenow-cr-update.json", FULL_JSON)
else:
print("ERROR: action unknown. Should not be here. Error should have been caught earlier")
if os.path.exists(env_file_path):
env_file = open(env_file_path, "a")
env_file.write(f"{jsonVar}=/codefresh/volume/servicenow-cr-close.json\n")
env_file.write(f"CR_NUMBER={CR_NUMBER}\n")
env_file.write(f"CR_SYSID={CR_SYSID}\n")

env_file.close()
exportVariable("CR_NUMBER", CR_NUMBER)
exportVariable("CR_SYSID", CR_SYSID)

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

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

def checkUser(username):
logging.debug("Entering checkUser: ")
logging.debug(" CR_USER: %s" % (username))

if ( username == None ):
logging.error("FATAL: CR_USER is not defined.")
sys.exit(1)

def checkConflictPolicy(policy):
logging.debug("Entering checkConflictPolicy: ")
logging.debug(" CR_CONFLICT_POLICY: %s" % (policy))
Expand All @@ -214,6 +298,7 @@ def main():
PASSWORD = os.getenv('SN_PASSWORD')
INSTANCE = os.getenv('SN_INSTANCE')
DATA = os.getenv('CR_DATA')
STD_NAME = os.getenv('STD_CR_TEMPLATE')
DEBUG = True if os.getenv('DEBUG', "false").lower() == "true" else False
TOKEN = os.getenv('TOKEN')
POLICY = os.getenv('CR_CONFLICT_POLICY')
Expand All @@ -230,17 +315,26 @@ def main():
logging.debug(f" DATA: {DATA}")
logging.debug(" SYSID: %s" % (os.getenv('CR_SYSID')))

checkUser(USER)

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

createChangeRequest(user=USER,
password=PASSWORD,
baseUrl=getBaseUrl(instance=INSTANCE),
data=DATA
)
if STD_NAME:
cr_number=createStandardChangeRequest(user=USER,
standardName=STD_NAME,
password=PASSWORD,
baseUrl=getBaseUrl(instance=INSTANCE),
data=DATA
)
else:
createChangeRequest(user=USER,
password=PASSWORD,
baseUrl=getBaseUrl(instance=INSTANCE),
data=DATA
)
elif ACTION == "callback":
callback(user=USER,
password=PASSWORD,
Expand Down
15 changes: 11 additions & 4 deletions incubating/service-now/step.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ kind: step-type
version: '1.0'
metadata:
name: service-now
version: 1.2.5
version: 1.4.0
isPublic: true
description: Integration with ServiceNow Change Management
sources:
Expand Down Expand Up @@ -86,7 +86,7 @@ spec:
},
"SN_IMAGE_VERSION": {
"type": "string",
"default": "1.2.5",
"default": "1.4.0",
"description": "Version of the ServiceNow image to use, Docker image tag."
},
"SN_INSTANCE": {
Expand Down Expand Up @@ -114,6 +114,10 @@ spec:
"type": "string",
"description": "The body to create the CR. Need to include all the fields required for your Change Management implementation."
},
"STD_CR_TEMPLATE": {
"type": "string",
"description": "name of a Standard Change template. Using this parameter will open a Standard Change (pre-approved) instead of a normal one."
},
"CR_CONFLICT_POLICY": {
"type": "string",
"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",
Expand Down Expand Up @@ -209,15 +213,18 @@ spec:
codefresh create annotation workflow ${{CF_BUILD_ID}} CR_NUMBER=${{CR_NUMBER}}
codefresh create annotation workflow ${{CF_BUILD_ID}} CR_SYSID=${{CR_SYSID}}
cf_export annotation_CF_OUTPUT_URL="[[.Arguments.SN_INSTANCE]]/nav_to.do?uri=%2Fchange_request.do%3Fsys_id%3D$CR_SYSID"

[[ if eq .Arguments.STD_CR_TEMPLATE "" ]]
callback:
name: invoke scripted REST API to have ServiceNow callback Codefresh when CR is approved/rejected
title: ServiceNow callback setup
image: '[[.Arguments.SN_IMAGE]]:[[.Arguments.SN_IMAGE_VERSION]]'
environment:
[[ range $key, $val := .Arguments ]]
[[ range $key, $val := .Arguments ]]
- '[[ $key ]]=[[ $val ]]'
[[- end ]]
[[- end ]]
- ACTION=callback
[[ end ]]
[[ end ]]
delimiters:
left: '[['
Expand Down
Loading