5 changed files with 375 additions and 0 deletions
@ -0,0 +1,296 @@ |
|||||
|
#!groovy |
||||
|
|
||||
|
def importOpenAPI(Map conf) { |
||||
|
assert conf.destination != null |
||||
|
assert conf.baseSystemName != null |
||||
|
assert conf.oasFile != null |
||||
|
|
||||
|
// Read the OpenAPI Specification file |
||||
|
def openAPI = readOpenAPISpecificationFile(conf.oasFile) |
||||
|
assert openAPI.swagger == "2.0" |
||||
|
def version = openAPI.info.version |
||||
|
assert version != null |
||||
|
def major = version.tokenize(".")[0] |
||||
|
def baseName = basename(conf.oasFile) |
||||
|
|
||||
|
// Compute the target system_name |
||||
|
def targetSystemName = (conf.environmentName != null ? "${conf.environmentName}_" : "") + conf.baseSystemName + "_${major}" |
||||
|
|
||||
|
def commandLine = "3scale import openapi -t ${targetSystemName} -d ${conf.destination} /artifacts/${baseName}" |
||||
|
def result = runToolbox(commandLine: commandLine, |
||||
|
jobName: "import", |
||||
|
openAPI: [ |
||||
|
"filename": baseName, |
||||
|
"content": readFile(conf.oasFile) |
||||
|
], |
||||
|
toolboxConfig: conf.toolboxConfig) |
||||
|
echo result.stdout |
||||
|
} |
||||
|
|
||||
|
def basename(path) { |
||||
|
return path.drop(path.lastIndexOf("/") != -1 ? path.lastIndexOf("/") : 0) |
||||
|
} |
||||
|
|
||||
|
def readOpenAPISpecificationFile(fileName) { |
||||
|
if (fileName.toLowerCase().endsWith(".json")) { |
||||
|
return readJSON(file: fileName) |
||||
|
} else if (fileName.toLowerCase().endsWith(".yaml") || fileName.toLowerCase().endsWith(".yml")) { |
||||
|
return readYaml(file: fileName) |
||||
|
} else { |
||||
|
throw new Exception("Can't decide between JSON and YAML on ${fileName}") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def getToolboxVersion() { |
||||
|
def result = runToolbox(commandLine: "3scale -v", |
||||
|
jobName: "version") |
||||
|
return result.stdout |
||||
|
} |
||||
|
|
||||
|
def generateRandomBaseSystemName() { |
||||
|
String alphabet = (('A'..'N')+('P'..'Z')+('a'..'k')+('m'..'z')+('2'..'9')).join() |
||||
|
def length = 6 |
||||
|
id = new Random().with { |
||||
|
(1..length).collect{ alphabet[nextInt(alphabet.length())] }.join() |
||||
|
} |
||||
|
return "testcase_${id}" |
||||
|
} |
||||
|
|
||||
|
def runToolbox(Map conf) { |
||||
|
def result = null |
||||
|
|
||||
|
assert conf.jobName != null |
||||
|
assert conf.commandLine != null |
||||
|
|
||||
|
def defaultToolboxConf = [ |
||||
|
"toolboxConfig": null, |
||||
|
"openAPI": null, |
||||
|
"image": "quay.io/redhat/3scale-toolbox:v0.10.0", |
||||
|
"backoffLimit": 2, // three attempts (one first try + two retries) |
||||
|
"imagePullPolicy": "IfNotPresent", |
||||
|
"activeDeadlineSeconds": 90 |
||||
|
] |
||||
|
|
||||
|
// Apply default values |
||||
|
conf = defaultToolboxConf + conf |
||||
|
|
||||
|
if (conf.toolboxConfig != null && conf.toolboxConfig.configFileId != null) { |
||||
|
// Generate a default secret name if none has been provided |
||||
|
if (conf.toolboxConfig.secretName == null) { |
||||
|
conf.toolboxConfig = [ |
||||
|
"configFileId": conf.toolboxConfig.configFileId, |
||||
|
"secretName": "3scale-toolbox-${JOB_BASE_NAME}" |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
echo "Creating a secret named ${conf.toolboxConfig.secretName} containing file ${conf.toolboxConfig.configFileId}..." |
||||
|
configFileProvider([configFile(fileId: conf.toolboxConfig.configFileId, variable: 'TOOLBOX_CONFIG')]) { |
||||
|
def toolboxConfig = readFile(TOOLBOX_CONFIG) |
||||
|
createSecret(conf.toolboxConfig.secretName, [ ".3scalerc.yaml": toolboxConfig ]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def oasConfigMapName = null |
||||
|
if (conf.openAPI != null) { |
||||
|
oasConfigMapName = "3scale-toolbox-${JOB_BASE_NAME}-${BUILD_NUMBER}-openapi" |
||||
|
echo "Creating a configMap named ${oasConfigMapName} containing the OpenAPI file..." |
||||
|
createConfigMap(oasConfigMapName, [ (conf.openAPI.filename): conf.openAPI.content ]) |
||||
|
} |
||||
|
|
||||
|
def jobName = "${JOB_BASE_NAME}-${BUILD_NUMBER}-${conf.jobName}" |
||||
|
def jobSpecs = [ |
||||
|
"apiVersion": "batch/v1", |
||||
|
"kind": "Job", |
||||
|
"metadata": [ |
||||
|
"name": jobName, |
||||
|
"labels": [ |
||||
|
"build": "${JOB_BASE_NAME}-${BUILD_NUMBER}", |
||||
|
"job": "${JOB_BASE_NAME}" |
||||
|
] |
||||
|
], |
||||
|
"spec": [ |
||||
|
"backoffLimit": conf.backoffLimit, |
||||
|
"activeDeadlineSeconds": conf.activeDeadlineSeconds, |
||||
|
"template": [ |
||||
|
"spec": [ |
||||
|
"restartPolicy": "Never", |
||||
|
"containers": [ |
||||
|
[ |
||||
|
"name": "job", |
||||
|
"image": conf.image, |
||||
|
"imagePullPolicy": conf.imagePullPolicy, |
||||
|
"command": [ "scl", "enable", "rh-ruby25", "/opt/rh/rh-ruby25/root/usr/local/bin/${conf.commandLine}" ], |
||||
|
"volumeMounts": [ |
||||
|
[ |
||||
|
"mountPath": "/opt/app-root/src/", |
||||
|
"name": "toolbox-config" |
||||
|
], |
||||
|
[ |
||||
|
"mountPath": "/artifacts", |
||||
|
"name": "artifacts" |
||||
|
] |
||||
|
|
||||
|
] |
||||
|
] |
||||
|
], |
||||
|
"volumes": [ |
||||
|
[ |
||||
|
"name": "toolbox-config" |
||||
|
], |
||||
|
[ |
||||
|
"name": "artifacts" |
||||
|
] |
||||
|
] |
||||
|
] |
||||
|
] |
||||
|
] |
||||
|
] |
||||
|
|
||||
|
// Inject the toolbox configuration as a volume |
||||
|
if (conf.toolboxConfig != null && conf.toolboxConfig.secretName != null) { |
||||
|
jobSpecs.spec.template.spec.volumes[0].secret = [ |
||||
|
"secretName": conf.toolboxConfig.secretName |
||||
|
] |
||||
|
} else { |
||||
|
jobSpecs.spec.template.spec.volumes[0].emptyDir = [:] |
||||
|
} |
||||
|
|
||||
|
// Inject the OpenAPI file as a volume |
||||
|
if (oasConfigMapName != null) { |
||||
|
jobSpecs.spec.template.spec.volumes[1].configMap = [ |
||||
|
"name": oasConfigMapName |
||||
|
] |
||||
|
} else { |
||||
|
jobSpecs.spec.template.spec.volumes[1].emptyDir = [:] |
||||
|
} |
||||
|
|
||||
|
def job = null |
||||
|
try { |
||||
|
job = openshift.create(jobSpecs) |
||||
|
|
||||
|
int jobTimeout = 2 + (int)(conf.activeDeadlineSeconds / 60.0f) |
||||
|
echo "Waiting ${jobTimeout} minutes for the job to complete..." |
||||
|
timeout(jobTimeout) { |
||||
|
// Wait for the job to complete, either Succeeded or Failed |
||||
|
job.watch { |
||||
|
def jobStatus = getJobStatus(it.object()) |
||||
|
echo "Job ${it.name()}: succeeded = ${jobStatus.succeeded}, failed = ${jobStatus.failed}, status = ${jobStatus.status}, reason = ${jobStatus.reason}" |
||||
|
|
||||
|
// Exit the watch loop when the Job has one successful pod or failed |
||||
|
return jobStatus.succeeded > 0 || jobStatus.status == "Failed" |
||||
|
} |
||||
|
} |
||||
|
} finally { |
||||
|
if (job != null) { |
||||
|
def jobStatus = getJobStatus(job.object()) |
||||
|
echo "job ${job.name()} has status '${jobStatus.status}' and reason '${jobStatus.reason}'" |
||||
|
|
||||
|
// Iterate over pods to find: |
||||
|
// - the pod that succeeded |
||||
|
// - as last resort, a pod that failed |
||||
|
def pods = job.related("pod") |
||||
|
pods.withEach { |
||||
|
if (it.object().status.phase == "Succeeded") { |
||||
|
result = getPodDetails(it) |
||||
|
} |
||||
|
if (it.object().status.phase == "Failed" && result == null) { |
||||
|
result = getPodDetails(it) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (result != null && result.podPhase == "Failed") { |
||||
|
echo "RC: ${result.status}" |
||||
|
echo "STDOUT:" |
||||
|
echo "-------" |
||||
|
echo result.stdout |
||||
|
echo "STDERR:" |
||||
|
echo "-------" |
||||
|
echo result.stderr |
||||
|
|
||||
|
error("job ${job.name()} exited with '${jobStatus.status}' and reason '${jobStatus.reason}'") |
||||
|
} |
||||
|
|
||||
|
// Delete the job |
||||
|
try { |
||||
|
openshift.selector('job', jobName).delete() |
||||
|
} catch (e2) { // Best effort |
||||
|
echo "cannot delete the job ${jobName}: ${e2}" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Delete the temporary configMap containing the OAS file |
||||
|
if (oasConfigMapName != null) { |
||||
|
try { |
||||
|
openshift.selector('configMap', oasConfigMapName).delete() |
||||
|
} catch (e2) { // Best effort |
||||
|
echo "cannot delete the configMap ${oasConfigMapName}: ${e2}" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Delete the temporary secret |
||||
|
if (conf.toolboxConfig != null && conf.toolboxConfig.configFileId != null) { |
||||
|
try { |
||||
|
openshift.selector('secret', conf.toolboxConfig.secretName).delete() |
||||
|
} catch (e2) { // Best effort |
||||
|
echo "cannot delete the secret ${conf.toolboxConfig.secretName}: ${e2}" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
def getJobStatus(obj) { |
||||
|
return [ |
||||
|
"succeeded": obj.status.succeeded != null ? obj.status.succeeded : 0, |
||||
|
"failed": obj.status.failed != null ? obj.status.failed : 0, |
||||
|
"status": obj.status.conditions != null && obj.status.conditions.size() > 0 ? obj.status.conditions[0].type : "Unknown", |
||||
|
"reason": obj.status.conditions != null && obj.status.conditions.size() > 0 ? obj.status.conditions[0].reason : "" |
||||
|
] |
||||
|
} |
||||
|
def getPodDetails(pod) { |
||||
|
def logs = pod.logs() |
||||
|
return [ |
||||
|
"status": logs.actions[0].status, |
||||
|
"stdout": logs.actions[0].out, |
||||
|
"stderr": logs.actions[0].err, |
||||
|
"podPhase": pod.object().status.phase, |
||||
|
"podName": pod.name() |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
def createConfigMap(configMapName, content) { |
||||
|
def configMapSpecs = [ |
||||
|
"apiVersion": "v1", |
||||
|
"kind": "ConfigMap", |
||||
|
"metadata": [ |
||||
|
"name": "${configMapName}", |
||||
|
"labels": [ |
||||
|
"job": "${JOB_BASE_NAME}", |
||||
|
"build": "${JOB_BASE_NAME}-${BUILD_NUMBER}" |
||||
|
] |
||||
|
], |
||||
|
"data": [:] |
||||
|
] |
||||
|
content.each{ k, v -> configMapSpecs.data[k] = v } |
||||
|
openshift.apply(configMapSpecs) |
||||
|
} |
||||
|
|
||||
|
def createSecret(secretName, content) { |
||||
|
def secretSpecs = [ |
||||
|
"apiVersion": "v1", |
||||
|
"kind": "Secret", |
||||
|
"metadata": [ |
||||
|
"name": "${secretName}", |
||||
|
"labels": [ |
||||
|
"job": "${JOB_BASE_NAME}" |
||||
|
] |
||||
|
], |
||||
|
"stringData": [:] |
||||
|
] |
||||
|
content.each{ k, v -> secretSpecs.stringData[k] = v } |
||||
|
openshift.apply(secretSpecs) |
||||
|
} |
||||
|
|
||||
|
// required to be loaded from a jenkins pipeline |
||||
|
return this; |
||||
@ -0,0 +1,13 @@ |
|||||
|
# API Lifecycle Mockup |
||||
|
|
||||
|
## Setup |
||||
|
|
||||
|
```sh |
||||
|
oc project api-lifecycle |
||||
|
3scale remote add $NAME https://$TOKEN@$TENANT.3scale.net/ |
||||
|
oc create secret generic 3scale-toolbox --from-file=~/.3scalerc.yaml |
||||
|
``` |
||||
|
|
||||
|
```sh |
||||
|
oc process -f testcase-01/setup.yaml |oc create -f - |
||||
|
``` |
||||
@ -0,0 +1,32 @@ |
|||||
|
#!groovy |
||||
|
|
||||
|
def toolbox = load '../3scale_toolbox.groovy' |
||||
|
def toolboxConfig = [ |
||||
|
"secretName": params.SECRET_NAME |
||||
|
] |
||||
|
def baseSystemName = toolbox.generateRandomBaseSystemName() |
||||
|
|
||||
|
node() { |
||||
|
stage('Checkout Source') { |
||||
|
checkout scm |
||||
|
} |
||||
|
|
||||
|
stage("Get toolbox version") { |
||||
|
openshift.withCluster() { |
||||
|
openshift.withProject(params.NAMESPACE) { |
||||
|
echo "toolbox version = " + toolbox.getToolboxVersion() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stage("Import OpenAPI") { |
||||
|
openshift.withCluster() { |
||||
|
openshift.withProject(params.NAMESPACE) { |
||||
|
toolbox.importOpenAPI(destination: params.TARGET_INSTANCE, |
||||
|
toolboxConfig: toolboxConfig, |
||||
|
oasFile: "swagger.json", |
||||
|
baseSystemName: baseSystemName) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
apiVersion: v1 |
||||
|
kind: Template |
||||
|
metadata: |
||||
|
name: testcase-01 |
||||
|
objects: |
||||
|
- kind: "BuildConfig" |
||||
|
apiVersion: "v1" |
||||
|
metadata: |
||||
|
name: "testcase-01" |
||||
|
namespace: ${NAMESPACE} |
||||
|
spec: |
||||
|
source: |
||||
|
git: |
||||
|
uri: ${GIT_REPO} |
||||
|
strategy: |
||||
|
type: "JenkinsPipeline" |
||||
|
jenkinsPipelineStrategy: |
||||
|
jenkinsfilePath: testcase-01/Jenkinsfile |
||||
|
env: |
||||
|
- name: SECRET_NAME |
||||
|
value: ${SECRET_NAME} |
||||
|
- name: NAMESPACE |
||||
|
value: ${NAMESPACE} |
||||
|
- name: TARGET_INSTANCE |
||||
|
value: ${TARGET_INSTANCE} |
||||
|
parameters: |
||||
|
- name: SECRET_NAME |
||||
|
value: 3scale-toolbox |
||||
|
- name: NAMESPACE |
||||
|
value: api-lifecycle |
||||
|
- name: TARGET_INSTANCE |
||||
|
value: nmasse-redhat |
||||
|
- name: GIT_REPO |
||||
|
value: https://github.com/nmasse-itix/API-Lifecycle-Mockup.git |
||||
Loading…
Reference in new issue