Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.netflix.spinnaker.igor.build.BuildController.groovy Maven / Gradle / Ivy
/*
* Copyright 2015 Netflix, Inc.
* Copyright (c) 2017, 2018, Oracle Corporation and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.spinnaker.igor.build
import com.google.common.base.Strings
import com.netflix.spinnaker.igor.PendingOperationsCache
import com.netflix.spinnaker.igor.artifacts.ArtifactExtractor
import com.netflix.spinnaker.igor.build.model.GenericBuild
import com.netflix.spinnaker.igor.build.model.UpdatedBuild
import com.netflix.spinnaker.igor.exceptions.BuildJobError
import com.netflix.spinnaker.igor.exceptions.QueuedJobDeterminationError
import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig
import com.netflix.spinnaker.igor.jenkins.service.JenkinsService
import com.netflix.spinnaker.igor.service.ArtifactDecorator
import com.netflix.spinnaker.igor.service.BuildOperations
import com.netflix.spinnaker.igor.service.BuildProperties
import com.netflix.spinnaker.igor.service.BuildServices
import com.netflix.spinnaker.kork.artifacts.model.Artifact
import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException
import com.netflix.spinnaker.kork.web.exceptions.NotFoundException
import com.netflix.spinnaker.security.AuthenticatedRequest
import groovy.transform.InheritConstructors
import groovy.util.logging.Slf4j
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.HandlerMapping
import retrofit.RetrofitError
import retrofit.http.Query
import javax.annotation.Nullable
import javax.servlet.http.HttpServletRequest
import static net.logstash.logback.argument.StructuredArguments.kv
import static org.springframework.http.HttpStatus.NOT_FOUND
@Slf4j
@RestController
class BuildController {
private BuildServices buildServices
private BuildArtifactFilter buildArtifactFilter
private ArtifactDecorator artifactDecorator
private ArtifactExtractor artifactExtractor
private PendingOperationsCache pendingOperationsCache
BuildController(BuildServices buildServices,
PendingOperationsCache pendingOperationsCache,
Optional buildArtifactFilter,
Optional artifactDecorator,
Optional artifactExtractor) {
this.buildServices = buildServices
this.pendingOperationsCache = pendingOperationsCache
this.buildArtifactFilter = buildArtifactFilter.orElse(null)
this.artifactDecorator = artifactDecorator.orElse(null)
this.artifactExtractor = artifactExtractor.orElse(null)
}
@Nullable
private GenericBuild jobStatus(BuildOperations buildService, String master, String job, Integer buildNumber) {
GenericBuild build = buildService.getGenericBuild(job, buildNumber)
if (!build)
return null
try {
build.genericGitRevisions = buildService.getGenericGitRevisions(job, build)
} catch (Exception e) {
log.error("could not get scm results for {} / {} / {}", kv("master", master), kv("job", job), kv("buildNumber", buildNumber), e)
}
if (artifactDecorator) {
artifactDecorator.decorate(build)
}
if (buildArtifactFilter) {
build.artifacts = buildArtifactFilter.filterArtifacts(build.artifacts)
}
return build
}
@RequestMapping(value = '/builds/status/{buildNumber}/{master:.+}/**')
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'READ')")
GenericBuild getJobStatus(@PathVariable String master, @PathVariable
Integer buildNumber, HttpServletRequest request) {
def job = ((String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).split('/').drop(5).join('/')
def buildService = getBuildService(master)
return jobStatus(buildService, master, job, buildNumber)
}
@RequestMapping(value = '/builds/artifacts/{buildNumber}/{master:.+}/**')
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'READ')")
List getBuildResults(@PathVariable String master, @PathVariable
Integer buildNumber, @Query("propertyFile") String propertyFile, HttpServletRequest request) {
def job = ((String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).split('/').drop(5).join('/')
def buildService = getBuildService(master)
GenericBuild build = jobStatus(buildService, master, job, buildNumber)
if (build && buildService instanceof BuildProperties && artifactExtractor != null) {
build.properties = buildService.getBuildProperties(job, build, propertyFile)
return artifactExtractor.extractArtifacts(build)
}
return Collections.emptyList()
}
@RequestMapping(value = '/builds/queue/{master}/{item}')
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'READ')")
Object getQueueLocation(@PathVariable String master, @PathVariable int item) {
def buildService = getBuildService(master)
return buildService.queuedBuild(master, item)
}
@RequestMapping(value = '/builds/all/{master:.+}/**')
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'READ')")
List getBuilds(@PathVariable String master, HttpServletRequest request) {
def job = ((String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).split('/').drop(4).join('/')
def buildService = getBuildService(master)
return buildService.getBuilds(job)
}
@RequestMapping(value = "/masters/{name}/jobs/{jobName}/stop/{queuedBuild}/{buildNumber}", method = RequestMethod.PUT)
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'WRITE')")
String stop(
@PathVariable("name") String master,
@PathVariable String jobName,
@PathVariable String queuedBuild,
@PathVariable Integer buildNumber) {
stopJob(master, buildNumber, jobName, queuedBuild)
"true"
}
@RequestMapping(value = "/masters/{master}/jobs/stop/{queuedBuild}/{buildNumber}", method = RequestMethod.PUT)
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'WRITE')")
String stopWithQueryParam(
@PathVariable String master,
@RequestParam String jobName,
@PathVariable String queuedBuild,
@PathVariable Integer buildNumber) {
stopJob(master, buildNumber, jobName, queuedBuild)
"true"
}
void stopJob(String master, int buildNumber, String jobName, String queuedBuild) {
def buildService = getBuildService(master)
if (buildService instanceof JenkinsService) {
// Jobs that haven't been started yet won't have a buildNumber
// (They're still in the queue). We use 0 to denote that case
if (buildNumber != 0 &&
buildService.metaClass.respondsTo(buildService, 'stopRunningBuild')) {
buildService.stopRunningBuild(jobName, buildNumber)
} else {
// The jenkins api for removing a job from the queue (http:///queue/cancelItem?id=)
// always returns a 404. This try catch block insures that the exception is eaten instead
// of being handled by the handleOtherException handler and returning a 500 to orca
try {
if (buildService.metaClass.respondsTo(buildService, 'stopQueuedBuild')) {
buildService.stopQueuedBuild(queuedBuild)
}
} catch (RetrofitError e) {
if (e.response?.status != NOT_FOUND.value()) {
throw e
}
}
}
}
}
@RequestMapping(value = "/masters/{name}/jobs/**/update/{buildNumber}", method = RequestMethod.PATCH)
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'WRITE')")
void update(
@PathVariable("name") String master,
@PathVariable("buildNumber") Integer buildNumber,
@RequestBody UpdatedBuild updatedBuild,
HttpServletRequest request
) {
def jobName = ((String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE))
.split('/')
.drop(4)
.dropRight(2)
.join('/')
def buildService = getBuildService(master)
buildService.updateBuild(jobName, buildNumber, updatedBuild)
}
@RequestMapping(value = '/masters/{name}/jobs/**', method = RequestMethod.PUT)
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'WRITE')")
ResponseEntity build(
@PathVariable("name") String master,
@RequestParam Map requestParams,
@RequestBody(required = false) String startTime,
HttpServletRequest request) {
def job = ((String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).split('/').drop(4).join('/')
String pendingKey = computePendingBuildKey(master, job, requestParams, startTime)
// Initializing buildNumber to null will get it silently casted to "null" down the line
String buildNumber = ""
PendingOperationsCache.OperationState pendingStatus = pendingOperationsCache.getAndSetOperationStatus(pendingKey, PendingOperationsCache.OperationStatus.PENDING, "")
if (pendingStatus.status == PendingOperationsCache.OperationStatus.PENDING) {
log.info("Received duplicate request to the start job {}, status: {}, pendingKey: {}", job,
pendingStatus.status, pendingKey)
return ResponseEntity.accepted().build()
}
if (pendingStatus.status == PendingOperationsCache.OperationStatus.COMPLETED && !Strings.isNullOrEmpty(pendingStatus.value)) {
log.info("Received duplicate request to the start job {}, status: {}, pendingKey: {}", job,
pendingStatus.status, pendingKey)
pendingOperationsCache.clear(pendingKey)
return ResponseEntity.of(Optional.of(pendingStatus.value))
}
try {
def buildService = getBuildService(master)
if (buildService instanceof JenkinsService) {
def response
JenkinsService jenkinsService = (JenkinsService) buildService
JobConfig jobConfig = jenkinsService.getJobConfig(job)
if (!jobConfig.buildable) {
throw new BuildJobError("Job '${job}' is not buildable. It may be disabled.")
}
if (jobConfig.parameterDefinitionList?.size() > 0) {
validateJobParameters(jobConfig, requestParams)
}
if (requestParams && jobConfig.parameterDefinitionList?.size() > 0) {
response = jenkinsService.buildWithParameters(job, requestParams)
} else if (!requestParams && jobConfig.parameterDefinitionList?.size() > 0) {
// account for when you just want to fire a job with the default parameter values by adding a dummy param
response = jenkinsService.buildWithParameters(job, ['startedBy': "igor"])
} else if (!requestParams && (!jobConfig.parameterDefinitionList || jobConfig.parameterDefinitionList.size() == 0)) {
response = jenkinsService.build(job)
} else { // Jenkins will reject the build, so don't even try
// we should throw a BuildJobError, but I get a bytecode error : java.lang.VerifyError: Bad method call from inside of a branch
throw new RuntimeException("job : ${job}, passing params to a job which doesn't need them")
}
if (response.status != 201) {
throw new BuildJobError("Received a non-201 status when submitting job '${job}' to master '${master}'")
}
log.info("Submitted build job '{}'", kv("job", job))
def locationHeader = response.headers.find { it.name.toLowerCase() == "location" }
if (!locationHeader) {
throw new QueuedJobDeterminationError("Could not find Location header for job '${job}'")
}
def queuedLocation = locationHeader.value
buildNumber = queuedLocation.split('/')[-1]
} else {
buildNumber = buildService.triggerBuildWithParameters(job, requestParams)
}
}
finally {
pendingOperationsCache.setOperationStatus(pendingKey, PendingOperationsCache.OperationStatus.COMPLETED, buildNumber)
}
return ResponseEntity.of(Optional.of(buildNumber))
}
static void validateJobParameters(JobConfig jobConfig, Map requestParams) {
jobConfig.parameterDefinitionList.each { parameterDefinition ->
String matchingParam = requestParams[parameterDefinition.name]
if (matchingParam != null &&
parameterDefinition.type == 'ChoiceParameterDefinition' &&
parameterDefinition.choices != null &&
!parameterDefinition.choices.contains(matchingParam)) {
throw new InvalidJobParameterException("`${matchingParam}` is not a valid choice " +
"for `${parameterDefinition.name}`. Valid choices are: ${parameterDefinition.choices.join(', ')}")
}
}
}
static String computePendingBuildKey(String master, String job, Map requestParams, String startTime) {
String key = master + ":" + job + ":" + AuthenticatedRequest.getSpinnakerExecutionId().orElse("NO_EXECUTION_ID")
if (startTime != null && !startTime.isEmpty()) {
key = key + ":startTime=" + startTime
}
requestParams.each { parameterDefinition ->
key = key + ":" + parameterDefinition.key + "=" + parameterDefinition.value
}
return key
}
@RequestMapping(value = '/builds/properties/{buildNumber}/{fileName}/{master:.+}/**')
@PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'READ')")
Map getProperties(
@PathVariable String master,
@PathVariable Integer buildNumber, @PathVariable
String fileName, HttpServletRequest request) {
def job = ((String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).split('/').drop(6).join('/')
def buildService = getBuildService(master)
if (buildService instanceof BuildProperties) {
BuildProperties buildProperties = (BuildProperties) buildService
def genericBuild = buildService.getGenericBuild(job, buildNumber)
return buildProperties.getBuildProperties(job, genericBuild, fileName)
}
return Collections.emptyMap()
}
private BuildOperations getBuildService(String master) {
def buildService = buildServices.getService(master)
if (buildService == null) {
throw new NotFoundException("Master '${master}' not found")
}
return buildService
}
@InheritConstructors
static class InvalidJobParameterException extends InvalidRequestException {}
}