diff --git a/License.md b/License.md index c77270a..dc9a6a8 100644 --- a/License.md +++ b/License.md @@ -1,7 +1,7 @@ -Copyright 2019 XEBIALABS +Copyright ${year} ${name} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 3bc7eec..2712a06 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,18 @@ This document describes the functionality provided by the XL Release Bamboo plug See the [XL Release reference manual](https://docs.xebialabs.com/xl-release) for background information on XL Release and release automation concepts. +This is a 'See It Work' plugin project, meaning the code base includes functionality that makes it easy to spin up and configure a dockerized version of the XebiaLabs platform with this plugin already installed. Using the provided test data, you can then try out the plugin features. This is useful for familiarizing yourself with the plugin functionality, for demonstrations, testing and for further plugin development. [See the Demo/Dev section.](#to-run-demo-or-dev-version-set-up-docker-containers-for-both-xlr-and-the-mock-server) + +The plugin code base also includes a gradle task for [automatic integration testing](#to-run-integration-tests). + ## Overview -This plugin allows XL Release to run a Bamboo plan or trigger a Bamboo deployment. +This plugin allows XL Release to run a Bamboo plan, create a Bamboo Release or trigger a Bamboo deployment. ## Requirements -* XL Release 5.0+ +* XL Release 9.0+ +* This has been tested with XL Release 9.7 and Bamboo version 6.10.3 ## Installation @@ -31,24 +36,87 @@ This plugin allows XL Release to run a Bamboo plan or trigger a Bamboo deploymen ## Usage -### RunPlan +### Run Plan -The RunPlan.py script accepts a Bamboo project-plan-key (for example, PROJ-PLAN). It calls Bamboo's API to run the next build job(s) for that plan and the build number is returned. Polling of the job status occurs at 5-second intervals. The script output will indicate the build status as success or failure. +The Run Plan task accepts a Bamboo project-plan-key (for example, MYN-MYN). It calls Bamboo's API to run the next build job(s) for that plan. Polling of the job status occurs at 5-second intervals. The script output will indicate the build status as success or failure. The Plan Result Key, Build Number, Build State and State, which are return by the Bamboo API call can be saved into Release Variables, if you choose. ![run-plan screenshot](images/run-plan.png) -### TriggerDeployment +### Create Release + +The Create Release task take a Bamboo Deployment Project Name, a Project Build Result (which is the Plan Result Key returned after a Plan is run, such as 'MYN-MYN-9'), and a Version Name. It calls Bamboo's API to create a release for that project. -The TriggerDeployment script accepts a project name, environment name, and version name. It calls Bamboo's API to look up to respective ids of these items and then triggers a deployment. +![run-plan screenshot](images/createRelease.png) -![trigger-deployment screenshot](images/trigger-deployment.png) +### Trigger Deployment + +The TriggerDeployment script accepts a project name, environment name, and version name. It calls Bamboo's API to look up the respective ids of these items and then triggers a deployment. + +![trigger-deployment screenshot](images/triggerDeployment.png) ### Configuration ### -![server-configuration screenshot](images/server-configuration.png) +![server-configuration screenshot](images/bambooServerConfig.png) + +## Developers + +Build and package the plugins with... + +```bash +./gradlew assemble +``` + +### To run integration tests + +1. Clone this git project to your local dev environment +2. You will need to have Docker and Docker Compose installed. +3. The XL-Release docker image uses the community trial license +4. Open a terminal in the root of the xlr-bamboo-plugin project and run the following gradle task + +```bash +./gradlew clean integrationTest +``` + +The test will set up a temporary xlr/mockserver testbed using docker. The mockserver acts as a mock Bamboo server and returns canned responses to Bamboo REST API calls. After testing is complete, the test docker containers are stopped and removed. + +### To run demo or dev version (set up docker containers for both XLR and the mock server) + +NOTE: + +1. For requirements, see the 'To run integration tests' above. +2. XL Release will run on the [localhost port 15516](http://localhost:15516/). +3. The XL Release username / password is admin / admin. +4. The Mockserver runs on the [localhost port 5099](http://localhost:5099/). +5. The Mockserver username / password is admin / admin +6. Within XLR, you will need to set up the psuedo Bamboo server and import the provide template. When you run this XLR release, the Mockserver will act as a pseudo Bamboo server and return canned responses to Bamboo REST API calls. + +* Before running the demo, be sure to create the plugin by opening a terminal, cd into the plugin source code directory, and run + +```bash +./gradlew clean build +``` + +* To run the dev/demo mode, open a terminal, cd into the src/test/resources/docker directory of the plugin code and run + +```bash +docker-compose up +``` + +* After XLR starts up, log in using the admin / admin credentials and set up the mock Bamboo server, as described below, and then use the XLR 'Import Template' feature to import the template found in the src/test/resources/docker/initialize/data directory. You can then create a release and run the test example. + +Set up the mock Bamboo server exactly as shown with a server name of 'bamboo server' and the username/passwor admin/admin. +![mockBambooServer](images/mockBambooServer.png) + +* To shut down and remove the docker containers - in a terminal, cd to the src/test/resources/docker directory, and run + +```bash +docker-compose down +``` ## References + + diff --git a/build.gradle b/build.gradle index 91b8615..211c955 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,6 @@ -buildscript { - repositories { - mavenLocal() - mavenCentral() - maven { - url 'https://dist.xebialabs.com/public/maven2' - } - } - -} - plugins { id "com.github.hierynomus.license" version "0.14.0" id 'nebula.release' version '6.0.0' - id "com.xebialabs.xl.docker" version "1.1.0" id "com.github.hierynomus.jython" version "0.8.0" } @@ -20,7 +8,33 @@ apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'java' apply plugin: 'maven' + +if (!project.hasProperty('release.scope')) { + project.ext['release.scope'] = 'patch' +} + +if (!project.hasProperty('release.useLastTag')) { + project.ext['release.useLastTag'] = true +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url 'https://dist.xebialabs.com/public/maven2' + } +} + + dependencies { + // Place rest-assured before JUnit dependency to make sure correct Hamcrest is used + testCompile 'io.rest-assured:rest-assured:3.2.0' + testCompile "junit:junit:4.11" + testCompile "com.googlecode.json-simple:json-simple:1.1.1" + testCompile "org.assertj:assertj-core:3.6.2" + testCompile "org.testcontainers:testcontainers:1.12.0" + testCompile "org.skyscreamer:jsonassert:1.5.0" + jython python(":httplib2:0.11.3") { copy { from "python2/httplib2" @@ -28,24 +42,34 @@ dependencies { } } -xlDocker { - compileImage = 'xebialabsunsupported/xlr_dev_compile' - compileVersion = '8.5.1' - runImage = 'xebialabsunsupported/xlr_dev_run' - runVersion = '8.5.1' - runPortMapping = '5516:5516' +test { + // Auto detected Unit tests only, exclude the end to end integration test + exclude '**/*IntegrationTest.class' + + // show standard out and standard error of the test JVM(s) on the console + testLogging.showStandardStreams = true } + -if (!project.hasProperty('release.scope')) { - project.ext['release.scope'] = 'patch' -} +task integrationTest(type: Test, dependsOn: ['build']) { + // do not automatically scan for tests + scanForTestClasses = false + // explicitly include the integration test + include '**/*IntegrationTest.class' + // To run tests - + // 1. Docker and Docker Compose must be installed + // 2. To run the test - ./gradlew clean integrationTest + // The test will set up a temporary xlr/mockserver testbed using docker. The mockserver + // will serve up Bamboo Rest Api Responses + // After testing is complete, the test docker containers are stopped and removed. -if (!project.hasProperty('release.useLastTag')) { - project.ext['release.useLastTag'] = true + // show standard out and standard error of the test JVM(s) on the console + testLogging.showStandardStreams = true } + license { - header rootProject.file('LICENSE.md') + header rootProject.file('License.md') strictCheck false excludes(["**/*.json", "**/httplib2/**/*.*"]) ext.year = Calendar.getInstance().get(Calendar.YEAR) diff --git a/deploy_key.enc b/deploy_key.enc new file mode 100644 index 0000000..034706d Binary files /dev/null and b/deploy_key.enc differ diff --git a/images/bambooServerConfig.png b/images/bambooServerConfig.png new file mode 100644 index 0000000..b3a119f Binary files /dev/null and b/images/bambooServerConfig.png differ diff --git a/images/createRelease.png b/images/createRelease.png new file mode 100644 index 0000000..302111d Binary files /dev/null and b/images/createRelease.png differ diff --git a/images/mockBambooServer.png b/images/mockBambooServer.png new file mode 100644 index 0000000..524c90f Binary files /dev/null and b/images/mockBambooServer.png differ diff --git a/images/run-plan.png b/images/run-plan.png index cf42674..5123ac7 100644 Binary files a/images/run-plan.png and b/images/run-plan.png differ diff --git a/images/triggerDeployment.png b/images/triggerDeployment.png new file mode 100644 index 0000000..62618f0 Binary files /dev/null and b/images/triggerDeployment.png differ diff --git a/src/main/resources/bamboo/CreateRelease.py b/src/main/resources/bamboo/CreateRelease.py index 55f4609..97efccf 100644 --- a/src/main/resources/bamboo/CreateRelease.py +++ b/src/main/resources/bamboo/CreateRelease.py @@ -1,5 +1,5 @@ # -# Copyright 2019 XEBIALABS +# Copyright 2020 XEBIALABS # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: # diff --git a/src/main/resources/bamboo/RunPlan.py b/src/main/resources/bamboo/RunPlan.py index 7296dac..2fc786a 100644 --- a/src/main/resources/bamboo/RunPlan.py +++ b/src/main/resources/bamboo/RunPlan.py @@ -1,5 +1,5 @@ # -# Copyright 2019 XEBIALABS +# Copyright 2020 XEBIALABS # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: # @@ -28,10 +28,10 @@ def successful(brkey): response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers) return json.loads(response.response)['successful'] -def getStatesAndTimes(brkey): +def getKeyStatesAndTimes(brkey): response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers) jsonData = json.loads(response.response) - return (jsonData['buildState'], jsonData['state'], jsonData['prettyBuildStartedTime'], jsonData['prettyBuildCompletedTime']) + return (jsonData['planResultKey']['key'], jsonData['buildState'], jsonData['state'], jsonData['prettyBuildStartedTime'], jsonData['prettyBuildCompletedTime']) credentials = CredentialsFallback(bambooServer, username, password).getCredentials() request = HttpRequest(bambooServer, credentials['username'], credentials['password']) @@ -44,7 +44,7 @@ def getStatesAndTimes(brkey): while (not finished(brkey)): time.sleep(5) -(buildState, state, prettyBuildStartedTime, prettyBuildCompletedTime) = getStatesAndTimes(brkey) +(planResultKey, buildState, state, prettyBuildStartedTime, prettyBuildCompletedTime) = getKeyStatesAndTimes(brkey) print "Build job started at " + prettyBuildStartedTime + "\n" diff --git a/src/main/resources/bamboo/TriggerDeployment.py b/src/main/resources/bamboo/TriggerDeployment.py old mode 100644 new mode 100755 index 6ce467c..af6faee --- a/src/main/resources/bamboo/TriggerDeployment.py +++ b/src/main/resources/bamboo/TriggerDeployment.py @@ -1,5 +1,5 @@ # -#Copyright 2019 XEBIALABS +#Copyright 2020 XEBIALABS # #Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: # @@ -38,7 +38,7 @@ def getEnvironmentId(projectId, environmentName): if item['name'] == environmentName: print "Environment ID for %s is %s\n" % (environmentName, item['id']) return item['id'] - print "Error: environment not found for %s, %s\n" % (projectName, environmentName) + print "Error: environment not found for %s, %s\n" % (projectId, environmentName) sys.exit(1) def getVersionId(projectId, versionName): @@ -48,20 +48,78 @@ def getVersionId(projectId, versionName): if item['name'] == versionName: print "Version ID for %s is %s\n" % (versionName, item['id']) return item['id'] + #print "Error: version not found for %s, %s\n" % (projectId, versionName) print "Error: version not found for %s, %s, %s\n" % (projectName, environmentName, versionName) sys.exit(1) +def getPlanKey(projectName): + print "Executing getPlanKey() with projectName %s\n" % projectName + response = request.get('rest/api/latest/deploy/project/all', contentType=contentType, headers=headers) + #print "response: %s " % response + for item in json.loads(response.response): + if item['name'] == projectName: + print "planKey for %s is %s\n" % (projectName, item['planKey']) + print "planKey.key for %s is %s\n" % (projectName, item['planKey']['key']) + return item['planKey']['key'] + print "Error: project not found for %s\n" % projectName + sys.exit(1) + +def getPlanKeyResult(planKey, versionName): + #e.g. SER-AR-139, planKey = SER-AR, versionName = 3.1.0-139 + print "in getPlanKeyResult- planKey: %s, versionName: %s" % (planKey,versionName) + planKeyRslt = planKey + versionName[versionName.index("-"):] + print "planKey: %s, versionName: %s, planKeyResult: %s\n" % (planKey, versionName, planKeyRslt) + return planKeyRslt + +def createDeploymentVersion(projectId, planKeyResult, versionName): + print "Executing createDeploymentVersion() with projectId %s and versionName %s\n" % (projectId, versionName) + response = request.post('rest/api/latest/deploy/project/%s/version' % (projectId), '{"planResultKey" : "%s", "name" : "%s"}' % (planKeyResult, versionName), contentType=contentType, headers=headers) + result = json.loads(response.response) + print (result['id']) + return result['id'] + +def getDeploymentVersion(projectId, planKeyResult, versionName): + print "Executing getDeploymentVersion() with projectId %s and versionName %s\n" % (projectId, versionName) + response = request.get('rest/api/latest/deploy/project/%s/versions' % (projectId), contentType=contentType, headers=headers) + for item in json.loads(response.response)['versions']: + if item['name'] == versionName: + print "versionId for %s is %s\n" % (versionName, item['id']) + return item['id'] + return None + def triggerDeployment(environmentId, versionId): print "Executing triggerDeployment() with environmentId %s and versionId %s\n" % (environmentId, versionId) response = request.post('rest/api/latest/queue/deployment/?environmentId=%s&versionId=%s' % (environmentId, versionId), '{}', contentType=contentType, headers=headers) result = json.loads(response.response) print (result['deploymentResultId'], result['link']['href']) - return (result['deploymentResultId'], result['link']['href']) + return result['deploymentResultId'], result['link']['href'] credentials = CredentialsFallback(bambooServer, username, password).getCredentials() +print "1) credentials: no value" + request = HttpRequest(bambooServer, credentials['username'], credentials['password']) +print "2) request: %s" % request + projectId = getProjectId(projectName) +print "3) projectId: %s" % projectId + environmentId = getEnvironmentId(projectId, environmentName) -versionId = getVersionId(projectId, versionName) +print "4) environmentId: %s" % environmentId + +#versionId = getVersionId(projectId, versionName) + +print "4a) before getPlanKey with projectName: %s" % projectName +planKey = getPlanKey(projectName) +print "5) planKey: %s" % planKey + +planKeyResult = getPlanKeyResult(planKey, versionName) +print "6) planKeyResult: %s" % planKeyResult + +versionId = getDeploymentVersion(projectId, planKeyResult, versionName) +print "7) versionId: %s" % versionId + +if versionId is None: + versionId = createDeploymentVersion(projectId, planKeyResult, versionName) +print "8) versionId: %s" % versionId (deploymentResultId, href) = triggerDeployment(environmentId, versionId) diff --git a/src/main/resources/synthetic.xml b/src/main/resources/synthetic.xml index d6e7751..675c319 100644 --- a/src/main/resources/synthetic.xml +++ b/src/main/resources/synthetic.xml @@ -1,7 +1,7 @@