How to integrate with my CI/CD DevOps Pipeline

Abstract: Subject7 has REST API to start and execute and getting reports on it. In this short article, I will be describing how to make a call to kick off an execution set as well making a call to get the reports all via REST. These articles will be enhanced in the future with more comprehensive cases but for now, it's just enough to get most teams starting their pipeline integration. 

  

Assumptions and References 

PLEASE READ THOROUGHLY

Subject7 REST API is all available in Swagger here.

In this example, we want to kick off an execution set by the name "sample_execution_setwith Firefox and Chrome, and we want to pass different data_sets using Swagger and show it here with Curl command. Here are details of what we will be doing: 

Main Request URL: https://platform.subject-7.com (This is where you log in to your subject7 account and it's not the same URL)

Execution Set:  The name of the set is "sample_execution_set" and this set is available in the account that is being used here. You will probably use your own set. 

Test Case and Data Templates: I have added one test case called "subject7_website_login_logout" which comes with every account in Subject7 under the subject7_samples folder. This test case uses three data templates: "subject7_ref_app_target_info",  "subject7_reference_environment" and "subject7_reference_credentials". In the JSON body below you will that for each data template used in each test case, we provide which DataSet to using. 

Other Parameters: Other parameters are browsers, in this case, we Firefox and Chrome. There is also the option of sending a build number and you can query the results later using this number. You can specify a delay for whatever reason you may think and runtime speed which I set to 200 ms. 

 

Sample Invocation Call 

JSON body of the post to start the execution sets with given parameters:

{ 
"name": "sample_execution_set",
"configuration": {
"pool": "default",
"buildNumber": "your_build_number",
"browserTypes": [ "CHROME", "FIRE_FOX" ],
"videoCaption": true,
"delay": 2,
"rotations": 1,
"runtimeSpeed": 200,
"dataSetSelection": [
{ "testCaseName": "subject7_website_login_logout",
"templateName": "subject7_ref_app_target_info",
"dataSetNames": [ "subject7_remote" ]
},
{ "testCaseName": "subject7_website_login_logout",
"templateName": "subject7_reference_environment",
"dataSetNames": [ "subject7_reference_url" ]
},
{ "testCaseName": "subject7_website_login_logout",
"templateName": "subject7_reference_credentials",
"dataSetNames": [ "subject7_login_tom_smith" ]
}
]
}
}
 

 

 

 You can try it out with swagger and here is a screenshot: 

swagger_invoke_set_post.png

Curl Command: 

curl -X POST "https://platform.subject-7.com/api/v2/executions" -H "accept: application/json" -H "authorization: Basic cmZlaXppOmSAMPLEnZWxhdGVyNzE=" -H "Content-Type: application/json" -d "{ \"name\": \"sample_execution_set\", \"configuration\": { \"pool\": \"default\",\"buildNumber\": \"your_build_number\", \"browserTypes\": [ \"CHROME\", \"FIRE_FOX\" ],\"videoCaption\": true, \"delay\": 2, \"rotations\": 1, \"runtimeSpeed\": 200, \"dataSetSelection\": [ { \"testCaseName\": \"subject7_website_login_logout\", \"templateName\": \"subject7_ref_app_target_info\", \"dataSetNames\": [ \"subject7_remote\" ] }, { \"testCaseName\": \"subject7_website_login_logout\", \"templateName\": \"subject7_reference_environment\", \"dataSetNames\": [ \"subject7_reference_url\" ] }, { \"testCaseName\": \"subject7_website_login_logout\", \"templateName\": \"subject7_reference_credentials\", \"dataSetNames\": [ \"subject7_login_tom_smith\" ] } ] } }"

Request URL: 

https://platform.subject-7.com/api/v2/executions

Request Body:

{
  "id": 4031
}

 

The 4031 is the execution id in Subject7, here is a screenshot from Subject7 Platform under executions:

mceclip0.png

 

Sample Report Call

