|
|
|
@ -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; |