Skip to content

Commit

Permalink
feat(jenkins): Support for updating job description (#924)
Browse files Browse the repository at this point in the history
This supports `orca` updating the description of a
running/completed job (cannot be queued) and deep linking
back to the execution view.

```
curl -X "PATCH" "https://localhost:8085/masters/my_master/jobs/my_job_name/update/553" \
     -H 'Content-Type: application/json' \
     -d $'{
  "description": "This build was triggered by a super swsssssseet <a href=\\"https://spinnaker/#/applications/my_app/executions/details/01EX5E3FT7PVSRNGS44EHCDPDF\\">Spinnaker</a>."
}'
```
  • Loading branch information
ajordens committed Jan 29, 2021
1 parent fd0a0fb commit 04b5627
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021 Netflix, Inc.
*
* 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.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UpdatedBuild {
private String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.netflix.spinnaker.igor.build.model.GenericBuild;
import com.netflix.spinnaker.igor.build.model.GenericGitRevision;
import com.netflix.spinnaker.igor.build.model.JobConfiguration;
import com.netflix.spinnaker.igor.build.model.UpdatedBuild;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -63,6 +64,17 @@ public interface BuildOperations extends BuildService {
*/
List<?> getBuilds(String job);

/**
* Updates attributes of a build, support varies across across CI systems
*
* @param jobName The name of the job
* @param buildNumber The build number
* @param updatedBuild The updated details for the build
*/
default void updateBuild(String jobName, Integer buildNumber, UpdatedBuild updatedBuild) {
// not supported by default
}

JobConfiguration getJobConfig(String jobName);

default Object queuedBuild(String master, int item) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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
Expand Down Expand Up @@ -171,6 +172,23 @@ class BuildController {
"true"
}

@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')")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ interface JenkinsClient {
@POST('/job/{jobName}/buildWithParameters')
Response buildWithParameters(@EncodedPath('jobName') String jobName, @QueryMap Map<String, String> queryParams, @Body String EmptyRequest, @Header("Jenkins-Crumb") String crumb)

@FormUrlEncoded
@POST('/job/{jobName}/{buildNumber}/submitDescription')
Response submitDescription(@EncodedPath('jobName') String jobName, @Path('buildNumber') Integer buildNumber, @Field("description") String description, @Header("Jenkins-Crumb") String crumb)

@POST('/job/{jobName}/{buildNumber}/stop')
Response stopRunningBuild(@EncodedPath('jobName') String jobName, @Path('buildNumber') Integer buildNumber, @Body String EmptyRequest, @Header("Jenkins-Crumb") String crumb)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.netflix.spinnaker.fiat.model.resources.Permissions;
import com.netflix.spinnaker.igor.build.model.GenericBuild;
import com.netflix.spinnaker.igor.build.model.GenericGitRevision;
import com.netflix.spinnaker.igor.build.model.UpdatedBuild;
import com.netflix.spinnaker.igor.exceptions.ArtifactNotFoundException;
import com.netflix.spinnaker.igor.exceptions.BuildJobError;
import com.netflix.spinnaker.igor.exceptions.QueuedJobDeterminationError;
Expand Down Expand Up @@ -258,6 +259,16 @@ public Response buildWithParameters(String jobName, Map<String, String> queryPar
() -> jenkinsClient.buildWithParameters(encode(jobName), queryParams, "", getCrumb()));
}

@Override
public void updateBuild(String jobName, Integer buildNumber, UpdatedBuild updatedBuild) {
if (updatedBuild.getDescription() != null) {
circuitBreaker.executeRunnable(
() ->
jenkinsClient.submitDescription(
encode(jobName), buildNumber, updatedBuild.getDescription(), getCrumb()));
}
}

@Override
public JobConfig getJobConfig(String jobName) {
return circuitBreaker.executeSupplier(() -> jenkinsClient.getJobConfig(encode(jobName)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.netflix.spinnaker.igor.build

import com.netflix.spinnaker.igor.PendingOperationsCache
import com.netflix.spinnaker.igor.build.model.GenericBuild
import com.netflix.spinnaker.igor.build.model.UpdatedBuild
import com.netflix.spinnaker.igor.config.JenkinsConfig
import com.netflix.spinnaker.igor.jenkins.client.model.Build
import com.netflix.spinnaker.igor.jenkins.client.model.BuildArtifact
Expand Down Expand Up @@ -46,6 +47,7 @@ import spock.lang.Specification
import static com.netflix.spinnaker.igor.build.BuildController.InvalidJobParameterException
import static com.netflix.spinnaker.igor.build.BuildController.validateJobParameters
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

Expand Down Expand Up @@ -402,4 +404,28 @@ class BuildControllerSpec extends Specification {
response.contentAsString == BUILD_NUMBER.toString()
1 * pendingOperationService.clear("${JENKINS_SERVICE}:${JOB_NAME}:NO_EXECUTION_ID:foo=bat")
}

void "updates a jenkins build description"() {
when:
MockHttpServletResponse response = mockMvc.perform(
patch("/masters/${JENKINS_SERVICE}/jobs/${jobName}/update/${BUILD_NUMBER}")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"description": "this is my new description"
}
""")
).andReturn().response

then:
1 * jenkinsService.updateBuild(jobName, BUILD_NUMBER, new UpdatedBuild("this is my new description"))
0 * _
response.status == 200

where:
jobName << [
"complex/job/name/with/slashes",
"simpleJobName"
]
}
}

0 comments on commit 04b5627

Please sign in to comment.