diff --git a/src/main/docker/Dockerfile b/src/main/docker/Dockerfile index fa10494d..462b154a 100644 --- a/src/main/docker/Dockerfile +++ b/src/main/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG jenkins_tag=2.303.3-lts-jdk11 +ARG jenkins_tag=2.414.3-lts-jdk17 FROM jenkins/jenkins:$jenkins_tag diff --git a/src/main/java/com/cdancy/jenkins/rest/JenkinsUtils.java b/src/main/java/com/cdancy/jenkins/rest/JenkinsUtils.java index 5b567460..784169ed 100644 --- a/src/main/java/com/cdancy/jenkins/rest/JenkinsUtils.java +++ b/src/main/java/com/cdancy/jenkins/rest/JenkinsUtils.java @@ -29,14 +29,18 @@ import static com.cdancy.jenkins.rest.JenkinsConstants.API_TOKEN_ENVIRONMENT_VARIABLE; import static com.cdancy.jenkins.rest.JenkinsConstants.API_TOKEN_SYSTEM_PROPERTY; +import com.google.common.base.Charsets; import com.google.common.base.Throwables; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.List; import java.util.Map; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import com.google.common.io.CharStreams; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParser; @@ -47,6 +51,7 @@ import java.util.Objects; import java.util.Properties; +import org.jclouds.http.HttpResponse; import org.jclouds.javax.annotation.Nullable; /** @@ -70,6 +75,26 @@ public static List nullToEmpty(final Iterable input) { return input == null ? ImmutableList.of() : ImmutableList.copyOf(input); } + /** + * Retrieves the text content from an HttpResponse object. + * + * This method extracts the payload from the HttpResponse, converts the InputStream to a String using UTF-8 encoding, + * and returns the result. In case of any exception during the process, it returns null. + * + * @param response the HttpResponse object containing the payload to be read + * @return the text content of the HttpResponse payload as a String, or null if an error occurs + */ + public static String getTextOutput(HttpResponse response) { + try (InputStream is = response.getPayload().openStream()) { + return CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8)); + } catch (Exception e) { + // ignore + } + // ignore + + return null; + } + /** * Convert passed Map into an ImmutableMap. * diff --git a/src/main/java/com/cdancy/jenkins/rest/domain/job/Folder.java b/src/main/java/com/cdancy/jenkins/rest/domain/job/Folder.java new file mode 100644 index 00000000..6c446e9b --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/domain/job/Folder.java @@ -0,0 +1,32 @@ +package com.cdancy.jenkins.rest.domain.job; + +/** + * Author: kun.tang@daocloud.io + * Date:2024/9/11 + * Time:15:52 + */ + +public class Folder { + + private String name; + private String mode; + private Folder(){} + + public Folder(String folderName){ + this.name = folderName; + this.mode = "com.cloudbees.hudson.plugins.folder.Folder"; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMode() { + return mode; + } + +} diff --git a/src/main/java/com/cdancy/jenkins/rest/domain/job/JobExisted.java b/src/main/java/com/cdancy/jenkins/rest/domain/job/JobExisted.java new file mode 100644 index 00000000..dcffad83 --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/domain/job/JobExisted.java @@ -0,0 +1,31 @@ +package com.cdancy.jenkins.rest.domain.job; + +import com.google.auto.value.AutoValue; +import org.jclouds.json.SerializedNames; + +/** + * Author: kun.tang@daocloud.io + * Date:2024/9/11 + * Time:12:45 + */ + +@AutoValue +public abstract class JobExisted { + + public abstract String msg(); + public abstract boolean existed(); + + JobExisted(){} + + public static JobExisted create(String msg){ + boolean existed = false; + if(msg!=null&&msg.contains("error")){ + msg = msg.replace("
","").replace("
",""); + existed = true; + }else { + msg = ""; + } + return new AutoValue_JobExisted(msg,existed); + } + +} diff --git a/src/main/java/com/cdancy/jenkins/rest/exception/JobCreateFailException.java b/src/main/java/com/cdancy/jenkins/rest/exception/JobCreateFailException.java new file mode 100644 index 00000000..638eb9f5 --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/exception/JobCreateFailException.java @@ -0,0 +1,15 @@ +package com.cdancy.jenkins.rest.exception; + +/** + * Author: kun.tang@daocloud.io + * Date:2024/9/11 + * Time:16:37 + */ + +public class JobCreateFailException extends RuntimeException{ + private static final long serialVersionUID = 1L; + + public JobCreateFailException(final String message) { + super(message); + } +} diff --git a/src/main/java/com/cdancy/jenkins/rest/features/JobsApi.java b/src/main/java/com/cdancy/jenkins/rest/features/JobsApi.java index b1f91495..6830dd09 100644 --- a/src/main/java/com/cdancy/jenkins/rest/features/JobsApi.java +++ b/src/main/java/com/cdancy/jenkins/rest/features/JobsApi.java @@ -18,32 +18,21 @@ package com.cdancy.jenkins.rest.features; import java.io.InputStream; + +import com.cdancy.jenkins.rest.exception.JobCreateFailException; import com.google.gson.JsonObject; import java.util.List; import java.util.Map; import javax.inject.Named; -import javax.ws.rs.Consumes; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import com.cdancy.jenkins.rest.domain.job.*; import com.cdancy.jenkins.rest.parsers.*; import org.jclouds.Fallbacks; import org.jclouds.javax.annotation.Nullable; -import org.jclouds.rest.annotations.BinderParam; -import org.jclouds.rest.annotations.Fallback; -import org.jclouds.rest.annotations.ParamParser; -import org.jclouds.rest.annotations.Payload; -import org.jclouds.rest.annotations.PayloadParam; -import org.jclouds.rest.annotations.RequestFilters; -import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.annotations.*; import com.cdancy.jenkins.rest.binders.BindMapToForm; import com.cdancy.jenkins.rest.domain.common.IntegerResponse; @@ -89,6 +78,16 @@ InputStream artifact(@Nullable @PathParam("optionalFolderPath") @ParamParser(Opt @PathParam("number") int buildNumber, @PathParam("relativeArtifactPath") String relativeArtifactPath); + + @Named("jobs:check-job-name") + @Path("{optionalFolderPath}checkJobName") + @Fallback(Fallbacks.NullOnNotFoundOr404.class) + @Consumes(MediaType.APPLICATION_XHTML_XML) + @ResponseParser(JobExistedParser.class) + @GET + JobExisted checkJobName(@Nullable @PathParam("optionalFolderPath") @ParamParser(OptionalFolderPathParser.class) String optionalFolderPath, + @QueryParam("value") String jobName); + @Named("jobs:create") @Path("{optionalFolderPath}createItem") @Fallback(JenkinsFallbacks.RequestStatusOnError.class) @@ -101,6 +100,19 @@ RequestStatus create(@Nullable @PathParam("optionalFolderPath") @ParamParser(Opt @QueryParam("name") String jobName, @PayloadParam(value = "configXML") String configXML); + + + + @Named("jobs:create-folder") + @Path("{optionalFolderPath}createItem") + @Fallback(JenkinsFallbacks.RequestStatusOnError.class) + @ResponseParser(RequestStatusParser.class) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @QueryParams(keys = "mode", values = "com.cloudbees.hudson.plugins.folder.Folder") + @POST + RequestStatus createFolder(@Nullable @PathParam("optionalFolderPath") @ParamParser(OptionalFolderPathParser.class) String optionalFolderPath, + @QueryParam("name") String name); + @Named("jobs:get-config") @Path("{optionalFolderPath}job/{name}/config.xml") @Fallback(Fallbacks.NullOnNotFoundOr404.class) diff --git a/src/main/java/com/cdancy/jenkins/rest/parsers/BuildNumberToInteger.java b/src/main/java/com/cdancy/jenkins/rest/parsers/BuildNumberToInteger.java index 056fec5f..931070ba 100644 --- a/src/main/java/com/cdancy/jenkins/rest/parsers/BuildNumberToInteger.java +++ b/src/main/java/com/cdancy/jenkins/rest/parsers/BuildNumberToInteger.java @@ -42,6 +42,7 @@ public Integer apply(HttpResponse response) { public String getTextOutput(HttpResponse response) { InputStream is = null; try { + is = response.getPayload().openStream(); return CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8)).trim(); } catch (Exception e) { diff --git a/src/main/java/com/cdancy/jenkins/rest/parsers/JobExistedParser.java b/src/main/java/com/cdancy/jenkins/rest/parsers/JobExistedParser.java new file mode 100644 index 00000000..9265a966 --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/parsers/JobExistedParser.java @@ -0,0 +1,24 @@ +package com.cdancy.jenkins.rest.parsers; + +import com.cdancy.jenkins.rest.domain.job.JobExisted; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jclouds.http.HttpResponse; +import com.google.common.base.Function; +import javax.inject.Singleton; + +import static com.cdancy.jenkins.rest.JenkinsUtils.getTextOutput; + +/** + * Author: kun.tang@daocloud.io + * Date:2024/9/11 + * Time:15:34 + */ + + +@Singleton +public class JobExistedParser implements Function { + @Override + public @Nullable JobExisted apply(@Nullable HttpResponse httpResponse) { + return JobExisted.create(getTextOutput(httpResponse)); + } +} diff --git a/src/main/java/com/cdancy/jenkins/rest/parsers/OutputToProgressiveText.java b/src/main/java/com/cdancy/jenkins/rest/parsers/OutputToProgressiveText.java index 4dcd8a80..82d71183 100644 --- a/src/main/java/com/cdancy/jenkins/rest/parsers/OutputToProgressiveText.java +++ b/src/main/java/com/cdancy/jenkins/rest/parsers/OutputToProgressiveText.java @@ -29,6 +29,8 @@ import com.google.common.base.Function; import com.google.common.io.CharStreams; +import static com.cdancy.jenkins.rest.JenkinsUtils.getTextOutput; + /** * Created by dancc on 3/11/16. */ @@ -43,16 +45,7 @@ public ProgressiveText apply(HttpResponse response) { return ProgressiveText.create(text, size, hasMoreData); } - public String getTextOutput(HttpResponse response) { - try (InputStream is = response.getPayload().openStream()) { - return CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8)); - } catch (Exception e) { - // ignore - } - // ignore - return null; - } public int getTextSize(HttpResponse response) { String textSize = response.getFirstHeaderOrNull("X-Text-Size"); diff --git a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java index 274bb5f3..8000a3f2 100644 --- a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java +++ b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java @@ -52,6 +52,12 @@ public void testCreateJob() { assertTrue(success.value()); } + @Test + public void testCreateFolder(){ + RequestStatus success = api().createFolder(null,"DevTestFolder"); + assertTrue(success.value()); + } + // The next 3 tests must run one after the other as they use the same Job @Test public void testStopFreeStyleBuild() throws InterruptedException { @@ -192,6 +198,20 @@ public void testGetJobInfo() { assertTrue(output.builds().isEmpty()); } + @Test(dependsOnMethods = "testCreateJob") + public void testCheckJobNameExisted() { + JobExisted output = api().checkJobName(null,"DevTest"); + assertNotNull(output); + assertTrue(output.existed()); + } + + @Test(dependsOnMethods = "testCreateJob") + public void testCheckJobNameNotExisted() { + JobExisted output = api().checkJobName(null,"DevTest1"); + assertNotNull(output); + assertFalse(output.existed()); + } + @Test(dependsOnMethods = "testGetJobInfo") public void testLastBuildNumberOnJobWithNoBuilds() { Integer output = api().lastBuildNumber(null, "DevTest"); diff --git a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java index a078884e..f16024f2 100644 --- a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java +++ b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java @@ -29,6 +29,7 @@ import javax.ws.rs.core.MediaType; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -115,9 +116,52 @@ public void testGetJobInfoNotFound() throws Exception { } } - public void testGetBuildInfo() throws Exception { + public void testCheckJobNameExisted() throws Exception { + MockWebServer server = mockWebServer(); + String body = payloadFromResource("/checkJobNameExisted.txt"); + server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.url("/").url()); + JobsApi api = jenkinsApi.jobsApi(); + try { + JobExisted output = api.checkJobName(null,"DevTest"); + assertTrue(output.existed()); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + + public void testCheckJobNameNotExisted() throws Exception { MockWebServer server = mockWebServer(); + String body = payloadFromResource("/checkJobNameNotExisted.txt"); + server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.url("/").url()); + JobsApi api = jenkinsApi.jobsApi(); + try { + JobExisted output = api.checkJobName(null,"DevTest1"); + assertFalse(output.existed()); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + + public void testCheckJobNameNotFound() throws Exception { + MockWebServer server = mockWebServer(); + server.enqueue(new MockResponse().setResponseCode(404)); + JenkinsApi jenkinsApi = api(server.url("/").url()); + JobsApi api = jenkinsApi.jobsApi(); + try { + JobExisted output = api.checkJobName(null,"DevTest1"); + assertNull(output); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + public void testGetBuildInfo() throws Exception { + MockWebServer server = mockWebServer(); String body = payloadFromResource("/build-info.json"); server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); JenkinsApi jenkinsApi = api(server.url("/").url()); @@ -174,6 +218,21 @@ public void testCreateJob() throws Exception { server.shutdown(); } } + public void testCreateFolder() throws Exception { + MockWebServer server = mockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.url("/").url()); + JobsApi api = jenkinsApi.jobsApi(); + try { + RequestStatus success = api.createFolder(null, "DevTestFolder"); + assertNotNull(success); + assertTrue(success.value()); + assertTrue(success.errors().isEmpty()); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } public void testCreateJobInFolder() throws Exception { MockWebServer server = mockWebServer(); diff --git a/src/test/resources/checkJobNameExisted.txt b/src/test/resources/checkJobNameExisted.txt new file mode 100644 index 00000000..16ce1b50 --- /dev/null +++ b/src/test/resources/checkJobNameExisted.txt @@ -0,0 +1 @@ +
A job already exists with the name ‘DevTest’
\ No newline at end of file diff --git a/src/test/resources/checkJobNameNotExisted.txt b/src/test/resources/checkJobNameNotExisted.txt new file mode 100644 index 00000000..78e7012b --- /dev/null +++ b/src/test/resources/checkJobNameNotExisted.txt @@ -0,0 +1 @@ +
\ No newline at end of file