Skip to content

Commit

Permalink
feat(jenkins): Enable Jenkins job triggers for jobs in sub-folders (#…
Browse files Browse the repository at this point in the history
…1373)

- Allow triggering of Jenkins jobs that reside in sub-folders, by treating job names containing slashes as query variables.

- Prior to this feature, jobs in sub-folders were not appropriately matched by the Spring framework due to slashes in their path, causing trigger requests to fail.

- Include a new feature flag to determine the usage of the existing endpoint (which uses the job name as a path variable) or an updated endpoint (which takes the job name as a query parameter).

(cherry picked from commit 18d038d)
  • Loading branch information
ciurescuraul authored and mergify[bot] committed Jan 22, 2024
1 parent 4f2b91c commit 458e799
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.echo.config.IgorConfigurationProperties;
import com.netflix.spinnaker.echo.jackson.EchoObjectMapper;
import com.netflix.spinnaker.echo.model.Trigger;
import com.netflix.spinnaker.echo.model.trigger.BuildEvent;
import com.netflix.spinnaker.echo.services.IgorService;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.kork.core.RetrySupport;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -40,6 +45,7 @@
public class BuildInfoService {
private final IgorService igorService;
private final RetrySupport retrySupport;
private final IgorConfigurationProperties igorConfigurationProperties;
private final ObjectMapper objectMapper = EchoObjectMapper.getInstance();

// Manual triggers try to replicate actual events (and in some cases build events) but rather than
Expand All @@ -49,7 +55,11 @@ public class BuildInfoService {
// an event as with other triggers, but for now we'll see whether we can extract a build event
// from the trigger.
public BuildEvent getBuildEvent(String master, String job, int buildNumber) {
Map<String, Object> rawBuild = retry(() -> igorService.getBuild(buildNumber, master, job));
Map<String, Object> rawBuild =
retry(
igorConfigurationProperties.isJobNameAsQueryParameter()
? () -> igorService.getBuildStatusWithJobQueryParameter(buildNumber, master, job)
: () -> igorService.getBuild(buildNumber, master, job));
BuildEvent.Build build = objectMapper.convertValue(rawBuild, BuildEvent.Build.class);
BuildEvent.Project project = new BuildEvent.Project(job, build);
BuildEvent.Content content = new BuildEvent.Content(project, master);
Expand All @@ -64,7 +74,11 @@ public Map<String, Object> getBuildInfo(BuildEvent event) {
int buildNumber = event.getBuildNumber();

if (StringUtils.isNoneEmpty(master, job)) {
return retry(() -> igorService.getBuild(buildNumber, master, job));
return retry(
() ->
igorConfigurationProperties.isJobNameAsQueryParameter()
? igorService.getBuildStatusWithJobQueryParameter(buildNumber, master, job)
: igorService.getBuild(buildNumber, master, job));
}
return Collections.emptyMap();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,19 @@
import com.netflix.spinnaker.echo.services.IgorService;
import com.netflix.spinnaker.kork.core.RetrySupport;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty("igor.enabled")
@EnableConfigurationProperties(IgorConfigurationProperties.class)
public class BuildInfoConfig {
@Bean
BuildInfoService buildInfoService(IgorService igorService, RetrySupport retrySupport) {
return new BuildInfoService(igorService, retrySupport);
BuildInfoService buildInfoService(
IgorService igorService,
RetrySupport retrySupport,
IgorConfigurationProperties igorConfigurationProperties) {
return new BuildInfoService(igorService, retrySupport, igorConfigurationProperties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 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.echo.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Setter
@Getter
@ConfigurationProperties(prefix = "feature.igor")
public class IgorConfigurationProperties {
private boolean jobNameAsQueryParameter = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import retrofit.client.Response;
import retrofit.http.*;
import retrofit.mime.TypedInput;
Expand All @@ -30,6 +31,12 @@ Map<String, Object> getBuild(
@Path("master") String master,
@Path(value = "job", encode = false) String job);

@GET("/builds/status/{buildNumber}/{master}")
Map<String, Object> getBuildStatusWithJobQueryParameter(
@NotNull @Path("buildNumber") Integer buildNumber,
@NotNull @Path("master") String master,
@NotNull @Query(value = "job") String job);

@GET("/builds/properties/{buildNumber}/{fileName}/{master}/{job}")
Map<String, Object> getPropertyFile(
@Path("buildNumber") Integer buildNumber,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.netflix.spinnaker.echo.pipelinetriggers.eventhandlers

import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spectator.api.NoopRegistry
import com.netflix.spinnaker.echo.build.BuildInfoService
import com.netflix.spinnaker.echo.config.IgorConfigurationProperties
import com.netflix.spinnaker.echo.jackson.EchoObjectMapper
import com.netflix.spinnaker.echo.model.Pipeline
import com.netflix.spinnaker.echo.model.trigger.BuildEvent
Expand All @@ -20,7 +20,7 @@ class BuildEventHandlerSpec extends Specification implements RetrofitStubs {
def registry = new NoopRegistry()
def objectMapper = EchoObjectMapper.getInstance()
def igorService = Mock(IgorService)
def buildInformation = new BuildInfoService(igorService, new RetrySupport())
def buildInformation = new BuildInfoService(igorService, new RetrySupport(), new IgorConfigurationProperties(jobNameAsQueryParameter: false))
def handlerSupport = new EventHandlerSupport()
def fiatPermissionEvaluator = Mock(FiatPermissionEvaluator)

Expand Down Expand Up @@ -246,6 +246,29 @@ class BuildEventHandlerSpec extends Specification implements RetrofitStubs {
outputTrigger.buildInfo.equals(BUILD_INFO)
}

def "getBuildInfo method gets job name from query when flag is true"()
{
given:
def mockBuildInfo = BUILD_INFO
def trigger = enabledJenkinsTrigger.withMaster(MASTER_NAME).withBuildNumber(BUILD_NUMBER)
createPipelineWith(enabledJenkinsTrigger).withTrigger(trigger)
def event = getBuildEvent()

def retrySupport = new RetrySupport()
def configProperties = new IgorConfigurationProperties(jobNameAsQueryParameter: true)
def buildInfoService = new BuildInfoService(igorService, retrySupport, configProperties)

def permissionEvaluator = fiatPermissionEvaluator
def buildEventHandler = new BuildEventHandler(registry, objectMapper, Optional.of(buildInfoService), permissionEvaluator)

when:
def outputTrigger = buildEventHandler.buildTrigger(event).apply(trigger)

then:
1 * igorService.getBuildStatusWithJobQueryParameter(BUILD_NUMBER, MASTER_NAME, JOB_NAME) >> mockBuildInfo
outputTrigger.buildInfo == mockBuildInfo
}

def "fetches property file if defined"() {
given:
def trigger = enabledJenkinsTrigger
Expand Down

0 comments on commit 458e799

Please sign in to comment.