437 lines
13 KiB
Groovy
437 lines
13 KiB
Groovy
#!/usr/bin/env groovy
|
|
|
|
/**
|
|
* This Jenkinsfile runs a set of parallel builders for the dcos-cli across
|
|
* multiple platforms (linux/mac/windows).
|
|
*
|
|
* One set of builders builds the CLI into a binary on each platform. The other
|
|
* set of builders runs integration tests on each platform. Under the hood, the
|
|
* integration test builders use `dcos_launch` to create the DC/OS clusters for
|
|
* each platform to run their tests against. Unfortunately, `dcos_luanch` only
|
|
* works reliably on Linux, so we use a single linux instance to create all of
|
|
* the clusters and separate linux/mac/windows instances to run the actual
|
|
* tests.
|
|
*/
|
|
|
|
/**
|
|
* These are the platforms we are building against.
|
|
*/
|
|
def platforms = ["linux", "mac", "windows"]
|
|
|
|
/**
|
|
* This generates the `dcos_launch` config for a particular build.
|
|
*/
|
|
def generateConfig(deploymentName, templateUrl) {
|
|
return """
|
|
---
|
|
launch_config_version: 1
|
|
deployment_name: ${deploymentName}
|
|
template_url: ${templateUrl}
|
|
provider: aws
|
|
aws_region: us-west-2
|
|
template_parameters:
|
|
KeyName: default
|
|
AdminLocation: 0.0.0.0/0
|
|
PublicSlaveInstanceCount: 1
|
|
SlaveInstanceCount: 1
|
|
"""
|
|
}
|
|
|
|
|
|
/**
|
|
* This class abstracts away the functions required to create a test cluster
|
|
* for each of our platforms.
|
|
*/
|
|
class TestCluster implements Serializable {
|
|
WorkflowScript script
|
|
String platform
|
|
int createAttempts
|
|
|
|
TestCluster(WorkflowScript script, String platform) {
|
|
this.script = script
|
|
this.platform = platform
|
|
this.createAttempts = 0
|
|
}
|
|
|
|
/**
|
|
* Creates a new DC/OS cluster for the given platform using `dcos_launch`.
|
|
*/
|
|
def launch_create() {
|
|
script.sh "./dcos-launch create -c ${platform}_config.yaml -i ${platform}_cluster_info.json"
|
|
}
|
|
|
|
/**
|
|
* Waits for a cluster previously created with `dcos_launch` to come online.
|
|
*/
|
|
def launch_wait() {
|
|
script.sh "./dcos-launch wait -i ${platform}_cluster_info.json"
|
|
}
|
|
|
|
/**
|
|
* Deletes a cluster previously created using `dcos_launch`.
|
|
*/
|
|
def launch_delete() {
|
|
script.sh "./dcos-launch delete -i ${platform}_cluster_info.json"
|
|
}
|
|
|
|
/**
|
|
* Creates a new test cluster for the given platform.
|
|
*
|
|
* It first creates a custom config file with a new deployment name that
|
|
* matches the given platform and then uses `launch_create()` to actually
|
|
* create the cluster.
|
|
*/
|
|
def create() {
|
|
script.sh "rm -rf ${platform}_config.yaml"
|
|
script.sh "rm -rf ${platform}_cluster_info.json"
|
|
|
|
script.writeFile([
|
|
"file": "${platform}_config.yaml",
|
|
"text" : script.generateConfig(
|
|
"dcos-cli-${platform}-${script.env.BRANCH_NAME}-${script.env.BUILD_ID}-${createAttempts}",
|
|
"${script.env.CF_TEMPLATE_URL}")])
|
|
|
|
launch_create()
|
|
|
|
createAttempts++
|
|
}
|
|
|
|
/**
|
|
* Blocks until a test cluster successfully comes online or a user
|
|
* interrupts the build.
|
|
*
|
|
* Under the hood, `launch_create()` will be re-executed anytime a previous
|
|
* creation attempt fails. The only way to exit this loop is to either
|
|
* create a cluster successfully, or interrupt the build manually.
|
|
*/
|
|
def block() {
|
|
while (true) {
|
|
try {
|
|
launch_wait()
|
|
break
|
|
} catch(InterruptedException e) {
|
|
destroy()
|
|
script.echo("Build interrupted. Exiting...")
|
|
throw e
|
|
} catch(Exception e) {
|
|
destroy()
|
|
if (createAttempts < 3) {
|
|
create()
|
|
} else {
|
|
script.echo("Maximum number of creation attempts exceeded. Exiting...")
|
|
throw e
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroys a test cluster previously created using `create()`.
|
|
*/
|
|
def destroy() {
|
|
launch_delete()
|
|
}
|
|
|
|
/**
|
|
* Retreives the URL of a cluster previously created using `create()`.
|
|
*/
|
|
def getDcosUrl() {
|
|
/* In the future, consider doing the following with jq instead of
|
|
inline python (however, jq is not installed on our windows machines
|
|
at the moment). */
|
|
script.sh """
|
|
./dcos-launch describe -i ${platform}_cluster_info.json \
|
|
| python -c \
|
|
'import sys, json; \
|
|
contents = json.load(sys.stdin); \
|
|
print(contents["masters"][0]["public_ip"], end="")' \
|
|
> ${platform}_dcos_url"""
|
|
|
|
return script.readFile("${platform}_dcos_url")
|
|
}
|
|
|
|
/**
|
|
* Retreives the ACS Token of a cluster previously created using `create()`.
|
|
*/
|
|
def getAcsToken() {
|
|
def dcosUrl = this.getDcosUrl()
|
|
|
|
/* In the future, consider doing the following with curl / jq instead
|
|
of inline python (however, jq is not installed on our windows
|
|
machines at the moment). */
|
|
script.sh """
|
|
python -c \
|
|
'import requests; \
|
|
requests.packages.urllib3.disable_warnings(); \
|
|
js={"uid":"${script.env.DCOS_ADMIN_USERNAME}", \
|
|
"password": "${script.env.DCOS_ADMIN_PASSWORD}"}; \
|
|
r=requests.post("http://${dcosUrl}/acs/api/v1/auth/login", \
|
|
json=js, \
|
|
verify=False); \
|
|
print(r.json()["token"], end="")' \
|
|
> ${platform}_acs_token"""
|
|
|
|
return script.readFile("${platform}_acs_token")
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This function returns a closure that prepares binary builds for a specific
|
|
* platform on a specific node in a specific workspace.
|
|
*/
|
|
def binaryBuilder(String platform, String nodeId, String workspace = null) {
|
|
return { Closure _body ->
|
|
def body = _body
|
|
|
|
return {
|
|
node(nodeId) {
|
|
if (!workspace) {
|
|
workspace = "${env.WORKSPACE}"
|
|
}
|
|
|
|
ws (workspace) {
|
|
stage ('Cleanup workspace') {
|
|
deleteDir()
|
|
}
|
|
|
|
stage ("Unstash dcos-cli repository") {
|
|
unstash('dcos-cli')
|
|
}
|
|
|
|
body()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This function returns a closure that prepares a test environment for a
|
|
* specific platform on a specific node in a specific workspace.
|
|
*/
|
|
def testBuilder(String platform, String nodeId, String workspace = null) {
|
|
return { Closure _body ->
|
|
def body = _body
|
|
|
|
return {
|
|
def destroyCluster = true
|
|
def cluster = new TestCluster(this, platform)
|
|
|
|
try {
|
|
stage ("Create ${platform} cluster") {
|
|
cluster.create()
|
|
}
|
|
|
|
stage ("Wait for ${platform} cluster") {
|
|
cluster.block()
|
|
}
|
|
|
|
def dcosUrl = cluster.getDcosUrl()
|
|
def acsToken = cluster.getAcsToken()
|
|
|
|
node(nodeId) {
|
|
if (!workspace) {
|
|
workspace = "${env.WORKSPACE}"
|
|
}
|
|
|
|
ws (workspace) {
|
|
stage ('Cleanup workspace') {
|
|
deleteDir()
|
|
}
|
|
|
|
stage ("Unstash dcos-cli repository") {
|
|
unstash('dcos-cli')
|
|
}
|
|
|
|
withCredentials(
|
|
[[$class: 'FileBinding',
|
|
credentialsId: '1c206779-acc0-4844-97f6-7b3ed081a456',
|
|
variable: 'DCOS_SNAKEOIL_CRT_PATH'],
|
|
[$class: 'FileBinding',
|
|
credentialsId: '23743034-1ac4-49f7-b2e6-a661aee2d11b',
|
|
variable: 'CLI_TEST_SSH_KEY_PATH']]) {
|
|
|
|
withEnv(["DCOS_URL=${dcosUrl}",
|
|
"DCOS_ACS_TOKEN=${acsToken}"]) {
|
|
try {
|
|
body()
|
|
} catch (Exception e) {
|
|
echo(
|
|
"Build failed. The DC/OS cluster at" +
|
|
" ${dcosUrl} will remain temporarily" +
|
|
" active so you can debug what went" +
|
|
" wrong.")
|
|
destroyCluster = false
|
|
throw e
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
if (destroyCluster) {
|
|
stage ("Destroy ${platform} cluster") {
|
|
try { cluster.destroy() }
|
|
catch (Exception e) {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* These are the builds that can be run in parallel.
|
|
*/
|
|
def builders = [:]
|
|
|
|
|
|
builders['linux-binary'] = binaryBuilder('linux', 'py35', '/workspace')({
|
|
stage ("Build dcos-cli binary") {
|
|
dir('dcos-cli/cli') {
|
|
sh "make binary"
|
|
sh "dist/dcos"
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
builders['mac-binary'] = binaryBuilder('mac', 'mac')({
|
|
stage ("Build dcos-cli binary") {
|
|
dir('dcos-cli/cli') {
|
|
sh "make binary"
|
|
sh "dist/dcos"
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
builders['windows-binary'] = binaryBuilder('windows', 'windows')({
|
|
stage ("Build dcos-cli binary") {
|
|
dir('dcos-cli/cli') {
|
|
bat 'bash -c "make binary"'
|
|
bat 'dist\\dcos.exe'
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
builders['linux-tests'] = testBuilder('linux', 'py35', '/workspace')({
|
|
stage ("Run dcos-cli tests") {
|
|
sh '''
|
|
rm -rf ~/.dcos; \
|
|
grep -q "^.* dcos.snakeoil.mesosphere.com$" /etc/hosts && \
|
|
sed -iold "s/^.* dcos.snakeoil.mesosphere.com$/${DCOS_URL} dcos.snakeoil.mesosphere.com/" /etc/hosts || \
|
|
echo ${DCOS_URL} dcos.snakeoil.mesosphere.com >> /etc/hosts'''
|
|
|
|
dir('dcos-cli/cli') {
|
|
sh '''
|
|
export PYTHONIOENCODING=utf-8; \
|
|
export DCOS_CONFIG=tests/data/dcos.toml; \
|
|
chmod 600 ${DCOS_CONFIG}; \
|
|
echo dcos_acs_token = \\\"${DCOS_ACS_TOKEN}\\\" >> ${DCOS_CONFIG}; \
|
|
cat ${DCOS_CONFIG}; \
|
|
unset DCOS_URL; \
|
|
unset DCOS_ACS_TOKEN; \
|
|
make test-binary'''
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
builders['mac-tests'] = testBuilder('mac', 'mac')({
|
|
stage ("Run dcos-cli tests") {
|
|
sh '''
|
|
rm -rf ~/.dcos; \
|
|
cp /etc/hosts hosts.local; \
|
|
grep -q "^.* dcos.snakeoil.mesosphere.com$" hosts.local && \
|
|
sed -iold "s/^.* dcos.snakeoil.mesosphere.com$/${DCOS_URL} dcos.snakeoil.mesosphere.com/" hosts.local || \
|
|
echo ${DCOS_URL} dcos.snakeoil.mesosphere.com >> hosts.local; \
|
|
sudo cp ./hosts.local /etc/hosts'''
|
|
|
|
dir('dcos-cli/cli') {
|
|
sh '''
|
|
export PYTHONIOENCODING=utf-8; \
|
|
export DCOS_CONFIG=tests/data/dcos.toml; \
|
|
chmod 600 ${DCOS_CONFIG}; \
|
|
echo dcos_acs_token = \\\"${DCOS_ACS_TOKEN}\\\" >> ${DCOS_CONFIG}; \
|
|
cat ${DCOS_CONFIG}; \
|
|
unset DCOS_URL; \
|
|
unset DCOS_ACS_TOKEN; \
|
|
make test-binary'''
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
builders['windows-tests'] = testBuilder('windows', 'windows', 'C:\\windows\\workspace')({
|
|
stage ("Run dcos-cli tests") {
|
|
bat '''
|
|
bash -c "rm -rf ~/.dcos"'''
|
|
bat '''
|
|
echo %DCOS_URL% dcos.snakeoil.mesosphere.com >> C:\\windows\\system32\\drivers\\etc\\hosts &
|
|
echo dcos_acs_token = \"%DCOS_ACS_TOKEN%\" >> dcos-cli\\cli\\tests\\data\\dcos.toml'''
|
|
|
|
dir('dcos-cli/cli') {
|
|
bat '''
|
|
bash -c " \
|
|
export PYTHONIOENCODING=utf-8; \
|
|
export DCOS_CONFIG=tests/data/dcos.toml; \
|
|
cat ${DCOS_CONFIG}; \
|
|
unset DCOS_URL; \
|
|
unset DCOS_ACS_TOKEN; \
|
|
make test-binary"'''
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
/**
|
|
* This node bootstraps everything including creating all the test clusters,
|
|
* starting the builders, and finally destroying all the clusters once they
|
|
* are done.
|
|
*/
|
|
node('py35') {
|
|
stage ('Cleanup workspace') {
|
|
deleteDir()
|
|
}
|
|
|
|
stage ('Update node') {
|
|
sh 'pip install requests'
|
|
}
|
|
|
|
stage ('Download dcos-launch') {
|
|
sh 'wget https://downloads.dcos.io/dcos-test-utils/bin/linux/dcos-launch'
|
|
sh 'chmod a+x dcos-launch'
|
|
}
|
|
|
|
stage ('Pull dcos-cli repository') {
|
|
dir('dcos-cli') {
|
|
checkout scm
|
|
}
|
|
}
|
|
|
|
stage ('Stash dcos-cli repository') {
|
|
stash(['includes': 'dcos-cli/**', name: 'dcos-cli'])
|
|
}
|
|
|
|
withCredentials(
|
|
[[$class: 'AmazonWebServicesCredentialsBinding',
|
|
credentialsId: '7155bd15-767d-4ae3-a375-e0d74c90a2c4',
|
|
accessKeyVariable: 'AWS_ACCESS_KEY_ID',
|
|
secretKeyVariable: 'AWS_SECRET_ACCESS_KEY'],
|
|
[$class: 'StringBinding',
|
|
credentialsId: 'fd1fe0ae-113d-4096-87b2-15aa9606bb4e',
|
|
variable: 'CF_TEMPLATE_URL'],
|
|
[$class: 'UsernamePasswordMultiBinding',
|
|
credentialsId: '323df884-742b-4099-b8b7-d764e5eb9674',
|
|
usernameVariable: 'DCOS_ADMIN_USERNAME',
|
|
passwordVariable: 'DCOS_ADMIN_PASSWORD']]) {
|
|
|
|
parallel builders
|
|
}
|
|
}
|