diff --git a/incubating/.DS_Store b/incubating/.DS_Store deleted file mode 100644 index 4e0d5217f..000000000 Binary files a/incubating/.DS_Store and /dev/null differ diff --git a/incubating/service-now/images/servicenow-2.svg b/incubating/service-now/images/servicenow-2.svg new file mode 100644 index 000000000..7913d06cf --- /dev/null +++ b/incubating/service-now/images/servicenow-2.svg @@ -0,0 +1,14 @@ + +ServiceNow logoA cloud computing and enterprise software provider based in Santa Clara, California, United Statesimage/svg+xml + + + + + + + + + + + + diff --git a/incubating/service-now/lib/snow.py b/incubating/service-now/lib/snow.py index 3d82274ab..a98affcc5 100644 --- a/incubating/service-now/lib/snow.py +++ b/incubating/service-now/lib/snow.py @@ -3,7 +3,6 @@ import json import requests -DEBUG = True API_NAMESPACE=409723 env_file_path = "/meta/env_vars_to_export" @@ -19,33 +18,35 @@ def processCallbackResponse(response): if (response.status_code != 200 and response.status_code != 201): print("Callback creation failed with code %s" % (response.status_code)) print("Error: " + response.text) - return response.status_code + sys.exit(response.status_code) print("Callback creation successful") -def processChangeRequestResponse(response): - - print("Processing answer from CR creation REST call") - print("Change Request returned code %s" % (response.status_code)) +def processCreateChangeRequestResponse(response): + if DEBUG: + print("Processing answer from CR creation REST call") + print(" Change Request returned code %s" % (response.status_code)) if (response.status_code != 200 and response.status_code != 201): - print("Change Request creation failed with code %s" % (response.status_code)) - print("Error: " + response.text) - return response.status_code + print(" Change Request creation failed with code %s" % (response.status_code)) + print(" ERROR: " + response.text) + sys.exit(response.status_code) - print("Change Request creation successful") + print(" Change Request creation successful") data=response.json() # json.loads(response.text) CR_NUMBER=data["result"]["number"] CR_SYSID=data["result"]["sys_id"] FULL_JSON=json.dumps(data, indent=2) - print(f"Change Request Number: {CR_NUMBER}") - print(f"Change Request sys_id: {CR_SYSID}") - print("Change Request full answer:\n" + FULL_JSON) + + print(f" Change Request Number: {CR_NUMBER}") + print(f" Change Request sys_id: {CR_SYSID}") + if DEBUG: + print( " 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_SYS_ID={CR_SYSID}\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() @@ -55,35 +56,107 @@ def processChangeRequestResponse(response): # # Call SNow REST API to create a new Change Request -# Fields required are past in the data -def createChangeRequest(user, password, baseUrl, title, data, description): +# Fields required are pasted in the data +def createChangeRequest(user, password, baseUrl, data): if DEBUG: print("Entering createChangeRequest:") - print("Body: " + data) if (bool(data)): crBody=json.loads(data) + if DEBUG: + print("Data: " + data) else: crBody= {} - + if DEBUG: + print(" Data: None") crBody["cf_build_id"] = os.getenv('CF_BUILD_ID') url="%s/now/table/change_request" % (baseUrl) if DEBUG: - print(f"Entering createChangeRequest:") - print(f"URL: {url}") - print(f"User: {user}") - print(f"Body: {crBody}") + print(f" URL: {url}") + print(f" User: {user}") + print(f" Body: {crBody}") resp=requests.post(url, json = crBody, headers = {"content-type":"application/json"}, auth=(user, password)) - processChangeRequestResponse(response=resp) + processCreateChangeRequestResponse(response=resp) + +def processModifyChangeRequestResponse(response, action): + + if DEBUG: + print("Processing answer from CR %s REST call" %(action)) + print(" %s Change Request returned code %s" % (action,response.status_code)) + if (response.status_code != 200 and response.status_code != 201): + print(" %s Change Request creation failed with code %s" % (action, response.status_code)) + print(" ERROR: " + response.text) + sys.exit(response.status_code) + + print(" %s Change Request successful" %(action)) + data=response.json() # json.loads(response.text) + FULL_JSON=json.dumps(data, indent=2) + if (action == "close" ): + jsonVar="CR_CLOSE_FULL_JSON" + jsonFileName="/codefresh/volume/servicenow-cr-close.json" + elif (action == "update" ): + jsonVar="CR_UPDATE_FULL_JSON" + jsonFileName="/codefresh/volume/servicenow-cr-update.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.close() + + 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 +def closeChangeRequest(user, password, baseUrl, sysid, code, notes, data): + if DEBUG: + print("Entering closeChangeRequest:") + print(f"DATA: {data}") + if (bool(data)): + crBody=json.loads(data) + else: + crBody= {} + crBody["state"] = 3 + crBody["close_code"] = code + crBody["close_notes"] = notes + url="%s/now/table/change_request/%s" % (baseUrl, sysid) + resp=requests.patch(url, + json = crBody, + headers = {"content-type":"application/json"}, + auth=(user, password)) + processModifyChangeRequestResponse(response=resp, action="close") + +# Call SNow REST API to update a CR +# Fields required are pasted in the data +def updateChangeRequest(user, password, baseUrl, sysid, data): + if DEBUG: + print("Entering updateChangeRequest:") + print(f"DATA: {data}") + if (bool(data)): + crBody=json.loads(data) + else: + crBody= {} + print("WARNING: CR_DATA is empty. What are you updating exactly?") + + url="%s/now/table/change_request/%s" % (baseUrl, sysid) + if DEBUG: + print(f" update CR URL: {url}") + resp=requests.patch(url, + json = crBody, + headers = {"content-type":"application/json"}, + auth=(user, password)) + processModifyChangeRequestResponse(response=resp, action="update") # Use rest API to call scripted REST API to start a flow that will wait for CR # to be approved or rejected, then callback Codefreh to approve/deny pipeline # @@ -112,26 +185,38 @@ def callback(user, password, baseUrl, number, cf_build_id, token): auth=(user, password)) processCallbackResponse(response=resp) +def checkSysid(sysid): + if DEBUG: + print("Entering checkSysid: ") + print(" CR_SYSID: %s" % (sysid)) + + if ( sysid == None ): + print("FATAL: CR_SYSID is not defined.") + sys.exit(1) + + def main(): global DEBUG - ACTION = os.getenv('action', 'createCR').lower() + ACTION = os.getenv('ACTION').lower() USER = os.getenv('SN_USER') PASSWORD = os.getenv('SN_PASSWORD') INSTANCE = os.getenv('SN_INSTANCE') - DATA = os.getenv('data') + DATA = os.getenv('CR_DATA') + DEBUG = True if os.getenv('DEBUG', "false").lower() == "true" else False - #DEBUG = True if os.getenv('debug', "false").lower == "true" else False - TITLE = os.getenv('title', 'Change Request created by Codefresh') - DESCRIPTION = os.getenv('description', '') + if DEBUG: + print("Starting ServiceNow plugin for Codefresh") + print(f" ACTION: {ACTION}") + print(f" DATA: {DATA}") + print(" SYSID: %s" % (os.getenv('CR_SYSID'))) + print("---") if ACTION == "createcr": createChangeRequest(user=USER, password=PASSWORD, baseUrl=getBaseUrl(instance=INSTANCE), - title=TITLE, - data=DATA, - description=DESCRIPTION) + data=DATA) elif ACTION == "callback": callback(user=USER, password=PASSWORD, @@ -140,9 +225,35 @@ def main(): token=os.getenv('TOKEN'), cf_build_id=os.getenv('CF_BUILD_ID') ) + elif ACTION == "closecr": + CR_SYSID= os.getenv('CR_SYSID') + CODE=os.getenv('CR_CLOSE_CODE') + NOTES=os.getenv('CR_CLOSE_NOTES') + checkSysid(CR_SYSID) + closeChangeRequest( + user=USER, + password=PASSWORD, + baseUrl=getBaseUrl(instance=INSTANCE), + sysid=os.getenv('CR_SYSID'), + code=CODE, + notes=NOTES, + data=DATA + ) + elif ACTION == "updatecr": + CR_SYSID= os.getenv('CR_SYSID') + checkSysid(CR_SYSID) + + updateChangeRequest( + user=USER, + password=PASSWORD, + baseUrl=getBaseUrl(instance=INSTANCE), + sysid=CR_SYSID, + data=DATA + ) else: - sys.exit(f"Unknown action: {ACTION}") + printf("FATAL: Unknown action: {ACTION}. Allowed values are createCR, closeCR or updateCR.") + sys.exit(1) if __name__ == "__main__": diff --git a/incubating/service-now/step.yaml b/incubating/service-now/step.yaml index 05ff2f46c..6583738fc 100644 --- a/incubating/service-now/step.yaml +++ b/incubating/service-now/step.yaml @@ -2,7 +2,7 @@ kind: step-type version: '1.0' metadata: name: service-now - version: 1.0.3 + version: 1.1.0 isPublic: true description: Integration with ServiceNow Change Management sources: @@ -17,7 +17,7 @@ metadata: tags: [] icon: type: svg - url: https://raw.githubusercontent.com/codefresh-io/steps/master/incubating/service-now/images/servicenow-1.png + url: https://raw.githubusercontent.com/codefresh-io/steps/master/incubating/service-now/images/servicenow-2.svg background: "#f4f4f4" examples: - description: service-now @@ -27,11 +27,44 @@ metadata: type: service-now stage: deploy arguments: - action: createCR + CR_ACTION: createCR SN_INSTANCE: https://instance.service-now.com SN_USER: ${{SN_USER}} SN_PASSWORD: ${{SN_PASSWORD}} - cr_body: '{"short_description":"This is a Change Request created by Codefresh","description":"long description for Codefresh build ${{CF_BUILD_ID}}","justification":"because our app is awesome"}' + CR_DATA: {"short_description":"This is a Change Request created by Codefresh","description":"long description for Codefresh build ${{CF_BUILD_ID}}","justification":"because our app is awesome"} + wait: + stage: deploy + title: Waiting for the Change Request to be approved + type: pending-approval + timeout: + duration: 48 + timeUnit: hours + finalState: denied + modifyCR: + title: Close the CR + type: service-now + stage: deploy + arguments: + CR_ACTION: closeCR + CR_SYSID: ${{CR_SYSID}}' + SN_INSTANCE: https://instance.service-now.com + SN_USER: ${{SN_USER}} + SN_PASSWORD: ${{SN_PASSWORD}} + CR_DATA: {"test_plan":"We ran all the test in the awesome test suite. Test pass rate: ${{TEST_RATE}}"} + closeCR: + title: Close the CR + type: service-now + stage: cleaning + arguments: + CR_ACTION: closeCR + CR_SYSID: ${{CR_SYSID}} + SN_INSTANCE: https://instance.service-now.com + SN_USER: ${{SN_USER}} + SN_PASSWORD: ${{SN_PASSWORD}} + CR_CLOSE_CODE: successful + CR_CLOSE_NOTES: "CR closed by Codefresh build ${{CF_BUILD_ID}}" + CR_DATA: {"work_notes":"Closed automatically by Codefresh build ${{CF_BUILD_ID}}"} + spec: arguments: |- { @@ -43,8 +76,7 @@ spec: "required": [ "SN_INSTANCE", "SN_USER", - "SN_PASSWORD", - "TOKEN" + "SN_PASSWORD" ], "properties": { "SN_INSTANCE": { @@ -63,18 +95,33 @@ spec: "type": "string", "description": "The Codefresh API token to allow ServiceNow to approve/deny the pipeline" }, - "namespace": { + "SN_NAMESPACE": { "type": "string", - "description": "The endpoint to call if using a different one. The system will call $SN_INSTANCE/api/$endpoint", + "description": "The endpoint namespace to call if using a different one. The system will call $SN_INSTANCE/api/$endpoint", "default": "409723" }, - "data": { + "CR_DATA": { "type": "string", "description": "The body to create the CR. Need to include all the fields required for your Change Management implementation." }, - "debug": { + "CR_ACTION": { + "type": "string", + "description": "Action to execute: createCR or closeCR", + "default": "createCR" + }, + "CR_CLOSE_CODE": { "type": "string", - "default": "false", + "description": "Close code for the closeCR action: successful, successful_issues or unsuccessful", + "default": "successful" + }, + "CR_CLOSE_NOTES": { + "type": "string", + "description": "Close notes for the closeCR action: successful, successful_issues or unsuccessful", + "default": "Closed by Codefresh" + }, + "DEBUG": { + "type": "boolean", + "default": false, "description": "a hidden option show more debug info" } } @@ -88,52 +135,70 @@ spec: "patterns": [], "required": [ "CR_NUMBER", - "CR_SYS_ID" + "CR_SYSID" ], "properties": { - "sys_id": { + "CR_SYSID": { "type": "string", - "description": "The sys_id of the CR created" + "description": "The sysid of the CR created" }, - "cr_number": { + "CR_NUMBER": { "type": "string", "description": "The number of the CR created." - }, - "cr_sysid": { - "type": "string", - "description": "The sys_id of the CR created." } } } stepsTemplate: |- - createCR: + [[ if eq .Arguments.CR_ACTION "closeCR" ]] + closeCR: + name: Close a ServiceNow Change Request + title: Use REST API to close the CR + image: quay.io/codefreshplugins/service-now:1.1.0 + environment: + [[ range $key, $val := .Arguments ]] + - '[[ $key ]]=[[ $val ]]' + [[- end ]] + - ACTION=closecr + [[ end ]] + [[ if eq .Arguments.CR_ACTION "updateCR" ]] + updateCR: + name: Update a ServiceNow Change Request + title: Use REST API to update the CR + image: quay.io/codefreshplugins/service-now:1.1.0 + environment: + [[ range $key, $val := .Arguments ]] + - '[[ $key ]]=[[ $val ]]' + [[- end ]] + - ACTION=updatecr + [[ end ]] + [[ if eq .Arguments.CR_ACTION "createCR" ]] + createcr: name: Create a ServiceNow Change Request title: Use REST API to create a CR - image: 'quay.io/codefreshplugins/service-now:1.0.3' + image: quay.io/codefreshplugins/service-now:1.1.0 environment: - - action=createCR [[ range $key, $val := .Arguments ]] - '[[ $key ]]=[[ $val ]]' [[- end ]] - createAnnotation: + - ACTION=createcr + annotation: name: Assign CR_NUMBER as annotation - image: codefresh/cli + image: quay.io/codefresh/cli commands: - | codefresh create annotation workflow ${{CF_BUILD_ID}} CR_NUMBER=${{CR_NUMBER}} - export createAnnotation_CF_OUTPUT_URL='[[.Arguments.SN_INSTANCE]]/nav_to.do?uri=/change_request.do?sys_id=${{CR_SYS_ID}}' - # echo $createAnnotation_CF_OUTPUT_URL - # cf_export createAnnotation_CF_OUTPUT_URL + 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" callback: name: invoke scripted REST API to have ServiceNow callback Codefresh when CR is approved/rejected title: ServiceNow callback setup - image: 'quay.io/codefreshplugins/service-now:1.0.3' - + image: quay.io/codefreshplugins/service-now:1.1.0 environment: - - action=callback [[ range $key, $val := .Arguments ]] - '[[ $key ]]=[[ $val ]]' [[- end ]] + - ACTION=callback + [[ end ]] delimiters: left: '[[' right: ']]'