Here is the swagger call, notice that the id=4031 that was returned in the response in the previous response. 

swagger_report_execution_4031.png

Curl Command:

curl -X GET "https://platform.subject-7.com/api/v2/executions/4031" -H "accept: application/json" -H "authorization: Basic cmZlaXppOmSampleZWxhdGVyNzE="

Request URL: 

https://platform.subject-7.com/api/v2/executions/4031

Respond Body: 

{
  "id": 4031,
  "executionSetId": 146,
  "executionSetName": "sample_execution_set",
  "requested": 1588961772.291,
  "executionState": "COMPLETED",
  "pool": "default",
  "executionType": "WEB_SERVICE",
  "executionStatus": "PASS",
  "result": "2/2",
  "browserTypes": [
    "FIRE_FOX",
    "CHROME"
  ],
  "build": null,
  "requester": "rfeizi",
  "speed": 1000
}

 

 

 Pipelining Example with Jenkins

 

We have provided examples above on how to show the low-level REST calls as building blocks for any generic integrations. I this section we will provide scripts that we use with our Jenkins as a sample. 

 Integration with pipelines will basically require making REST calls to start an execution set (Running tests) and to pull results withing a timeout. Meaning, after a certain amount of time you need to give up the automation test stage in your pipeline as there maybe executions that are stuck and you need to fail the build. 

 

Here is a working sample Groovy script of the pipeline and sample screenshots of the stages:

 Screen_Shot_2020-07-06_at_1.53.56_AM.png

 

 

//0 16 * * 1-5 % S3_PUBLISH=true; DEPLOY=true; XPOINT_DEPLOY=true;BRANCH=release/release-8.10.0

