From 57b0385e8e80977cb5acc0b59fa25e213afb9c9a Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 28 Aug 2025 19:14:31 +0200 Subject: [PATCH 1/7] Allow read timeout configuration Signed-off-by: Tomasz Janiszewski --- .../resources/template.xml | 1 + .../resources/templateNoFile.xml | 1 + .../src/main/groovy/JenkinsClient.groovy | 33 ++++++++++++++++--- .../src/test/groovy/ImageScanningTest.groovy | 13 ++++++++ .../jenkins/plugins/StackroxBuilder.java | 16 +++++++-- .../plugins/services/ApiClientFactory.java | 19 ++++++----- .../plugins/StackroxBuilder/config.jelly | 4 +++ .../plugins/StackroxBuilder/config.properties | 1 + .../webapp/help/help-readTimeoutSeconds.html | 4 +++ .../plugins/services/AbstractServiceTest.java | 2 +- .../services/ApiClientFactoryTest.java | 6 ++-- 11 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 stackrox-container-image-scanner/src/main/webapp/help/help-readTimeoutSeconds.html diff --git a/functionaltest-jenkins-plugin/resources/template.xml b/functionaltest-jenkins-plugin/resources/template.xml index 92b15b9b..3784b273 100644 --- a/functionaltest-jenkins-plugin/resources/template.xml +++ b/functionaltest-jenkins-plugin/resources/template.xml @@ -21,6 +21,7 @@ + diff --git a/functionaltest-jenkins-plugin/resources/templateNoFile.xml b/functionaltest-jenkins-plugin/resources/templateNoFile.xml index 734528f7..240771ec 100644 --- a/functionaltest-jenkins-plugin/resources/templateNoFile.xml +++ b/functionaltest-jenkins-plugin/resources/templateNoFile.xml @@ -18,6 +18,7 @@ + diff --git a/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy b/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy index d8a5a3fb..bab1502c 100644 --- a/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy +++ b/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy @@ -49,7 +49,16 @@ class JenkinsClient { static String createJobConfig(String imageName, String portalAddress, String token, Boolean policyEvalCheck, Boolean failOnCriticalPluginError) { Map param = createConfigMap( - imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError) + imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, null) + // parse the xml + String path = TEMPLATE_WITHOUT_IMAGE_NAMES + return createJobConfigFromPath(path, param) + } + + static String createJobConfig(String imageName, String portalAddress, String token, Boolean policyEvalCheck, + Boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { + Map param = createConfigMap( + imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) // parse the xml String path = TEMPLATE_WITHOUT_IMAGE_NAMES return createJobConfigFromPath(path, param) @@ -58,7 +67,16 @@ class JenkinsClient { static String createJobConfigNoFile(String imageName, String portalAddress, String token, Boolean policyEvalCheck, Boolean failOnCriticalPluginError) { Map param = createConfigMap( - imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError) + imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, null) + // parse the xml + String path = JOB_TEMPLATE_WITH_IMAGE_NAMES + return createJobConfigFromPath(path, param) + } + + static String createJobConfigNoFile(String imageName, String portalAddress, String token, Boolean policyEvalCheck, + Boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { + Map param = createConfigMap( + imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) // parse the xml String path = JOB_TEMPLATE_WITH_IMAGE_NAMES return createJobConfigFromPath(path, param) @@ -67,8 +85,9 @@ class JenkinsClient { //TODO(ROX-8458): add tests for pipeline private static Map createConfigMap(String imageName, String portalAddress, String token, boolean policyEvalCheck, - boolean failOnCriticalPluginError) { - return [ // codenarc-disable UnnecessaryCast + boolean failOnCriticalPluginError, + Integer readTimeoutSeconds) { + Map configMap = [ // codenarc-disable UnnecessaryCast command : """mkdir \$BUILD_TAG cd \$BUILD_TAG echo '${imageName}' >> rox_images_to_scan""", @@ -79,6 +98,12 @@ class JenkinsClient { enableTLSVerification : false, imageNames : imageName, ] as Map + + if (readTimeoutSeconds != null) { + configMap.readTimeoutSeconds = readTimeoutSeconds + } + + return configMap } @CompileStatic(TypeCheckingMode.SKIP) diff --git a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy index 4902a903..cda359e6 100644 --- a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy +++ b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy @@ -22,6 +22,15 @@ class ImageScanningTest extends BaseSpecification { protected static final String CENTRAL_URI = Config.roxEndpoint protected static final String QUAY_REPO = "quay.io/openshifttest/" + def "Test read timeout with minimal timeout should fail"() { + when: + BuildResult status = jenkins.createAndRunJob( + getJobConfigWithTimeout("nginx-alpine:latest", false, true, 1)) + + then: + assert status == FAILURE + } + @Unroll def "image scanning test with toggle enforcement(#imageName, #policyName, #enforcements, #endStatus)"() { given: @@ -94,6 +103,10 @@ class ImageScanningTest extends BaseSpecification { return createJobConfig(QUAY_REPO + imageName, CENTRAL_URI, token, policyEvalCheck, failOnCriticalPluginError) } + String getJobConfigWithTimeout(String imageName, Boolean policyEvalCheck, Boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { + return createJobConfig(QUAY_REPO + imageName, CENTRAL_URI, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) + } + StoragePolicy updatePolicy(String policyName, String tag, List enforcements) { List policies = restApiClient.policies def policyId = policies.find { it.name == policyName }?.id diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java index dd4bf51e..2ca0f0c7 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java @@ -75,6 +75,8 @@ public class StackroxBuilder extends Builder implements SimpleBuildStep { private String caCertPEM; @DataBoundSetter private String cluster; + @DataBoundSetter + private int readTimeoutSeconds = 600; private RunConfig runConfig; @@ -150,7 +152,7 @@ private List checkImages() throws IOException { List results = Lists.newArrayList(); ApiClient apiClient = ApiClientFactory.newApiClient( - getPortalAddress(), getApiToken().getPlainText(), getCaCertPEM(), getTLSValidationMode()); + getPortalAddress(), getApiToken().getPlainText(), getCaCertPEM(), getTLSValidationMode(), getReadTimeoutSeconds()); ImageService imageService = new ImageService(apiClient); DetectionService detectionService = new DetectionService(apiClient); @@ -249,6 +251,16 @@ public FormValidation doCheckApiToken(@QueryParameter final String apiToken) { } } + @SuppressWarnings("unused") + public FormValidation doCheckReadTimeoutSeconds(@QueryParameter final int readTimeoutSeconds) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + if (readTimeoutSeconds > 0 && readTimeoutSeconds <= 3600) { + return FormValidation.ok(); + } else { + return FormValidation.error("Read timeout must be between 1 and 3600 seconds."); + } + } + @SuppressWarnings("unused") @POST public FormValidation doTestConnection(@QueryParameter("portalAddress") final String portalAddress, @QueryParameter("apiToken") final String apiToken, @@ -275,7 +287,7 @@ public FormValidation doTestConnection(@QueryParameter("portalAddress") final St } private boolean checkRoxAuthStatus(final String portalAddress, final String apiToken, final boolean tlsVerify, final String caCertPEM) throws IOException { - ApiClient apiClient = ApiClientFactory.newApiClient(portalAddress, apiToken, caCertPEM, validationMode(tlsVerify)); + ApiClient apiClient = ApiClientFactory.newApiClient(portalAddress, apiToken, caCertPEM, validationMode(tlsVerify), 10); try { V1AuthStatus status = new AuthServiceApi(apiClient).authServiceGetAuthStatus(); return !Strings.isNullOrEmpty(status.getUserId()); diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java index 238ad7ba..34ded5d7 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java @@ -43,13 +43,13 @@ public enum StackRoxTlsValidationMode { } private static final Duration TIMEOUT = Duration.ofSeconds(30); - private static final Duration READ_TIMEOUT = Duration.ofMinutes(10); private static final int MAXIMUM_CACHE_SIZE = 5; // arbitrary chosen as there are no data to support this decision @Data private static class CacheKey { private final String caCert; private final StackRoxTlsValidationMode tlsValidationMode; + private final int readTimeoutSeconds; } // It is good practice to avoid creating OkHttpClient on each request. @@ -61,13 +61,13 @@ private static class CacheKey { new CacheLoader() { @Override public OkHttpClient load(@Nonnull CacheKey key) throws IOException { - return newHttpClient(key.caCert, key.tlsValidationMode); + return newHttpClient(key.caCert, key.tlsValidationMode, key.readTimeoutSeconds); } }); - public static ApiClient newApiClient(String basePath, String apiKey, @Nullable String caCert, StackRoxTlsValidationMode tlsValidationMode) throws IOException { - OkHttpClient client = getClient(tlsValidationMode, caCert); + public static ApiClient newApiClient(String basePath, String apiKey, @Nullable String caCert, StackRoxTlsValidationMode tlsValidationMode, int readTimeoutSeconds) throws IOException { + OkHttpClient client = getClient(tlsValidationMode, caCert, readTimeoutSeconds); ApiClient apiClient = new ApiClient(client); apiClient.setBearerToken(apiKey); apiClient.setBasePath(basePath); @@ -75,16 +75,19 @@ public static ApiClient newApiClient(String basePath, String apiKey, @Nullable S } @Nonnull - static OkHttpClient getClient(StackRoxTlsValidationMode tlsValidationMode, @Nullable String caCert) throws IOException { + static OkHttpClient getClient(StackRoxTlsValidationMode tlsValidationMode, @Nullable String caCert, int readTimeoutSeconds) throws IOException { try { - return CLIENT_CACHE.get(new CacheKey(caCert, tlsValidationMode)); + return CLIENT_CACHE.get(new CacheKey(caCert, tlsValidationMode, readTimeoutSeconds)); } catch (ExecutionException e) { throw new IOException("Could not get HTTP client from cache", e); } } @Nonnull - private static OkHttpClient newHttpClient(@Nullable String caCert, StackRoxTlsValidationMode tlsValidationMode) throws IOException { + private static OkHttpClient newHttpClient(@Nullable String caCert, StackRoxTlsValidationMode tlsValidationMode, int readTimeoutSeconds) throws IOException { + if (readTimeoutSeconds < 1) { + readTimeoutSeconds = 600; + } OkHttpClient.Builder builder; try { if (tlsValidationMode == INSECURE_ACCEPT_ANY) { @@ -101,7 +104,7 @@ private static OkHttpClient newHttpClient(@Nullable String caCert, StackRoxTlsVa } builder.retryOnConnectionFailure(true); builder.connectTimeout(TIMEOUT); - builder.readTimeout(READ_TIMEOUT); + builder.readTimeout(Duration.ofSeconds(readTimeoutSeconds)); builder.writeTimeout(TIMEOUT); builder.addNetworkInterceptor(new UserAgentInterceptor()); return builder.build(); diff --git a/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly b/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly index b703eaa2..bd6b0e83 100644 --- a/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly +++ b/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly @@ -16,6 +16,10 @@ help="/plugin/stackrox-container-image-scanner/help/help-cluster.html"> + + + + HTTP read timeout in seconds for API requests to StackRox portal. + Increase this value if you experience timeout errors during image scans. + diff --git a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/AbstractServiceTest.java b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/AbstractServiceTest.java index 8e63d06c..8eb793c9 100644 --- a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/AbstractServiceTest.java +++ b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/AbstractServiceTest.java @@ -24,7 +24,7 @@ public abstract class AbstractServiceTest { @BeforeAll static void setup() throws IOException { MOCK_SERVER.start(); - client = ApiClientFactory.newApiClient(MOCK_SERVER.baseUrl(), MOCK_TOKEN.getPlainText(), "", INSECURE_ACCEPT_ANY); + client = ApiClientFactory.newApiClient(MOCK_SERVER.baseUrl(), MOCK_TOKEN.getPlainText(), "", INSECURE_ACCEPT_ANY, 1); } @AfterAll diff --git a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ApiClientFactoryTest.java b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ApiClientFactoryTest.java index 1355dd7b..1e9e4f6b 100644 --- a/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ApiClientFactoryTest.java +++ b/stackrox-container-image-scanner/src/test/java/com/stackrox/jenkins/plugins/services/ApiClientFactoryTest.java @@ -54,7 +54,7 @@ void shouldHandleTLSOptions(ApiClientFactory.StackRoxTlsValidationMode tlsVerify File caPemFile = Paths.get("src", "test", "resources", "cert", "localhost.pem").toFile(); String pem = useCaCert ? FileUtils.readFileToString(caPemFile, StandardCharsets.UTF_8) : null; - OkHttpClient client = ApiClientFactory.getClient(tlsVerify, pem); + OkHttpClient client = ApiClientFactory.getClient(tlsVerify, pem, 1); Request request = new Request.Builder().url(SERVER.baseUrl()).build(); Response response = client.newCall(request).execute(); @@ -65,7 +65,7 @@ void shouldHandleTLSOptions(ApiClientFactory.StackRoxTlsValidationMode tlsVerify @Test @DisplayName("TLS should FAIL when tlsVerify: true and custom PEM: false") void shouldThrowWhenTLSCouldNotBeVerified() throws IOException { - OkHttpClient client = ApiClientFactory.getClient(VALIDATE, ""); + OkHttpClient client = ApiClientFactory.getClient(VALIDATE, "", 1); Request request = new Request.Builder().url(SERVER.baseUrl()).build(); Exception exception = assertThrows(IOException.class, () -> client.newCall(request).execute()); @@ -81,7 +81,7 @@ void shouldThrowWhenHostIsInvalid() throws IOException { File clientPem = Paths.get("src", "test", "resources", "cert", "client.pem").toFile(); String pem = FileUtils.readFileToString(clientPem, StandardCharsets.UTF_8); - OkHttpClient client = ApiClientFactory.getClient(VALIDATE, pem); + OkHttpClient client = ApiClientFactory.getClient(VALIDATE, pem, 1); WireMockServer server = new WireMockServer(wireMockConfig().httpDisabled(true) .dynamicHttpsPort().keystorePath(keyStorePath).keystorePassword(KEY_STORE_PASSWORD)); From 9f07081e16a479501b80053669d410d89b3de2a0 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 29 Aug 2025 12:57:54 +0200 Subject: [PATCH 2/7] extract const Signed-off-by: Tomasz Janiszewski --- .../java/com/stackrox/jenkins/plugins/StackroxBuilder.java | 2 +- .../stackrox/jenkins/plugins/services/ApiClientFactory.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java index 2ca0f0c7..8c145774 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/StackroxBuilder.java @@ -76,7 +76,7 @@ public class StackroxBuilder extends Builder implements SimpleBuildStep { @DataBoundSetter private String cluster; @DataBoundSetter - private int readTimeoutSeconds = 600; + private int readTimeoutSeconds = ApiClientFactory.DEFAULT_READ_TIMEOUT_SECONDS; private RunConfig runConfig; diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java index 34ded5d7..9de340e2 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java @@ -42,6 +42,7 @@ public enum StackRoxTlsValidationMode { INSECURE_ACCEPT_ANY } + public static final int DEFAULT_READ_TIMEOUT_SECONDS = 600; private static final Duration TIMEOUT = Duration.ofSeconds(30); private static final int MAXIMUM_CACHE_SIZE = 5; // arbitrary chosen as there are no data to support this decision @@ -86,7 +87,7 @@ static OkHttpClient getClient(StackRoxTlsValidationMode tlsValidationMode, @Null @Nonnull private static OkHttpClient newHttpClient(@Nullable String caCert, StackRoxTlsValidationMode tlsValidationMode, int readTimeoutSeconds) throws IOException { if (readTimeoutSeconds < 1) { - readTimeoutSeconds = 600; + readTimeoutSeconds = DEFAULT_READ_TIMEOUT_SECONDS; } OkHttpClient.Builder builder; try { From 022dd7b593c83cf0a190330ca14977ac531c0f08 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 29 Aug 2025 13:37:12 +0200 Subject: [PATCH 3/7] fix Signed-off-by: Tomasz Janiszewski --- .../src/main/groovy/JenkinsClient.groovy | 13 ++----------- .../src/test/groovy/ImageScanningTest.groovy | 9 +++------ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy b/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy index bab1502c..82f6dfe2 100644 --- a/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy +++ b/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy @@ -47,16 +47,7 @@ class JenkinsClient { } static String createJobConfig(String imageName, String portalAddress, String token, Boolean policyEvalCheck, - Boolean failOnCriticalPluginError) { - Map param = createConfigMap( - imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, null) - // parse the xml - String path = TEMPLATE_WITHOUT_IMAGE_NAMES - return createJobConfigFromPath(path, param) - } - - static String createJobConfig(String imageName, String portalAddress, String token, Boolean policyEvalCheck, - Boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { + Boolean failOnCriticalPluginError, Integer readTimeoutSeconds = null) { Map param = createConfigMap( imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) // parse the xml @@ -83,7 +74,7 @@ class JenkinsClient { } //TODO(ROX-8458): add tests for pipeline - private static Map createConfigMap(String imageName, String portalAddress, String token, + private static Map createConfigMap(String imageName, String portalAddress, String token, boolean policyEvalCheck, boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { diff --git a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy index cda359e6..cf1074af 100644 --- a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy +++ b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy @@ -25,7 +25,7 @@ class ImageScanningTest extends BaseSpecification { def "Test read timeout with minimal timeout should fail"() { when: BuildResult status = jenkins.createAndRunJob( - getJobConfigWithTimeout("nginx-alpine:latest", false, true, 1)) + getJobConfig("nginx-alpine:latest", false, true, 1)) then: assert status == FAILURE @@ -99,11 +99,8 @@ class ImageScanningTest extends BaseSpecification { "mis-spelled:lts" | false | SUCCESS } - String getJobConfig(String imageName, Boolean policyEvalCheck, Boolean failOnCriticalPluginError) { - return createJobConfig(QUAY_REPO + imageName, CENTRAL_URI, token, policyEvalCheck, failOnCriticalPluginError) - } - - String getJobConfigWithTimeout(String imageName, Boolean policyEvalCheck, Boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { + String getJobConfig(String imageName, Boolean policyEvalCheck, Boolean failOnCriticalPluginError, + Integer readTimeoutSeconds = null) { return createJobConfig(QUAY_REPO + imageName, CENTRAL_URI, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) } From 0fc44b0fe716db1ab87a9af711069e2ebbbd4096 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 29 Aug 2025 15:20:16 +0200 Subject: [PATCH 4/7] style Signed-off-by: Tomasz Janiszewski --- .../src/test/groovy/ImageScanningTest.groovy | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy index cf1074af..a6670087 100644 --- a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy +++ b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy @@ -99,9 +99,16 @@ class ImageScanningTest extends BaseSpecification { "mis-spelled:lts" | false | SUCCESS } - String getJobConfig(String imageName, Boolean policyEvalCheck, Boolean failOnCriticalPluginError, + String getJobConfig(String imageName, + Boolean policyEvalCheck, + Boolean failOnCriticalPluginError, Integer readTimeoutSeconds = null) { - return createJobConfig(QUAY_REPO + imageName, CENTRAL_URI, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) + return createJobConfig(QUAY_REPO + imageName, + CENTRAL_URI, + token, + policyEvalCheck, + failOnCriticalPluginError, + readTimeoutSeconds) } StoragePolicy updatePolicy(String policyName, String tag, List enforcements) { From 5ed7064e5def3fee0d563f1ff2862308ae360418 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 29 Aug 2025 16:48:27 +0200 Subject: [PATCH 5/7] fix style Signed-off-by: Tomasz Janiszewski --- .../src/main/groovy/JenkinsClient.groovy | 121 +++++++++--------- .../src/test/groovy/ImageScanningTest.groovy | 15 ++- .../groovy/ImageScanningTestNoFileTest.groovy | 11 +- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy b/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy index 82f6dfe2..a474b2b9 100644 --- a/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy +++ b/functionaltest-jenkins-plugin/src/main/groovy/JenkinsClient.groovy @@ -15,6 +15,63 @@ class JenkinsClient { public static final String TEMPLATE_WITHOUT_IMAGE_NAMES = "resources/template.xml" private final JenkinsServer jenkins + static class Config { + String imageName + String portalAddress + String token + Boolean policyEvalCheck + Boolean failOnCriticalPluginError + Integer readTimeoutSeconds = null + + String createJobConfig() { + Map param = createConfigMap() + // parse the xml + String path = TEMPLATE_WITHOUT_IMAGE_NAMES + return createJobConfigFromPath(path, param) + } + + String createJobConfigNoFile() { + Map param = createConfigMap() + // parse the xml + String path = JOB_TEMPLATE_WITH_IMAGE_NAMES + return createJobConfigFromPath(path, param) + } + + //TODO(ROX-8458): add tests for pipeline + Map createConfigMap() { + Map configMap = [ // codenarc-disable UnnecessaryCast + command : """mkdir \$BUILD_TAG + cd \$BUILD_TAG + echo '${imageName}' >> rox_images_to_scan""", + portalAddress : portalAddress, + apiToken : token, + failOnPolicyEvalFailure : policyEvalCheck, + failOnCriticalPluginError: failOnCriticalPluginError, + enableTLSVerification : false, + imageNames : imageName, + ] as Map + + if (readTimeoutSeconds != null) { + configMap.readTimeoutSeconds = readTimeoutSeconds + } + + return configMap + } + + @CompileStatic(TypeCheckingMode.SKIP) + private static String createJobConfigFromPath(String path, Map param) { + def parsexml = new XmlSlurper().parse(new File(path)) + param.each { key, value -> + parsexml.breadthFirst().findAll { NodeChild it -> + if (it.name() == key) { + it.replaceBody value + } + } + } + return XmlUtil.serialize(parsexml) + } + } + JenkinsClient() { def env = System.getenv() String jenkinsAddress = env.getOrDefault('JENKINS_ADDRESS', "http://localhost:8080/jenkins/") @@ -45,68 +102,4 @@ class JenkinsClient { println result.consoleOutputText return result.result } - - static String createJobConfig(String imageName, String portalAddress, String token, Boolean policyEvalCheck, - Boolean failOnCriticalPluginError, Integer readTimeoutSeconds = null) { - Map param = createConfigMap( - imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) - // parse the xml - String path = TEMPLATE_WITHOUT_IMAGE_NAMES - return createJobConfigFromPath(path, param) - } - - static String createJobConfigNoFile(String imageName, String portalAddress, String token, Boolean policyEvalCheck, - Boolean failOnCriticalPluginError) { - Map param = createConfigMap( - imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, null) - // parse the xml - String path = JOB_TEMPLATE_WITH_IMAGE_NAMES - return createJobConfigFromPath(path, param) - } - - static String createJobConfigNoFile(String imageName, String portalAddress, String token, Boolean policyEvalCheck, - Boolean failOnCriticalPluginError, Integer readTimeoutSeconds) { - Map param = createConfigMap( - imageName, portalAddress, token, policyEvalCheck, failOnCriticalPluginError, readTimeoutSeconds) - // parse the xml - String path = JOB_TEMPLATE_WITH_IMAGE_NAMES - return createJobConfigFromPath(path, param) - } - - //TODO(ROX-8458): add tests for pipeline - private static Map createConfigMap(String imageName, String portalAddress, String token, - boolean policyEvalCheck, - boolean failOnCriticalPluginError, - Integer readTimeoutSeconds) { - Map configMap = [ // codenarc-disable UnnecessaryCast - command : """mkdir \$BUILD_TAG - cd \$BUILD_TAG - echo '${imageName}' >> rox_images_to_scan""", - portalAddress : portalAddress, - apiToken : token, - failOnPolicyEvalFailure : policyEvalCheck, - failOnCriticalPluginError: failOnCriticalPluginError, - enableTLSVerification : false, - imageNames : imageName, - ] as Map - - if (readTimeoutSeconds != null) { - configMap.readTimeoutSeconds = readTimeoutSeconds - } - - return configMap - } - - @CompileStatic(TypeCheckingMode.SKIP) - private static String createJobConfigFromPath(String path, Map param) { - def parsexml = new XmlSlurper().parse(new File(path)) - param.each { key, value -> - parsexml.breadthFirst().findAll { NodeChild it -> - if (it.name() == key) { - it.replaceBody value - } - } - } - return XmlUtil.serialize(parsexml) - } } diff --git a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy index a6670087..44e978f2 100644 --- a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy +++ b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTest.groovy @@ -1,4 +1,3 @@ -import static JenkinsClient.createJobConfig import static com.offbytwo.jenkins.model.BuildResult.FAILURE import static com.offbytwo.jenkins.model.BuildResult.SUCCESS import static com.stackrox.model.StorageEnforcementAction.FAIL_BUILD_ENFORCEMENT @@ -103,12 +102,14 @@ class ImageScanningTest extends BaseSpecification { Boolean policyEvalCheck, Boolean failOnCriticalPluginError, Integer readTimeoutSeconds = null) { - return createJobConfig(QUAY_REPO + imageName, - CENTRAL_URI, - token, - policyEvalCheck, - failOnCriticalPluginError, - readTimeoutSeconds) + return new JenkinsClient.Config( + imageName: QUAY_REPO + imageName, + portalAddress: CENTRAL_URI, + token: token, + policyEvalCheck: policyEvalCheck, + failOnCriticalPluginError: failOnCriticalPluginError, + readTimeoutSeconds: readTimeoutSeconds) + .createJobConfig() } StoragePolicy updatePolicy(String policyName, String tag, List enforcements) { diff --git a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTestNoFileTest.groovy b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTestNoFileTest.groovy index 2de63299..00abb3d2 100644 --- a/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTestNoFileTest.groovy +++ b/functionaltest-jenkins-plugin/src/test/groovy/ImageScanningTestNoFileTest.groovy @@ -1,9 +1,12 @@ -import static JenkinsClient.createJobConfigNoFile - class ImageScanningTestNoFileTest extends ImageScanningTest { @Override String getJobConfig(String imageName, Boolean policyEvalCheck, Boolean failOnCriticalPluginError) { - String image = QUAY_REPO + imageName - return createJobConfigNoFile(image, CENTRAL_URI, token, policyEvalCheck, failOnCriticalPluginError) + return new JenkinsClient.Config( + imageName: QUAY_REPO + imageName, + portalAddress: CENTRAL_URI, + token: token, + policyEvalCheck: policyEvalCheck, + failOnCriticalPluginError: failOnCriticalPluginError, + ).createJobConfigNoFile() } } From 3cb6f961d93358ed263821f82d8ee797c75a0187 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Wed, 3 Sep 2025 12:56:33 +0200 Subject: [PATCH 6/7] Apply suggestion from @andham --- .../com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly b/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly index bd6b0e83..4d76cbf3 100644 --- a/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly +++ b/stackrox-container-image-scanner/src/main/resources/com/stackrox/jenkins/plugins/StackroxBuilder/config.jelly @@ -18,7 +18,7 @@ - + From ec211817be4bc74bb0f5e4b2027438895e274bf2 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Wed, 3 Sep 2025 12:56:56 +0200 Subject: [PATCH 7/7] Apply suggestion from @andham --- .../com/stackrox/jenkins/plugins/services/ApiClientFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java index 9de340e2..d236bed4 100644 --- a/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java +++ b/stackrox-container-image-scanner/src/main/java/com/stackrox/jenkins/plugins/services/ApiClientFactory.java @@ -42,7 +42,7 @@ public enum StackRoxTlsValidationMode { INSECURE_ACCEPT_ANY } - public static final int DEFAULT_READ_TIMEOUT_SECONDS = 600; + public static final int DEFAULT_READ_TIMEOUT_SECONDS = 60; private static final Duration TIMEOUT = Duration.ofSeconds(30); private static final int MAXIMUM_CACHE_SIZE = 5; // arbitrary chosen as there are no data to support this decision