diff --git a/.github/workflows/servicenow.py b/.github/workflows/servicenow.py new file mode 100644 index 0000000000..b91436e183 --- /dev/null +++ b/.github/workflows/servicenow.py @@ -0,0 +1,178 @@ +import requests +import time +import datetime +import timedelta +import json +import os +import sys + +def find_string_in_json(json_data, target_string): + """ + Finds a target string in a JSON object. + + Args: + json_data (dict or list): The JSON data to search. + target_string (str): The string to find. + + Returns: + bool: True if the string is found, False otherwise. + """ + + if isinstance(json_data, dict): + for key, value in json_data.items(): + if isinstance(value, str) and target_string in value: + return True + elif isinstance(value, (dict, list)): + if find_string_in_json(value, target_string): + return True + elif isinstance(json_data, list): + for item in json_data: + if isinstance(item, str) and target_string in item: + return True + elif isinstance(item, (dict, list)): + if find_string_in_json(item, target_string): + return True + + return False + +# Execute Script logic: +# python3 servicenow.py +if __name__ == "__main__": + + print("Starting CMR Action...") + + print("Setting Planned Maintenance Time Windows for CMR...") + start_time = (datetime.datetime.now() + datetime.timedelta(seconds = 10)).timestamp() + end_time = (datetime.datetime.now() + datetime.timedelta(minutes = 10)).timestamp() + + print("Set Release Summary for CMR...") + release_title = os.environ['PR_TITLE'] + release_details = os.environ['PR_BODY'] + pr_num = os.environ['PR_NUMBER'] + pr_created = os.environ['PR_CREATED_AT'] + pr_merged = os.environ['PR_MERGED_AT'] + release_summary = f"Release_Details: {release_details} \n\nPull Request Number: {pr_num} \nPull Request Created At: {pr_created} \nPull Request Merged At: {pr_merged}" + + print("Getting IMS Token") + ims_url = 'https://ims-na1.adobelogin.com/ims/token' + headers = {"Content-Type":"multipart/form-data"} + data = { + 'client_id': os.environ['IMSACCESS_CLIENT_ID'], + 'client_secret': os.environ['IMSACCESS_CLIENT_SECRET'], + 'grant_type': "authorization_code", + 'code': os.environ['IMSACCESS_AUTH_CODE'] + } + response = requests.post(ims_url, data=data) + jsonParse = json.loads(response.text) + + if response.status_code != 200: + print("POST failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + elif find_string_in_json(jsonParse, "error"): + print("IMS token request failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + else: + print("IMS token request was successful") + token = jsonParse["access_token"] + + print("Create CMR in ServiceNow...") + + servicenow_cmr_url = 'https://ipaasapi.adobe-services.com/change_management/changes' + headers = { + "Accept":"application/json", + "Authorization":token, + "Content-Type":"application/json", + "api_key":os.environ['IPAAS_KEY'] + } + data = { + "title":release_title, + "description":release_summary, + "instanceIds": [ 537445 ], + "plannedStartDate": start_time, + "plannedEndDate": end_time, + "coordinator": "narcis@adobe.com", + "customerImpact": "No Impact", + "changeReason": [ "New Features", "Bug Fixes", "Enhancement", "Maintenance", "Security" ], + "preProductionTestingType": [ "End-to-End", "Functional", "Integrations", "QA", "Regression", "UAT", "Unit Test" ], + "backoutPlanType": "Roll back", + "approvedBy": [ "casalino@adobe.com", "jmichnow@adobe.com", "mauchley@adobe.com", "bbalakrishna@adobe.com", "tuscany@adobe.com", "brahmbha@adobe.com" ], + "testPlan": "Test plan is documented in the PR link in the Milo repository above. See the PR's merge checks to see Unit and Nala testing.", + "implementationPlan": "The change will be released as part of the continuous deployment of Milo's production branch, i.e., \"main\"", + "backoutPlan": "Revert merge to the Milo production branch by creating a revert commit.", "testResults": "Changes are tested and validated successfully in staging environment. Please see the link of the PR in the description for the test results and/or the \"#nala-test-results\" slack channel." + } + response = requests.post(servicenow_cmr_url, headers=headers, json=data) + jsonParse = json.loads(response.text) + + if response.status_code != 200: + print("POST failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + elif find_string_in_json(jsonParse, "error"): + print("CMR creation failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + else: + print("CMR creation was successful") + transaction_id = jsonParse["id"] + + print("Waiting for Transaction from Queue to ServiceNow then Retrieve CMR ID...") + + servicenow_get_cmr_url = f'https://ipaasapi.adobe-services.com/change_management/transactions/{transaction_id}' + headers = { + "Accept":"application/json", + "Authorization":token, + "api_key":os.environ['IPAAS_KEY'] + } + + # Wait 10 seconds to provide time for the transaction to exit the queue and be saved into ServiceNow as a CMR record. + time.sleep(10) + response = requests.get(servicenow_get_cmr_url, headers=headers) + jsonParse = json.loads(response.text) + + if response.status_code != 200: + print("GET failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + elif find_string_in_json(jsonParse, "error"): + print("CMR ID retrieval failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + else: + print("CMR ID retrieval was successful") + cmr_id = jsonParse["result"]["changeId"] + + print("Setting Actual Maintenance Time Windows for CMR...") + actual_start_time = (datetime.datetime.now() - datetime.timedelta(seconds = 10)).timestamp() + actual_end_time = datetime.datetime.now().timestamp() + + print("Closing CMR in ServiceNow...") + + headers = { + "Accept":"application/json", + "Authorization":token, + "Content-Type":"application/json", + "api_key":os.environ['IPAAS_KEY'] + } + data = { + "id": transaction_id, + "actualStartDate": actual_start_time, + "actualEndDate": actual_end_time, + "state": "Closed", + "closeCode": "Successful", + "notes": "The change request is closed as the change was released successfully" + } + response = requests.post(servicenow_cmr_url, headers=headers, json=data) + jsonParse = json.loads(response.text) + + if response.status_code != 200: + print("POST failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + elif find_string_in_json(jsonParse, "error"): + print("CMR closure failed with response code: ", response.status_code) + print(response.text) + sys.exit(1) + else: + print("CMR closure was successful") diff --git a/.github/workflows/servicenow.yaml b/.github/workflows/servicenow.yaml new file mode 100644 index 0000000000..0ca4f8724d --- /dev/null +++ b/.github/workflows/servicenow.yaml @@ -0,0 +1,44 @@ +# This workflow will install Python dependencies, run CMR creation in ServiceNow +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Create CMR in ServiceNow + +on: + pull_request: + types: + - closed + branches: + - main + +permissions: + contents: read + +env: + IMSACCESS_CLIENT_ID: ${{ secrets.IMSACCESS_CLIENT_ID }} + IMSACCESS_CLIENT_SECRET: ${{ secrets.IMSACCESS_CLIENT_SECRET_PROD }} + IMSACCESS_AUTH_CODE: ${{ secrets.IMSACCESS_AUTH_CODE_PROD }} + IPAAS_KEY: ${{ secrets.IPAAS_KEY_PROD }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_CREATED_AT: ${{ github.event.pull_request.created_at }} + PR_MERGED_AT: ${{ github.event.pull_request.merged_at }} + +jobs: + build: + # Only run this workflow on pull requests that have merged and not manually closed by user + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.x, latest minor release + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip requests timedelta + - name: Execute script for creating and closing CMR + run: | + python ./.github/workflows/servicenow.py diff --git a/.github/workflows/skms.yaml b/.github/workflows/skms.yaml deleted file mode 100644 index 269cfe8969..0000000000 --- a/.github/workflows/skms.yaml +++ /dev/null @@ -1,130 +0,0 @@ -name: Create CMR in SKMS - -on: - pull_request: - types: - - closed - branches: - - main - -jobs: - create-cmr: - # Only run this workflow on pull requests that have merged and not manually closed by user - if: github.event.pull_request.merged == true - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - # Runs a single command using the runners shell for shell validation - - name: Validate Shell - run: echo Starting CMR Action... - - - name: Check Dependencies - run: | - if ! type "jq" >/dev/null; then - echo "jq is required but not installed" - exit 1 - fi - - if ! type "curl" >/dev/null; then - echo "curl is required but not installed" - exit 1 - fi - - echo "Dependencies check was successful" - - - name: Set Maintenance Time Windows for CMR - run: | - echo "start_time=$(date -d "+60 minutes" '+%Y-%m-%d %H:%M')" >> $GITHUB_ENV - echo "end_time=$(date -d "+90 minutes" '+%Y-%m-%d %H:%M')" >> $GITHUB_ENV - - - name: Set Release Summary for CMR - run: | - function sanitizeStr() { - local str=$1 - - if [ -z "$str" ]; then - echo "First parameter missing, must be a valid string" - return 1 - fi - - str="${str//\"/""}" - str="${str//\'/""}" - str="${str//(/"["}" - str="${str//)/"]"}" - str="${str//$'\r'/"\r"}" - str="${str//$'\n'/"\n"}" - str="${str//$'\t'/"\t"}" - str="${str//\\//}" - str="${str////"\/"}" - - echo "$str" - } - - release_title=$(sanitizeStr "${{ github.event.pull_request.title }}") - release_details=$(sanitizeStr "${{ github.event.pull_request.body }}") - release=Release_Title--"${release_title}"--Release_Details--"${release_details}"--Pull_Request_Number--"${{ github.event.pull_request.number }}"--Pull_Request_Created_At--"${{ github.event.pull_request.created_at }}"--Pull_Request_Merged_At--"${{ github.event.pull_request.merged_at }}" - - echo "release_summary<> $GITHUB_ENV - echo "$release" >> $GITHUB_ENV - echo "RS_EOF" >> $GITHUB_ENV - - - name: Create CMR in SKMS - run: | - DEFAULT_SKMS_URL='api.skms.adobe.com' - - function skms_request() { - local username=$1 - local passkey=$2 - local object=$3 - local method=$4 - local method_params=$5 - local url=$6 - - if [ -z "$username" ]; then - echo "First parameter missing, must be SKMS username" - return 1 - fi - - if [ -z "$passkey" ]; then - echo "Second parameter missing, must be SKMS passkey" - return 1 - fi - - if [ -z "$object" ]; then - echo "Third parameter missing, must be an SKMS dao object" - return 1 - fi - - if [ -z "$method" ]; then - echo "Fourth parameter missing, must be SKMS dao method" - return 1 - fi - - if [ -z "$method_params" ]; then - method_params='{}' - fi - - if [ -z "$url" ]; then - url=$DEFAULT_SKMS_URL - fi - - local params="{\"_username\":\"${username}\",\"_passkey\":\"${passkey}\",\"_object\":\"${object}\",\"_method\": \"${method}\"}" - params=$(echo "$params $method_params" | jq -s add) - - local response=$(curl --write-out "%{http_code}" --silent --output response.txt https://${url}/web_api --data-urlencode "_parameters=${params}") - - if [ $response != "200" ]; then - echo "CURL call returned HTTP status code: $response" - exit 1 - elif grep -q "\"status\":\"error\"" response.txt; then - echo "CMR creation failed with response: " - cat response.txt - exit 1 - else - echo "CMR creation was successful" - fi - } - - skms_request '${{ secrets.SKMS_USER }}' '${{ secrets.SKMS_PASS }}' CmrDao createCmrFromPreapprovedChangeModel '{"change_executor":"${{ secrets.SKMS_CHANGE_EXECUTOR }}","maintenance_window_end_time":"${{ env.end_time }}","maintenance_window_start_time":"${{ env.start_time }}","preapproved_change_model_id":"${{ secrets.SKMS_CHANGE_MODEL_ID }}","summary":"${{ env.release_summary }}"}'