diff --git a/README.md b/README.md index 6870657..9aeb450 100644 --- a/README.md +++ b/README.md @@ -55,16 +55,19 @@ npm unlink . ### Building Docker Image ```sh -docker build -t servicenowdocker/sndevops:4.0.0 . +docker build -t servicenowdocker/sndevops:5.1.0 . ``` ```sh -docker push servicenowdocker/sndevops:4.0.0 +docker push servicenowdocker/sndevops:5.1.0 ``` ## Integrating with GitLab - -[Example gitlab project](/gitlab-ci.yml) +[Example Gitlab project](/gitlab-ci.yml) +## Integrating with GitHub +[Example Github project](/github-ci.yml) +## Integrating with Harness +[Example Harness project](/harness-ci.yml) ### Env variables @@ -75,6 +78,23 @@ SNOW_TOKEN = SNOW_TOOLID = ``` +### Additonal Env variables +``` +CI_PIPELINE_ID: +CI_API_V4_URL: +CI_JOB_ID: +CI_PROJECT_PATH: +CI_REPOSITORY_NAME: +CI_RUN_ATTEMPT: +CI_PROJECT_TITLE: +``` + +### Optional Env variables +``` +CI_COMMIT_BRANCH: +CI_WORKFLOW_NAME: +``` + **Example with passing all ServiceNow information via commandline** ```yaml @@ -85,7 +105,7 @@ stages: package: stage: package - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - sndevopscli create artifact -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}]' - sndevopscli create package -n "package-name" -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}] @@ -97,7 +117,7 @@ stages: package: stage: package - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - sndevopscli create artifact -u -t --token -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}]' - sndevopscli create package -u -t --token -n "package-mame" -a '[{"name":"artifact-name-$CI_JOB_ID","repositoryName":"artifact-repo-name" ,"version":"1.3.0"}] @@ -121,10 +141,20 @@ stages: ServiceNow DevOps Change: stage: DevOpsChangeApproval - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - sndevopscli create change -p '{"changeStepDetails":{"timeout":3600,"interval":100},"attributes":{"short_description":"Automated Software Deployment","description":"Automated Software Deployment.","assignment_group":"XXXXXXX","implementation_plan":"Software update is tested and results can be found in Test Summaries Tab.","backout_plan":"When software fails in production, the previous software release will be re-deployed.","test_plan":"Testing if the software was successfully deployed or not"}}' + +-p: [optional] +Change Attributes payload in JSON format + +-ctx: [optional] +Additional context parameters in JSON format. These details will be used to build the request for the DevOps Change API + +-w: [optional] +Specify for the pipeline to wait for change creation and approval. By default, the pipeline will be waiting until the change creation and it's approval + changeStepDetails: [optional] It holds the timeout and interval details. @@ -151,9 +181,9 @@ stages: ServiceNow DevOps Sonar Scan Results: stage: DevOpsSonarStage - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - - sndevopscli create sonar -url 'https://sonarcloud.io' -projectKey 'xxxxxxx' + - sndevopscli create sonar -url 'https://sonarcloud.io' -projectKey 'xxxxxxx' -branch 'master' url: [mandatory] This specifies the sonar url. @@ -161,6 +191,37 @@ This specifies the sonar url. projectKey: [mandatory] This specifies the sonar project key. +branch: [optional] +This specifies the branch on which the Sonar scan was executed. By default, it matches the branch for which the build was run. Note, for Harness, the branch option is required if CI_COMMIT_BRANCH is not provided. + +``` + +**Example of Registring Security scan results in ServiceNow via commandline** +```yaml + +This custom step needs to be added at job level to register security scan results in ServiceNow instance. + +stages: + - DevOpsSecurityScanStage + +ServiceNow DevOps Security Scan Results: + stage: DevOpsSecurityScanStage + image: servicenowdocker/sndevops:5.1.0 + script: + - sndevopscli create securityScan -p "{\"pipelineInfo\":{\"buildNumber\":\"${CI_PIPELINE_ID}\",\"pipelineExecutionUrl\":\"${CI_PIPELINE_URL}\" },\"securityResultAttributes\":{ \"scanner\":\"Veracode\",\"applicationName\":\"PetStoreAPI-Github\",\"buildVersion\":\"\",\"securityToolId\":\"\"}}" + + +-p: [mandatory] + It the payload of security result attributes. The payload will have attributes as follows: + buildNumber: CI_PIPELINE_ID (mandatory) + pipelineExecutionUrl: CI_PIPELINE_URL (mandatory) + scanner: Scanning tool and is required e.g. Checkmarx One. + projectName/projectId: Name/Id of your Checkmarx One project and is required. This attribute is applicable only for Checkmarx One. + applicationName: Name of your Veracode application and is required. This attribute is applicable only for Veracode. + buildVersion": Veracode Scan name / build version and is optional. This attribute is applicable only for Veracode. + scanId: Checkmarx One scan id and is optional. This attribute is applicable only for Checkmarx One. + securityToolId: Security tool onboarded in ServiceNow (sys_id of the onboarded security tool) and is optional. + ``` **Example of get change for ServiceNow via commandline** @@ -173,7 +234,7 @@ stages: ServiceNow DevOps Get Change: stage: DevOpsGetChange - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - sndevopscli get change -p "{\"buildNumber\":${CHG_JOB_ID},\"stageName\":\"ServiceNow DevOps Change Step\",\"pipelineName\":\"GitlabDockerGetAndUpdateChange\"}" @@ -209,9 +270,9 @@ stages: ServiceNow DevOps Update Change: stage: DevOpsUpdateChangeStage - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - - sndevopscli update change -n 'CHGXXXXXX' -p "{\"short_description\":\"G Venkata12345 Automated Software Deployment\",\"description\":\"Automated Software Deployment.\",\"assignment_group\":\"XXXXX\",\"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\",\"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\",\"test_plan\":\"Testing if the software was successfully deployed or not\"}" + - sndevopscli update change -n 'CHGXXXXXX' -p "{\"short_description\":\"Automated Software Deployment\",\"description\":\"Automated Software Deployment.\",\"assignment_group\":\"XXXXX\",\"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\",\"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\",\"test_plan\":\"Testing if the software was successfully deployed or not\"}" -n [Not mandatory if we have sndevopschg.json in our pipeline yml]: It stands for changeRequestNumber. The change request number to identify a unique change request. Precedence of choosing changeRequestNumber: @@ -236,9 +297,9 @@ stages: ServiceNow DevOps Change Step: stage: changeapproval - image: servicenowdocker/sndevops:4.0.0 + image: servicenowdocker/sndevops:5.1.0 script: - - sndevopscli create change -p "{\"changeStepDetails\":{\"timeout\":3600,\"interval\":100},\"autoCloseChange\":true,\"attributes\":{\"short_description\":\"G Venkata Automated Software Deployment\",\"description\":\"Automated Software Deployment.\",\"assignment_group\":\"xxxxxxxx\",\"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\",\"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\",\"test_plan\":\"Testing if the software was successfully deployed or not\"}}" + - sndevopscli create change -p "{\"changeStepDetails\":{\"timeout\":3600,\"interval\":100},\"autoCloseChange\":true,\"attributes\":{\"short_description\":\"Automated Software Deployment\",\"description\":\"Automated Software Deployment.\",\"assignment_group\":\"xxxxxxxx\",\"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\",\"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\",\"test_plan\":\"Testing if the software was successfully deployed or not\"}}" autoCloseChange: [optional] : Boolean value diff --git a/github-ci.yml b/github-ci.yml new file mode 100644 index 0000000..6202a56 --- /dev/null +++ b/github-ci.yml @@ -0,0 +1,95 @@ +name: GithubAction + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +permissions: + contents: read +env: + SNOW_URL: ${{ secrets.SNOW_URL }} + SNOW_TOKEN: ${{ secrets.SNOW_TOKEN }} + SNOW_TOOLID: ${{ secrets.SNOW_TOOLID }} + # These are the optional env variables + #CI_PIPELINE_ID: ${{ github.run_id }} + #CI_API_V4_URL: ${{ github.server_url }} + #CI_JOB_ID: ${{ github.run_id }} + #CI_PROJECT_PATH: ${{ github.job }} + #CI_REPOSITORY_NAME: ${{ github.repository }} + #CI_RUN_ATTEMPT: ${{ github.run_attempt }} + #CI_PROJECT_TITLE: ${{ github.repository }}/${{github.workflow}} + #CI_COMMIT_BRANCH: ${{ github.ref_name }} + #CI_WORKFLOW_NAME: ${{ github.workflow }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Set up JDK 17 + run: | + echo 'hello' + + ServiceNowDevOpsChange: + runs-on: ubuntu-latest + needs: build + container: + image: servicenowdocker/sndevops:5.1.0 + env: + CI_JOB_NAME: "ServiceNowDevOpsChange" + + steps: + - name: create change + run: | + sndevopscli create change -p "{\"changeStepDetails\":{\"timeout\":3600,\"interval\":10},\"autoCloseChange\":true,\"attributes\":{\"short_description\":\"G Venkata Automated Software Deployment\",\"description\":\"Automated Software Deployment.\",\"assignment_group\":\"a715cd759f2002002920bde8132e7018\",\"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\",\"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\",\"test_plan\":\"Testing if the software was successfully deployed or not\"}}" + + ServiceNowUpdateDevOpsChange: + runs-on: ubuntu-latest + needs: ServiceNowDevOpsChange + container: + image: servicenowdocker/sndevops:5.1.0 + env: + CI_JOB_NAME: "ServiceNowDevOpsChange" + + steps: + - name: update change + run: | + sndevopscli update change -p "{\"short_description\":\"Updated Automated Software Deployment\",\"description\":\"Automated Software Deployment.\",\"assignment_group\":\"a715cd759f2002002920bde8132e7018\",\"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\",\"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\",\"test_plan\":\"Testing if the software was successfully deployed or not\"}" + + ArtifactAndPackage: + runs-on: ubuntu-latest + needs: build + container: + image: servicenowdocker/sndevops:5.1.0 + env: + CI_JOB_NAME: "ArtifactAndPackage" + steps: + - name: create artifact + run: | + sndevopscli create artifact -a "[{\"name\": \"com:customactiondemo\",\"version\": \"1.${{ github.run_number }}\",\"semanticVersion\": \"1.${{ github.run_number }}.0\",\"repositoryName\": \"${{ github.repository }}\"}]" + sndevopscli create package -n "Test_Package" -a "[{\"name\": \"com:customactiondemo\",\"version\": \"1.${{ github.run_number }}\",\"semanticVersion\": \"1.${{ github.run_number }}.0\",\"repositoryName\": \"${{ github.repository }}\"}]" + + ServiceNowDevOpsSonarScanResults: + name: ServiceNowDevOpsSonarScanResults + needs: ArtifactAndPackage + runs-on: ubuntu-latest + container: + image: servicenowdocker/sndevops:5.1.0 + env: + CI_JOB_NAME: "ServiceNowDevOpsSonarScanResults" + steps: + - name: devops soanr results + run: | + sndevopscli create sonar -url 'https://sonarcloud.io' -projectKey 'xxxxxxx' + + ServiceNowDevOpsSecurityScan: + name: ServiceNowDevOpsSecurityScan + runs-on: ubuntu-latest + needs: build + container: + image: servicenowdocker/sndevops:5.1.0 + steps: + - name: security scan + run: | + sndevopscli create securityScan -p "{ \"pipelineInfo\": { \"runId\": \"${{ github.run_id }}\", \"runNumber\": \"${{ github.run_number }}\", \"runAttempt\": \"${{ github.run_attempt }}\", \"job\": \"ServiceNowDevOpsSecurityScan\", \"workflow\": \"${{ github.workflow }}\", \"repository\": \"${{github.repository}}\" }, \"securityResultAttributes\": { \"scanner\": \"Veracode\", \"applicationName\": \"PetStoreAPI-Github\",\"buildVersion\": \"\", \"securityToolId\": \"\"}}" \ No newline at end of file diff --git a/gitlab-ci.yml b/gitlab-ci.yml index 0b3216a..74da4d5 100644 --- a/gitlab-ci.yml +++ b/gitlab-ci.yml @@ -1,8 +1,9 @@ -image: servicenowdocker/sndevops:4.0.0 +image: servicenowdocker/sndevops:5.1.0 stages: - pre-build - build - sonar + - securityscan - changeapproval - getchange - updatechange @@ -26,6 +27,12 @@ ServiceNow DevOps SonarScan Results: script: - sndevopscli create sonar -url 'https://sonarcloud.io' -projectKey 'xxxxxxx' + +ServiceNow DevOps Security Scan Results: + stage: securityscan + script: + - sndevopscli create securityScan -p "{\"pipelineInfo\":{\"buildNumber\":\"${CI_JOB_ID}\", \"stageName\":\"ServiceNow DevOps Security Scan Results\",\"taskExecutionUrl\":\"${CI_JOB_URL}\" },\"securityResultAttributes\":{ \"scanner\":\"Veracode\",\"applicationName\":\"PetStoreAPI-Github\",\"buildVersion\":\"\",\"securityToolId\":\"\"}}" + ServiceNow DevOps Change Step: stage: changeapproval variables: diff --git a/harness-ci.yml b/harness-ci.yml new file mode 100644 index 0000000..19119a3 --- /dev/null +++ b/harness-ci.yml @@ -0,0 +1,254 @@ +# This is an example pipeline, and your pipeline and container configurations may vary from the setup shown here. +# Refer to the pipeline stages for Security, Software, and Change configurations. +# Using this YAML file directly will not work as it contains several Harness-specific configurations; check the Command section present for container steps. +pipeline: + name: Example pipeline + identifier: Example_pipeline + projectIdentifier: Example_project + orgIdentifier: ExampleOrg + tags: {} + stages: + - stage: + name: Build + identifier: Build + description: "" + type: Custom + spec: + execution: + steps: + - step: + type: ShellScript + name: ShellScript_1 + identifier: ShellScript_1 + spec: + shell: Bash + executionTarget: {} + source: + type: Inline + spec: + script: echo "Building.." + environmentVariables: [] + outputVariables: [] + timeout: 10m + tags: {} + - stage: + name: ServiceNow DevOps Sonar Scan Results + identifier: ServiceNow_DevOps_Sonar_Scan_Results + description: "" + type: Custom + spec: + execution: + steps: + - step: + type: Container + name: Sonar container + identifier: Sonar_container + spec: + connectorRef: docker_connector # harness docker connector + image: servicenowdocker/sndevops:5.1.0 + command: |- + export SNOW_URL="<+variable.SNOW_URL>" + export SNOW_TOOLID="<+variable.SNOW_TOOLID>" + export SNOW_TOKEN="<+variable.SNOW_TOKEN>" + export HARNESS_STAGE_NAME="<+stage.identifier>" + export HARNESS_PIPELINE_NAME="<+org.identifier>/<+project.identifier>/<+pipeline.name>" + export CI_COMMIT_BRANCH="main" + sndevopscli create sonar -url 'https://sonarcloud.io' -projectKey 'SONAR_PROJECT_KEY' -branch "main" + shell: Sh + infrastructure: + type: KubernetesDirect + spec: + connectorRef: KUBE_CONNECTOR # harness kubernates connector + namespace: harness-delegate-ng + resources: + limits: + cpu: "0.5" + memory: 500Mi + annotations: {} + labels: {} + containerSecurityContext: + capabilities: + drop: [] + add: [] + nodeSelector: {} + reports: + type: JUnit + spec: + paths: [] + outputVariables: [] + envVariables: {} + timeout: 1h + tags: {} + - stage: + name: ServiceNow DevOps Security Scan Results + identifier: ServiceNow_DevOps_Security_Scan_Results + description: "" + type: Custom + spec: + execution: + steps: + - step: + type: Container + name: security + identifier: security + spec: + connectorRef: docker_connector # harness docker connector + image: servicenowdocker/sndevops:5.1.0 + command: |- + export SNOW_URL="<+variable.SNOW_URL>" + export SNOW_TOOLID="<+variable.SNOW_TOOLID>" + export SNOW_TOKEN="<+variable.SNOW_TOKEN>" + sndevopscli create securityScan -p \ + "{\"pipelineInfo\":{ + \"buildNumber\":\"<+stage.nodeExecutionId>\", + \"taskExecutionUrl\":\"<+pipeline.executionUrl>?stage=<+stage.nodeExecutionId>\", + \"orchestrationPipeline\":\"<+org.identifier>/<+project.identifier>/<+pipeline.name>\" + }, + \"securityResultAttributes\":{ + \"scanner\":\"Veracode\", + \"applicationName\":\"VeraDemo-10\" + } + }" + shell: Sh + infrastructure: + type: KubernetesDirect + spec: + connectorRef: KUBE_CONNECTOR # harness kubernates connector + namespace: harness-delegate-ng + resources: + limits: + cpu: "0.5" + memory: 500Mi + annotations: {} + labels: {} + containerSecurityContext: + capabilities: + drop: [] + add: [] + nodeSelector: {} + reports: + type: JUnit + spec: + paths: [] + outputVariables: [] + envVariables: {} + timeout: 1h + tags: {} + - stage: + name: Deploy + identifier: Deploy + description: "" + type: Deployment + spec: + deploymentType: Kubernetes + service: + serviceRef: service + environment: + environmentRef: Dev + deployToAll: false + infrastructureDefinitions: + - identifier: Infra + execution: + steps: + - step: + type: Container + name: ServiceNow change + identifier: ServiceNow_change + spec: + connectorRef: docker_connector # harness docker connector + image: servicenowdocker/sndevops:5.1.0 + command: |- + # set mandetory variables. These can be set from Environment variable section of Optional configuration as well. + export HARNESS_STAGE_NAME="<+stage.identifier>" + export SNOW_URL="<+variable.SNOW_URL>" + export SNOW_TOOLID="<+variable.SNOW_TOOLID>" + export SNOW_TOKEN="<+variable.SNOW_TOKEN>" + # "sndevopscli create change" command notifies ServiceNow DevOps to create a change. + # Ensure to update "upstreamStage" accordingly. Example, if you have 3 stages Test, Change and Deploy in your pipeline, then you must update as "upstreamStage": "<+pipeline.stages.Test.nodeExecutionId>". Note that if your change stage is the first stage, you can remove this line. + sndevopscli create change -ctx \ + "{ + \"pipelineExecutionUrl\":\"<+pipeline.executionUrl>\", + \"stageIdentifier\": \"<+stage.identifier>\", + \"stageNodeExecutionId\": \"<+stage.nodeExecutionId>\", + \"pipelineName\": \"<+pipeline.name>\", + \"upstreamStage\": \"<+pipeline.stages.ServiceNow_DevOps_Security_Scan_Results.nodeExecutionId>\" + }" -p \ + "{ + \"changeStepDetails\": + { + \"timeout\":3600, + \"interval\":100 + }, + \"autoCloseChange\":true, + \"attributes\":{ + \"chg_model\":\"adffaa9e4370211072b7f6be5bb8f2ed\", + \"short_description\":\"Software Deployment\", + \"description\":\"Automated Software Deployment.\", + \"implementation_plan\":\"Software update is tested and results can be found in Test Summaries Tab.\", + \"backout_plan\":\"When software fails in production, the previous software release will be re-deployed.\", + \"test_plan\":\"Testing if the software was successfully deployed or not\" + } + }" + shell: Sh + infrastructure: + type: KubernetesDirect + spec: + connectorRef: KUBE_CONNECTOR # harness kubernates connector + namespace: harness-delegate-ng + resources: + limits: + cpu: "0.5" + memory: 500Mi + annotations: {} + labels: {} + containerSecurityContext: + capabilities: + drop: [] + add: [] + nodeSelector: {} + reports: + type: JUnit + spec: + paths: [] + outputVariables: [] + envVariables: {} + timeout: 1h + - step: + type: ShellScript + name: ShellScript_1 + identifier: ShellScript_1 + spec: + shell: Bash + executionTarget: {} + source: + type: Inline + spec: + script: echo "Deploying.." + environmentVariables: [] + outputVariables: [] + timeout: 10m + rollbackSteps: [] + tags: {} + failureStrategies: + - onFailure: + errors: + - AllErrors + action: + type: StageRollback + properties: + ci: + codebase: + connectorRef: CI_CONNECTOR + repoName: REPO_NAME + build: <+input> + sparseCheckout: [] + notificationRules: + - name: devopsbugbashwp5 + identifier: devopsbugbashwp5 + pipelineEvents: + - type: AllEvents + notificationMethod: + type: Webhook + spec: + webhookUrl: https://{instance_url}/api/sn_devops/v2/devops/tool/orchestration?toolId={tool sys id}&projectId={project sys id}&ni.nolog.token={token} + enabled: true diff --git a/package-lock.json b/package-lock.json index d86565f..8bf5599 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,9 +24,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://artifact.devsnc.com/repository/npm-all/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.2", + "resolved": "https://artifact.devsnc.com/repository/npm-all/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -77,9 +77,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://artifact.devsnc.com/repository/npm-all/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.6", + "resolved": "https://artifact.devsnc.com/repository/npm-all/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", diff --git a/package.json b/package.json index 89a48c9..b142663 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "author": "", "license": "ISC", "dependencies": { - "axios": "^1.7.4", + "axios": "^1.6.8", "commander": "^11.0.0", "dotenv": "^16.3.1" } diff --git a/src/api/base/sndevopsApi.js b/src/api/base/sndevopsApi.js index 731d736..3677306 100644 --- a/src/api/base/sndevopsApi.js +++ b/src/api/base/sndevopsApi.js @@ -3,6 +3,7 @@ const axios = require('axios'); const url = require('node:url'); require('dotenv').config(); const BaseEnv = require('../../common/baseEnv') +const ToolHandlerRegistry = require('../../handler/registry.js'); class SndevopsApi { @@ -79,6 +80,14 @@ class SndevopsApi { return branchName; } + + buildPipelineName(pipelineName) { + const handler = new ToolHandlerRegistry().getToolHandler(); + if(handler) { + return handler.getPipelineName(pipelineName) + } + return pipelineName + } } diff --git a/src/api/change/changeRequest.js b/src/api/change/changeRequest.js index 342e5f8..7aa199e 100644 --- a/src/api/change/changeRequest.js +++ b/src/api/change/changeRequest.js @@ -2,7 +2,7 @@ const SnDevopsApi = require('../base/sndevopsApi.js') const API_CHANGE_PATH = 'api/sn_devops/devops/orchestration/changeControl'; const API_CHANGE_STATUS_PATH = 'api/sn_devops/devops/orchestration/changeStatus'; const axios = require('axios'); -const BaseEnv = require('../../common/baseEnv.js') +const BaseEnv = require('../../common/baseEnv.js'); class ChangeRequestManager extends SnDevopsApi { @@ -34,11 +34,9 @@ class ChangeRequestManager extends SnDevopsApi { try { changePayload = { "buildNumber": BaseEnv.CI_PIPELINE_ID, - "apiPath": BaseEnv.CI_API_V4_URL, "jobId": BaseEnv.CI_JOB_ID, "pipelineName": BaseEnv.CI_PROJECT_TITLE, - "jobName": BaseEnv.CI_JOB_NAME, - "projectPath": BaseEnv.CI_PROJECT_PATH + "jobName": BaseEnv.CI_JOB_NAME }; if(BaseEnv.CI_PROJECT_ID) { changePayload.projectId = BaseEnv.CI_PROJECT_ID; @@ -52,6 +50,12 @@ class ChangeRequestManager extends SnDevopsApi { if(BaseEnv.CI_REPOSITORY_NAME) { changePayload.repository = BaseEnv.CI_REPOSITORY_NAME; } + if(BaseEnv.CI_PROJECT_PATH) { + changePayload.projectPath = BaseEnv.CI_PROJECT_PATH; + } + if(BaseEnv.CI_API_V4_URL) { + changePayload.apiPath = BaseEnv.CI_API_V4_URL; + } //ChangeAttributes are optional if (changeAttrPayload) { @@ -71,7 +75,8 @@ class ChangeRequestManager extends SnDevopsApi { } } - response = await this.createChangeNotification(changePayload, pipelineContext); + let payload = this._getRequestBodyForChangeCreation(changePayload, pipelineContext); + response = await this.createChangeNotification(payload); if (status) { interval = interval >= 100 ? interval : 100; timeout = timeout >= 100 ? timeout : 3600; @@ -80,7 +85,7 @@ class ChangeRequestManager extends SnDevopsApi { let prevPollChangeDetails = {}; try { - response = await this.tryFetch(start, interval, timeout, prevPollChangeDetails, changePayload); + response = await this.tryFetch(start, interval, timeout, prevPollChangeDetails, payload); } catch (error) { throw new Error(error.message); } @@ -92,7 +97,7 @@ class ChangeRequestManager extends SnDevopsApi { } } - async createChangeNotification(changePayload, pipelineContext) { + async createChangeNotification(changePayload) { let status = false; let response; @@ -100,8 +105,7 @@ class ChangeRequestManager extends SnDevopsApi { let url = new URL(API_CHANGE_PATH, this.url); url.searchParams.append("toolId", this.toolId); - let payload = this._getRequestBodyForChangeCreation(changePayload, pipelineContext); - console.log("Change creation payload = " + JSON.stringify(payload)); + console.log("Change creation payload = " + JSON.stringify(changePayload)); const defaultHeadersForToken = { 'Content-Type': 'application/json', 'Accept': 'application/json', @@ -109,7 +113,7 @@ class ChangeRequestManager extends SnDevopsApi { }; let httpHeaders = { headers: defaultHeadersForToken }; try { - response = await axios.post(url.toString(), JSON.stringify(payload), httpHeaders); + response = await axios.post(url.toString(), JSON.stringify(changePayload), httpHeaders); status = true; } catch (err) { if (err.code === 'ECONNABORTED') { @@ -163,7 +167,9 @@ class ChangeRequestManager extends SnDevopsApi { if (status) { var result = response.data.result; if (result && result.status == "Success") { - if (result.message) + if (result.changeControl === false) + console.log('\n \x1b[1m\x1b[36m' + "Change control is not enabled on the pipeline stage" + '\x1b[0m\x1b[0m'); + else if (result.message) console.log('\n \x1b[1m\x1b[36m' + result.message + '\x1b[0m\x1b[0m'); else 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'); @@ -235,10 +241,10 @@ class ChangeRequestManager extends SnDevopsApi { let url = new URL(API_CHANGE_STATUS_PATH, this.url); url.searchParams.append("toolId", this.toolId); - url.searchParams.append("stageName", changePayload.jobName); + url.searchParams.append("stageName", changePayload.stageName); url.searchParams.append("buildNumber", changePayload.jobId); - url.searchParams.append("pipelineName", changePayload.pipelineName); - url.searchParams.append("pipelineId", changePayload.projectId); + url.searchParams.append("pipelineName", this.buildPipelineName(changePayload.pipelineName)); + url.searchParams.append("pipelineId", changePayload.gitLabProjectId); if(changePayload.attemptNumber) url.searchParams.append("attemptNumber", changePayload.attemptNumber); @@ -369,6 +375,7 @@ class ChangeRequestManager extends SnDevopsApi { let payload = { "toolId": this.toolId, "stageName": changePayload.jobName, + "stageIdentifier": changePayload.jobName, "buildNumber": changePayload.buildNumber, "callbackURL": BaseEnv.CI_CALLBACK_URL ? BaseEnv.CI_CALLBACK_URL : changePayload.apiPath + "/projects/" + changePayload.projectId + "/jobs/" + changePayload.jobId, "isMultiBranch": "true", @@ -384,8 +391,13 @@ class ChangeRequestManager extends SnDevopsApi { "attemptNumber": changePayload.attemptNumber } + if(BaseEnv.CI_ORG_ID) { + payload.orgIdentifier = BaseEnv.CI_ORG_ID; + } + if(changePayload.projectId) { payload.gitLabProjectId = changePayload.projectId; + payload.projectIdentifier = changePayload.projectId; } if(pipelineContext) { diff --git a/src/api/change/getChange.js b/src/api/change/getChange.js index d8ef7d5..9f1f68c 100644 --- a/src/api/change/getChange.js +++ b/src/api/change/getChange.js @@ -56,7 +56,7 @@ class GetChangeManager extends SnDevopsApi { url = new URL(API_GET_CHANGE_PATH, this.url); url.searchParams.append("buildNumber", buildNumber); url.searchParams.append("stageName", stageName); - url.searchParams.append("pipelineName", pipelineName); + url.searchParams.append("pipelineName", this.buildPipelineName(pipelineName)); url.searchParams.append("toolId", this.toolId); if(gitLabProjectId) url.searchParams.append("pipelineId", gitLabProjectId); diff --git a/src/api/sonar/sonarRegistration.js b/src/api/sonar/sonarRegistration.js index 541be51..a6f2dc5 100644 --- a/src/api/sonar/sonarRegistration.js +++ b/src/api/sonar/sonarRegistration.js @@ -5,7 +5,7 @@ const BaseEnv = require('../../common/baseEnv.js'); class SonarRegistrationManager extends SnDevopsApi { - async createSonarSummary(sonarProjectKey, sonarUrl) { + async createSonarSummary(sonarProjectKey, sonarUrl, branchName) { let sonarPayload; //Payload contains information necessary for fetching sonar summary let endpoint; let httpHeaders; @@ -24,6 +24,7 @@ class SonarRegistrationManager extends SnDevopsApi { sonarPayload = { "sonarProjectKey": sonarProjectKey, "sonarUrl": sonarUrl, + "branchName": branchName }; endpoint = new URL(API_SONAR_PATH, this.url); @@ -79,7 +80,10 @@ class SonarRegistrationManager extends SnDevopsApi { } _getRequestBodyForSonarSummary(sonarPayload) { - let branchName = this.fetchBranchName(); + let branchName = sonarPayload.branchName; + if(!branchName){ + branchName = this.fetchBranchName(); + } let payload = { "sonarProjectKey": sonarPayload.sonarProjectKey, "sonarUrl": sonarPayload.sonarUrl, diff --git a/src/commands/entity/sonarRegistration.js b/src/commands/entity/sonarRegistration.js index 2d93828..6e50b78 100644 --- a/src/commands/entity/sonarRegistration.js +++ b/src/commands/entity/sonarRegistration.js @@ -14,11 +14,12 @@ module.exports = class SonarRegistration extends Base { this.addDefaultOptions(command); command.requiredOption('-projectKey, --sonarProjectKey ', 'Sonar Project Key') - .requiredOption('-url, --sonarUrl ', 'Sonar URL'); + .requiredOption('-url, --sonarUrl ', 'Sonar URL') + .option('-branch, --branchName ', 'branch Name'); command.action((options) => { console.log("Sonar registartion action called " ) - new SonarRegistrationManager(options.url,options.token,options.toolId).createSonarSummary(options.sonarProjectKey, options.sonarUrl) + new SonarRegistrationManager(options.url,options.token,options.toolId).createSonarSummary(options.sonarProjectKey, options.sonarUrl, options.branchName) }) } diff --git a/src/common/baseEnv.js b/src/common/baseEnv.js index c07aab5..a572663 100644 --- a/src/common/baseEnv.js +++ b/src/common/baseEnv.js @@ -1,4 +1,5 @@ require('dotenv').config(); +const ToolHandlerRegistry = require("../handler/registry"); module.exports = class BaseEnv{ @@ -31,4 +32,26 @@ module.exports = class BaseEnv{ static getEnv(envVariableName){ return process.env[envVariableName]; } + + + static loadEnvironmentVariables() { + const handler = new ToolHandlerRegistry().getToolHandler(); + if(handler) { + this.CI_JOB_ID ||= handler.getJobId(); + this.CI_JOB_NAME ||= handler.getJob(); + this.CI_PIPELINE_ID ||= handler.getPipelineId(); + this.CI_PROJECT_TITLE ||= handler.getProjectTitle(); + this.CI_COMMIT_BRANCH ||= handler.getBranch(); + this.CI_DEFAULT_BRANCH ||= handler.getBranch(); + this.CI_RUN_ATTEMPT ||= handler.getRunAttempt(); + this.CI_WORKFLOW_NAME ||= handler.getWorkflow(); + this.CI_REPOSITORY_NAME ||= handler.getRepository(); + this.CI_API_V4_URL ||= handler.getServerURL(); + this.CI_ORG_ID ||= handler.getOrgId(); + this.CI_PROJECT_ID ||= handler.getProjectId(); + if(handler['getCallbackURL'] && typeof handler['getCallbackURL'] === 'function'){ + this.CI_CALLBACK_URL = handler.getCallbackURL(); + } + } + } } diff --git a/src/handler/abstractHandler.js b/src/handler/abstractHandler.js new file mode 100644 index 0000000..a2ab309 --- /dev/null +++ b/src/handler/abstractHandler.js @@ -0,0 +1,27 @@ +class AbstractToolIntegrationHandler { + getJob() {} + + getJobId() {} + + getPipelineId () {} + + getProjectTitle () {} + + getBranch () {} + + getRunAttempt() {} + + getOrgId () {} + + getWorkflow () {} + + getRepository () {} + + getServerURL () {} + + getProjectId () {} + + getPipelineName() {} +} + +module.exports = AbstractToolIntegrationHandler diff --git a/src/handler/github.js b/src/handler/github.js new file mode 100644 index 0000000..784361e --- /dev/null +++ b/src/handler/github.js @@ -0,0 +1,53 @@ +const AbstractToolIntegrationHandler = require('./abstractHandler') + +class GithubIntegrationHandler extends AbstractToolIntegrationHandler { + + handle () { + if (process.env.GITHUB_ACTIONS) { + console.log('Platform: GitHub') + return true + } + } + + getJob () { + return process.env.GITHUB_JOB + } + + getJobId () { + return process.env.GITHUB_RUN_ID + } + + getPipelineId () { + return process.env.GITHUB_RUN_ID + } + + getProjectTitle () { + return `${process.env.GITHUB_REPOSITORY}/${process.env.GITHUB_WORKFLOW}` + } + + getBranch () { + return process.env.GITHUB_REF + } + + getRunAttempt () { + return process.env.GITHUB_RUN_ATTEMPT + } + + getWorkflow () { + return process.env.GITHUB_WORKFLOW + } + + getRepository () { + return process.env.GITHUB_REPOSITORY + } + + getServerURL () { + return process.env.GITHUB_SERVER_URL + } + + getPipelineName (pipelineName) { + return pipelineName + } +} + +module.exports = GithubIntegrationHandler; diff --git a/src/handler/harness.js b/src/handler/harness.js new file mode 100644 index 0000000..89a2c91 --- /dev/null +++ b/src/handler/harness.js @@ -0,0 +1,58 @@ +const AbstractToolIntegrationHandler = require('./abstractHandler') + +class HarnessIntegrationHandler extends AbstractToolIntegrationHandler { + handle () { + if (process.env.HARNESS_ORG_ID) { + console.log('Platform: Harness.io') + return true + } + } + + getJobId () { + return process.env.HARNESS_STAGE_ID + } + + getJob () { + return process.env.DRONE_STAGE_NAME || process.env.HARNESS_STAGE_NAME + } + + getRunId () { + return process.env.HARNESS_BUILD_ID + } + + getPipelineId () { + return process.env.HARNESS_PIPELINE_ID + } + + getBranch () { + return process.env.CI_COMMIT_BRANCH + } + + getProjectTitle () { + return process.env.HARNESS_PIPELINE_NAME || process.env.HARNESS_PIPELINE_ID + } + + getRunAttempt () { + return null + } + + getOrgId () { + return process.env.HARNESS_ORG_ID + } + + getProjectId () { + return process.env.HARNESS_PROJECT_ID + } + + getCallbackURL (){ + const HARNESS_DEFAULT_URL = 'https://app.harness.io/'; + const harnessPipelineExecutionUrl = `${HARNESS_DEFAULT_URL}/ng/#/account/${process.env.HARNESS_ACCOUNT_ID}/ci/orgs/${process.env.HARNESS_ORG_ID}/projects/${process.env.HARNESS_PROJECT_ID}/pipeline/${process.env.HARNESS_PIPELINE_ID}/executions/${process.env.HARNESS_EXECUTION_ID}`; + return process.env.CI_CALLBACK_URL || harnessPipelineExecutionUrl; + } + + getPipelineName (pipelineName) { + return `${process.env.HARNESS_ORG_ID}/${process.env.HARNESS_PROJECT_ID}/${pipelineName}` + } +} + +module.exports = HarnessIntegrationHandler; \ No newline at end of file diff --git a/src/handler/registry.js b/src/handler/registry.js new file mode 100644 index 0000000..91ef980 --- /dev/null +++ b/src/handler/registry.js @@ -0,0 +1,26 @@ +const HarnessIntegrationHandler = require("./harness"); +const GithubIntegrationHandler = require("./github"); + +class ToolHandlerRegistry { + handlers = []; + + constructor() { + this.handlers.push(new HarnessIntegrationHandler()); + this.handlers.push(new GithubIntegrationHandler()); + } + + getToolHandler() { + for (let handler of this.handlers) { + try { + if (handler.handle()) { + return handler; + } + } catch (error) { + console.error(`Error in handler: ${error.message}`); + } + } + return null; + } +} + +module.exports = ToolHandlerRegistry; diff --git a/src/index.js b/src/index.js index a418a04..d9ee730 100755 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,9 @@ const Create = require('./commands/create') const Get = require('./commands/get') const Update = require('./commands/update') const commander = require('commander') +const BaseEnv = require('./common/baseEnv') + +BaseEnv.loadEnvironmentVariables() const program = new commander.Command(); // Add nested commands using `.command()`.