pipeline {
agent any

options {
// skip checking out code from source control by default in the agent directive
skipDefaultCheckout()
// disallow concurrent executions of the Pipeline
disableConcurrentBuilds()
// make sure that this build doesn't hang forever
timeout(time: 300, unit: 'MINUTES')
// prepend all console output generated by the Pipeline run with the time at which the line was emitted
timestamps()
}

tools {
maven 'maven-3.6.0'
jdk 'openjdk-11.0.2'
}

parameters {
string(name: 'BRANCH', defaultValue: "develop", description: 'Repository branch')
string(name: 'RELEASE', defaultValue: 'Rene', description: 'Release name')
booleanParam(name: 'BUILD_PROOF', defaultValue: true, description: 'Build Proof Docker Image')
booleanParam(name: 'BUILD_HERA', defaultValue: true, description: 'Build Hera Docker Image')
booleanParam(name: 'BUILD_ACTIVEMQ', defaultValue: false, description: 'Build ActiveMQ Docker Image')
booleanParam(name: 'BUILD_DB', defaultValue: false, description: 'Build PostgreSQL Docker Image')
booleanParam(name: 'S3_PUBLISH', defaultValue: false, description: 'Upload artifacts (not docker images) to S3')
booleanParam(name: 'XPOINT_DEPLOY', defaultValue: false, description: 'Runs "deploy_xpoint_dev" job with "XPOINT_URL" argument. Available only if publish to S3 enabled.')
booleanParam(name: 'DEPLOY', defaultValue: false, description: 'Deploy platform to the server')
choice(name: 'HOST', choices: ['dev.subject7.co', 'demo.subject7.co', 'featuresprivate.subject7.co'], description: 'Target system for deployment')
choice(name: 'LICENSE_PROFILE', choices: ['default', 'dev', 'demo', 'prod'], description: 'License profile value for further updating of license provider. If specify "default" then License provider will not be changed during build process.')
booleanParam(name: 'AUTOMATION_TESTS', defaultValue: true, description: 'Executes end-to-end tests before deployment')
}


stages {
stage("Notify Slack users about upcoming deployment") {
when {
expression { params.DEPLOY }
}

steps {
wrap([$class: 'BuildUser']) {
script {
def safeBuildUser = "automated"
try {
safeBuildUser = BUILD_USER
} catch (e) {
echo "User not in scope, probably triggered from another job or scheduled"
}
echo "Build User is: ${safeBuildUser}"

slackSend (message: "@here*New build with furher deployment will start in a minute*\nRequester: ${safeBuildUser}\nBranch: ${params.BRANCH}\nDeployment host: ${params.HOST}.\nExecute end-to-end tests: ${params.AUTOMATION_TESTS}.\n*<${env.JOB_URL}|View job>*\t*<${env.BUILD_URL}|View build>*\t*<${env.BUILD_URL}stop|Abort build>*")
sleep time: 1, unit: 'MINUTES'
}
}
}
}

stage("Checkout code") {
steps {
deleteDir()

// code
checkout([
$class : 'GitSCM',
branches : [[name: "${params.BRANCH}"]],
doGenerateSubmoduleConfigurations: false,
extensions : [[
$class : 'RelativeTargetDirectory',
relativeTargetDir: 'repository'
], [
$class : 'CloneOption',
timeout: 20
]],
submoduleCfg : [],
userRemoteConfigs : [[
url : 'https://github.com/subject7inc/platform.git',
credentialsId: "${env.GITHUB_CREDENTIALS_ID}"
]]
])
}
}

stage('Build') {
steps {
dir('repository') {
withCredentials([string(credentialsId: 'WIN_SIGNING_PASSWORD', variable: 'win_signing_password')]) {
script {
def pom = readMavenPom file: 'pom.xml'
env.version = pom.version

def mvnCommandParams = "-Pjenkins-plugin -Pchrome-plugin -Pinstallers -Dapp.release=${params.RELEASE} -Dapp.build.number=${BUILD_NUMBER} -Dwin.signing.password=${win_signing_password} -Dwin.signing.cert=player.pfx -U"
if(params.LICENSE_PROFILE && params.LICENSE_PROFILE != 'default'){
mvnCommandParams = mvnCommandParams + " -Dlicense.profile=${params.LICENSE_PROFILE}"
}
sh "mvn clean package $mvnCommandParams"
}
}
}
}
}

stage('Build Proof docker image') {
when {
expression { params.BUILD_PROOF }
}
steps {
dir('repository/proof') {
script {
docker.withRegistry('https://nexus.subject-7.com:18043', "${env.NEXUS_CREDENTIALS_ID}") {
def image = docker.build("s7/proof:${env.version}.${env.BUILD_ID}-rc")
image.push()
}
}
}
}
}

stage('Build Hera docker image') {
when {
expression { params.BUILD_HERA }
}

steps {
dir('repository/hera') {
script {
docker.withRegistry('https://nexus.subject-7.com:18043', "${env.NEXUS_CREDENTIALS_ID}") {
def image = docker.build("s7/hera:${env.version}.${env.BUILD_ID}-rc")
image.push()
}
}
}
}
}

stage('Build ActiveMQ docker image') {
when {
expression { params.BUILD_ACTIVEMQ }
}

steps {
dir('repository/docker/platform-build/activemq') {
script {
docker.withRegistry('https://nexus.subject-7.com:18043', "${env.NEXUS_CREDENTIALS_ID}") {
def image = docker.build("s7/activemq:${env.version}.${env.BUILD_ID}-rc")
image.push()
}
}
}
}
}

stage('Build DB Postgres docker image') {
when {
expression { params.BUILD_DB }
}
steps {
dir('repository/docker/platform-build/postgres') {
script {
docker.withRegistry('https://nexus.subject-7.com:18043', "${env.NEXUS_CREDENTIALS_ID}") {
def image = docker.build("s7/postgres:${env.version}.${env.BUILD_ID}-rc")
image.push()
}
}
}
}
}

stage('Automation Tests') {
when {
expression { params.AUTOMATION_TESTS }
}
steps {
dir('repository') {
script {
echo 'deploy to test server'
build job: 'deploy', parameters: [string(name: 'VERSION', value: "${env.version}.${env.BUILD_ID}-rc"), string(name: 'HOST', value: "test.subject7.co")]
withAWS(region: 'us-east-1', credentials: "${env.AWS_CREDENTIALS_ID}") {
path = "xpoint_tmp/xpoint-${params.RELEASE}/${params.BRANCH}/${BUILD_NUMBER}.zip"
s3Upload(
includePathPattern: 'proof/executor-distrib/target/xpoint*.zip',
bucket: 'subject7-dev-share',
path: "$path")
url = s3PresignURL(bucket: 'subject7-dev-share', key: "$path", durationInSeconds: 600000);
echo 'deploy xpoints to test server'
build job: 'xpoint_deploy', parameters: [
string(name: 'XPOINT_URL', value: "$url"),
string(name: 'POOL', value: 'default'),
string(name: 'COUNT', value: '1'),
string(name: 'SERVER', value: 'test')
]
}
echo 'start end-to-end tests'
build job: 'test_automation_plugin_job'
}
}
}
}

stage("Tag images with version") {
steps {
dir('repository') {
script {
docker.withRegistry('https://nexus.subject-7.com:18043', "${env.NEXUS_CREDENTIALS_ID}") {
if (params.BUILD_DB) {
def image = docker.image("s7/postgres:${env.version}.${env.BUILD_ID}-rc")
image.push("${env.version}")
image.push("${env.version}.${env.BUILD_ID}")
}

if (params.BUILD_ACTIVEMQ) {
def image = docker.image("s7/activemq:${env.version}.${env.BUILD_ID}-rc")
image.push("${env.version}")
image.push("${env.version}.${env.BUILD_ID}")
}

if (params.BUILD_HERA) {
def image = docker.image("s7/hera:${env.version}.${env.BUILD_ID}-rc")
image.push("${env.version}")
image.push("${env.version}.${env.BUILD_ID}")
}

if (params.BUILD_PROOF) {
def image = docker.image("s7/proof:${env.version}.${env.BUILD_ID}-rc")
image.push("${env.version}")
image.push("${env.version}.${env.BUILD_ID}")
}
}
}
}
}
}

stage('Publish to S3') {
when {
expression { params.S3_PUBLISH }
}
steps {
dir('repository') {
sh 'if [ -d "artifacts" ]; then rm -Rf artifacts; fi'
sh './bin/copy_artifacts.sh artifacts'
script {
withAWS(region: 'us-east-1', credentials: "${env.AWS_CREDENTIALS_ID}") {
s3Upload(
file: 'artifacts',
bucket: 'subject7-dev-share',
path: "builds/${params.RELEASE}/${params.BRANCH}/${BUILD_NUMBER}")
files = s3FindFiles(bucket: 'subject7-dev-share', path: "builds/${params.RELEASE}/${params.BRANCH}/${BUILD_NUMBER}")
sh 'touch artifacts.html'
for (file in files) {
def url = s3PresignURL(bucket: 'subject7-dev-share', key: "builds/${params.RELEASE}/${params.BRANCH}/${BUILD_NUMBER}/$file.name", durationInSeconds: 600000);
def readContent = readFile 'artifacts.html'
writeFile file: 'artifacts.html', text: readContent + "<a href='$url'>$file.name</a></br>"
}
archiveArtifacts artifacts: 'artifacts.html'
}
}
}
}
}

stage('Deploy') {
when {
expression { params.DEPLOY }
}
steps {
dir('repository') {
script {
build job: 'deploy', parameters: [string(name: 'VERSION', value: "${env.version}.${env.BUILD_ID}"), string(name: 'HOST', value: "${params.HOST}")]
}
}
}

}

stage('Xpoints Deploy') {
when {
expression { params.XPOINT_DEPLOY && params.S3_PUBLISH }
}
steps {
script {
withAWS(region: 'us-east-1', credentials: "${env.AWS_CREDENTIALS_ID}") {
def xpoint_search_exp = "builds/${params.RELEASE}/${params.BRANCH}/${BUILD_NUMBER}/xpoint*.zip"
files = s3FindFiles(bucket: 'subject7-dev-share', glob: "$xpoint_search_exp")
if (files.length == 0) {
error("Unable to find XPoint on S3, used path $xpoint_search_exp")
}
file = files[0]
def url = s3PresignURL(bucket: 'subject7-dev-share', key: "builds/${params.RELEASE}/${params.BRANCH}/${BUILD_NUMBER}/$file.name", durationInSeconds: 600000);
if (params.HOST == 'dev.subject7.co') {
withCredentials([file(credentialsId: 'HYDRA_REST_API_CRT', variable: 'CRT'), file(credentialsId: 'HYDRA_REST_API_PEM', variable: 'PEM')]) {
def response_url = sh(script:"curl --request POST --insecure --cert $CRT --key $PEM --header 'Content-Type: application/json' --url 'https://10.1.3.19:8443/api/defaults/xpointurl' --data '$url'", returnStdout: true)
sh "echo '$response_url'"

def response_default = sh(script:"curl --request POST --insecure --cert $CRT --key $PEM --url 'https://10.1.3.19:8443/api/pool/default/shutdown'", returnStdout: true)
sh "echo '$response_default'"

def response_default_load = sh(script:"curl --request POST --insecure --cert $CRT --key $PEM --url 'https://10.1.3.19:8443/api/pool/default_load/shutdown'", returnStdout: true)
sh "echo '$response_default_load'"
}
} else if (params.HOST == 'demo.subject7.co') {
withCredentials([file(credentialsId: 'HYDRA_REST_API_CRT', variable: 'CRT'), file(credentialsId: 'HYDRA_REST_API_PEM', variable: 'PEM')]) {
def response_url = sh(script:"curl --request POST --insecure --cert $CRT --key $PEM --header 'Content-Type: application/json' --url 'https://10.1.3.130:8443/api/defaults/xpointurl' --data '$url'", returnStdout: true)
sh "echo '$response_url'"

def response_default = sh(script:"curl --request POST --insecure --cert $CRT --key $PEM --url 'https://10.1.3.130:8443/api/pool/default/shutdown'", returnStdout: true)
sh "echo '$response_default'"

def response_default_load = sh(script:"curl --request POST --insecure --cert $CRT --key $PEM --url 'https://10.1.3.130:8443/api/pool/default_load/shutdown'", returnStdout: true)
sh "echo '$response_default_load'"
}
} else if (params.HOST == 'featuresprivate.subject7.co') {
build job: 'xpoint_deploy', parameters: [
string(name: 'XPOINT_URL', value: "$url"),
string(name: 'POOL', value: 'default'),
string(name: 'COUNT', value: '1'),
string(name: 'SERVER', value: 'features')
]
} else {
echo "skipped: host is not support xpoint deployment"
}
}
}
}
}
}

post {
always {
dir('repository') {
script {
sh "docker images -a | grep 's7/postgres\\|s7/hera\\|s7/proof\\|s7/activemq' | awk '{print \$3}' | uniq | xargs docker rmi -f"
def message = "${currentBuild.fullProjectName}\nResult: ${currentBuild.currentResult}\nURL: ${currentBuild.absoluteUrl}\nBranch: ${params.BRANCH}\nVersion: ${env.version}.${env.BUILD_ID}\nDeploy: ${params.DEPLOY}\nHost: ${params.HOST}\nExecute end-to-end tests: ${params.AUTOMATION_TESTS}"
slackSend(color: "good", message: message)
}
}
cleanWs()
}
}
}

 

 

Was this article helpful?
1 out of 1 found this helpful
Have more questions? Submit a request

Comments

Please sign in to leave a comment.