diff --git a/README.md b/README.md index 30cbb60..ada9b9f 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Build a default client: Build a customized client: ``` DiggerClient client = DiggerClient.builder() - .createJobService(new CreateJobService()) - .triggerBuildService(new TriggerBuildService(10000, 100)) + .jobService(new JobService()) + .triggerBuildService(new BuildService(10000, 100)) + .artifactsService(artifactsService) .withAuth("https://digger.com", "admin", "password") .build(); ``` diff --git a/pom.xml b/pom.xml index 0d09fa4..e30ff9d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,11 +4,21 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - groupId + org.jboss.aerogear jenkins-client 1.0.0 jar + + 4.11 + 1.9.5 + 0.3.7-SNAPSHOT + 5.65 + 1.7.21 + 1.7.21 + 3.6.1 + + Apache License, Version 2.0 @@ -22,42 +32,41 @@ com.offbytwo.jenkins jenkins-client - 0.3.7-SNAPSHOT + ${jenkins.client.version} org.jtwig jtwig-core - 5.65 + ${jtwig.templates.version} org.slf4j slf4j-api - 1.7.21 + ${slf4j.api.version} org.slf4j slf4j-log4j12 - 1.7.21 + ${slf4j-log4j12.version} junit junit - 4.12 + ${junit.version} test - org.mockito mockito-core - 1.10.19 + ${mockito-core.version} test org.assertj assertj-core - 3.6.1 + ${assertj-core.version} test diff --git a/src/main/java/com/redhat/digkins/DiggerClient.java b/src/main/java/com/redhat/digkins/DiggerClient.java index f4edc49..abaf8ba 100644 --- a/src/main/java/com/redhat/digkins/DiggerClient.java +++ b/src/main/java/com/redhat/digkins/DiggerClient.java @@ -2,14 +2,17 @@ import com.offbytwo.jenkins.JenkinsServer; import com.redhat.digkins.model.BuildStatus; -import com.redhat.digkins.services.CreateJobService; +import com.redhat.digkins.services.ArtifactsService; +import com.redhat.digkins.services.JobService; import com.redhat.digkins.services.TriggerBuildService; import com.redhat.digkins.util.DiggerClientException; import com.redhat.digkins.util.JenkinsAuth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; @@ -24,8 +27,9 @@ public class DiggerClient { private JenkinsServer jenkinsServer; - private CreateJobService createJobService; + private JobService jobService; private TriggerBuildService triggerBuildService; + private ArtifactsService artifactsService; private DiggerClient() { } @@ -42,9 +46,13 @@ private DiggerClient() { * @throws DiggerClientException if something goes wrong */ public static DiggerClient createDefaultWithAuth(String url, String user, String password) throws DiggerClientException { + TriggerBuildService triggerBuildService = new TriggerBuildService(TriggerBuildService.DEFAULT_FIRST_CHECK_DELAY, TriggerBuildService.DEFAULT_POLL_PERIOD); + JobService jobService = new JobService(); + ArtifactsService artifactsService = new ArtifactsService(); return DiggerClient.builder() - .createJobService(new CreateJobService()) - .triggerBuildService(new TriggerBuildService(TriggerBuildService.DEFAULT_FIRST_CHECK_DELAY, TriggerBuildService.DEFAULT_POLL_PERIOD)) + .createJobService(jobService) + .triggerBuildService(triggerBuildService) + .artifactsService(artifactsService) .withAuth(url, user, password) .build(); } @@ -52,19 +60,19 @@ public static DiggerClient createDefaultWithAuth(String url, String user, String public static DiggerClientBuilder builder() { return new DiggerClientBuilder(); } - public static class DiggerClientBuilder { private JenkinsAuth auth; - private CreateJobService createJobService; + private JobService jobService; private TriggerBuildService triggerBuildService; + private ArtifactsService artifactsService; public DiggerClientBuilder withAuth(String url, String user, String password) { this.auth = new JenkinsAuth(url, user, password); return this; } - public DiggerClientBuilder createJobService(CreateJobService createJobService) { - this.createJobService = createJobService; + public DiggerClientBuilder createJobService(JobService jobService) { + this.jobService = jobService; return this; } @@ -73,12 +81,18 @@ public DiggerClientBuilder triggerBuildService(TriggerBuildService triggerBuildS return this; } + public DiggerClientBuilder artifactsService(ArtifactsService artifactsService) { + this.artifactsService = artifactsService; + return this; + } + public DiggerClient build() throws DiggerClientException { final DiggerClient client = new DiggerClient(); try { client.jenkinsServer = new JenkinsServer(new URI(auth.getUrl()), auth.getUser(), auth.getPassword()); - client.createJobService = this.createJobService; + client.jobService = this.jobService; client.triggerBuildService = this.triggerBuildService; + client.artifactsService = this.artifactsService; return client; } catch (URISyntaxException e) { throw new DiggerClientException("Invalid jenkins url format."); @@ -97,7 +111,7 @@ public DiggerClient build() throws DiggerClientException { */ public void createJob(String name, String gitRepo, String gitBranch) throws DiggerClientException { try { - createJobService.create(this.jenkinsServer, name, gitRepo, gitBranch); + jobService.create(this.jenkinsServer, name, gitRepo, gitBranch); } catch (Throwable e) { throw new DiggerClientException(e); } @@ -153,4 +167,32 @@ public BuildStatus build(String jobName) throws DiggerClientException { return this.build(jobName, DEFAULT_BUILD_TIMEOUT); } + /** + * Fetch artifacts urls for specific job and build number + * + * @param jobName name of the job + * @param buildNumber job build number + * @param artifactName - name of the artifact to fetch - can be regexp + * @return InputStream with file contents + * @throws DiggerClientException - when problem with fetching artifacts from jenkins + */ + public InputStream fetchArtifact(String jobName, int buildNumber, String artifactName) throws DiggerClientException { + return artifactsService.streamArtifact(jenkinsServer,jobName, buildNumber, artifactName); + } + + /** + * Save artifact for specified location for specific job, build number and artifact name. + * If name would be an regular expression method would return stream for the first match. + * + * @param jobName name of the job + * @param buildNumber job build number + * @param artifactName name of the artifact to fetch - can be regexp for example *.apk + * @param outputFile file (location) used to save artifact + * + * @throws DiggerClientException when problem with fetching artifacts from jenkins + * @throws IOException when one of the files cannot be saved + */ + public void saveArtifact(String jobName, int buildNumber, String artifactName, File outputFile) throws DiggerClientException, IOException { + artifactsService.saveArtifact(jenkinsServer,jobName, buildNumber, artifactName,outputFile); + } } diff --git a/src/main/java/com/redhat/digkins/Main.java b/src/main/java/com/redhat/digkins/Main.java new file mode 100644 index 0000000..7800df4 --- /dev/null +++ b/src/main/java/com/redhat/digkins/Main.java @@ -0,0 +1,36 @@ +package com.redhat.digkins; + +import com.redhat.digkins.services.JobService; +import com.redhat.digkins.services.TriggerBuildService; +import com.redhat.digkins.util.DiggerClientException; +import org.apache.commons.io.IOUtils; + +import java.io.*; + +/** + */ +public class Main { + + public static void main(String[] args) throws DiggerClientException, IOException { + DiggerClient client = DiggerClient.builder() + .createJobService(new JobService()) + .triggerBuildService(new TriggerBuildService(10000, 100)) + .withAuth("https://jenkins-digger2.osm3.feedhenry.net", "admin", "Vu8ysYH5f2dJiLgL") + .build(); + //client.createJob("wtr-java-tests2", "https://github.com/wtrocki/helloworld-android-gradle", "master"); + //client.build("wtr-java-tests2"); + InputStream inputStream = client.fetchArtifact("wtr-java-tests2", 1, "app-debug.apk"); + if(inputStream != null){ + File targetFile = new File("artifact.tmp"); + OutputStream outStream = new FileOutputStream(targetFile); + + byte[] buffer = new byte[8 * 1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outStream.write(buffer, 0, bytesRead); + } + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(outStream); + } + } +} diff --git a/src/main/java/com/redhat/digkins/services/ArtifactsService.java b/src/main/java/com/redhat/digkins/services/ArtifactsService.java new file mode 100644 index 0000000..68efca8 --- /dev/null +++ b/src/main/java/com/redhat/digkins/services/ArtifactsService.java @@ -0,0 +1,94 @@ +package com.redhat.digkins.services; + +import com.offbytwo.jenkins.JenkinsServer; +import com.offbytwo.jenkins.model.Artifact; +import com.offbytwo.jenkins.model.Build; +import com.offbytwo.jenkins.model.BuildWithDetails; +import com.offbytwo.jenkins.model.JobWithDetails; +import com.redhat.digkins.util.DiggerClientException; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.URISyntaxException; +import java.util.List; + +/** + * Service used to retrieve artifacts + */ +public class ArtifactsService { + + /** + */ + public ArtifactsService() { + } + + private static final Logger LOG = LoggerFactory.getLogger(ArtifactsService.class); + private static int DEFAULT_BUFFER = 8 * 1024; + + /** + * Save artifact for specified location for specific job, build number and artifact name. + * If name would be an regular expression method would return stream for the first match. + * + * @param jobName name of the job + * @param buildNumber job build number + * @param artifactName name of the artifact to fetch - can be regexp for example *.apk + * @param outputFile file (location) used to save artifact + * @throws DiggerClientException when problem with fetching artifacts from jenkins + * @throws IOException when one of the files cannot be saved + */ + public void saveArtifact(JenkinsServer jenkins, String jobName, int buildNumber, String artifactName, File outputFile) throws DiggerClientException, IOException { + InputStream inputStream = streamArtifact(jenkins, jobName, buildNumber, artifactName); + if (inputStream != null) { + OutputStream outStream = new FileOutputStream(outputFile); + byte[] buffer = new byte[DEFAULT_BUFFER]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outStream.write(buffer, 0, bytesRead); + } + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(outStream); + } else { + throw new DiggerClientException("Cannot fetch artifacts from jenkins"); + } + } + + /** + * Get artifact inputstream for specific job, build number and artifact name. + * If name would be an regular expression method would return stream for the first match. + * + * @param jobName name of the job + * @param buildNumber job build number + * @param artifactName name of the artifact to fetch - can be regexp for example *.apk + * @return InputStream with file contents that can be saved or piped to socket + * @throws DiggerClientException when problem with fetching artifacts from jenkins + */ + public InputStream streamArtifact(JenkinsServer jenkins, String jobName, int buildNumber, String artifactName) throws DiggerClientException { + try { + JobWithDetails job = jenkins.getJob(jobName); + if (job == null) { + LOG.error("Cannot fetch job from jenkins {0}", jobName); + throw new DiggerClientException("Cannot fetch job from jenkins"); + } + Build build = job.getBuildByNumber(buildNumber); + BuildWithDetails buildWithDetails = build.details(); + List artifacts = buildWithDetails.getArtifacts(); + for (Artifact artifact : artifacts) { + if (artifact.getFileName().matches(artifactName)) { + LOG.debug("Streaming artifact {0}", artifactName); + return buildWithDetails.downloadArtifact(artifact); + } + } + } catch (URISyntaxException e) { + LOG.error("Invalid job name {0}", jobName, e); + throw new DiggerClientException(e); + } catch (IOException e) { + LOG.error("Problem when fetching artifacts for {0} {1} {2}", jobName, buildNumber, artifactName, e); + throw new DiggerClientException(e); + } + LOG.debug("Cannot find build for ", jobName, buildNumber, artifactName); + return null; + } + +} diff --git a/src/main/java/com/redhat/digkins/services/CreateJobService.java b/src/main/java/com/redhat/digkins/services/JobService.java similarity index 97% rename from src/main/java/com/redhat/digkins/services/CreateJobService.java rename to src/main/java/com/redhat/digkins/services/JobService.java index 1d658bd..521fc1d 100644 --- a/src/main/java/com/redhat/digkins/services/CreateJobService.java +++ b/src/main/java/com/redhat/digkins/services/JobService.java @@ -9,11 +9,10 @@ /** * Create digger job on jenkins platform */ -public class CreateJobService { +public class JobService { private final static String GIT_REPO_URL = "GIT_REPO_URL"; private final static String GIT_REPO_BRANCH = "GIT_REPO_BRANCH"; - private static final String JOB_TEMPLATE_PATH = "templates/job.xml"; /** @@ -29,5 +28,4 @@ public void create(JenkinsServer jenkinsServer, String name, String gitRepo, Str JtwigModel model = JtwigModel.newModel().with(GIT_REPO_URL, gitRepo).with(GIT_REPO_BRANCH, gitBranch); jenkinsServer.createJob(name, template.render(model)); } - } diff --git a/src/test/java/com/redhat/digkins/services/ArtifactsServiceTests.java b/src/test/java/com/redhat/digkins/services/ArtifactsServiceTests.java new file mode 100644 index 0000000..59d4fc2 --- /dev/null +++ b/src/test/java/com/redhat/digkins/services/ArtifactsServiceTests.java @@ -0,0 +1,87 @@ +package com.redhat.digkins.services; + +import com.offbytwo.jenkins.JenkinsServer; +import com.offbytwo.jenkins.model.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ArtifactsServiceTests { + + @Mock + private JenkinsServer server; + private ArtifactsService artifactsService; + + @Before + public void beforeTests() { + artifactsService = new ArtifactsService(); + } + + @Test + public void shouldFetchArtifactsWithNoResults() throws Exception { + BuildWithDetails build = mock(BuildWithDetails.class); + JobWithDetails job = mock(JobWithDetails.class); + when(server.getJob(anyString())).thenReturn(job); + when(job.getBuildByNumber(anyInt())).thenReturn(build); + when(build.details()).thenReturn(build); + InputStream artifact = artifactsService.streamArtifact(server, "artifact", 1, "test"); + + assertNull(artifact); + } + + @Test + public void shouldFetchArtifactsWithResults() throws Exception { + Artifact artifact = mock(Artifact.class); + JobWithDetails job = mock(JobWithDetails.class); + BuildWithDetails build = mock(BuildWithDetails.class); + FileInputStream fs = mock(FileInputStream.class); + when(server.getJob(anyString())).thenReturn(job); + when(job.getBuildByNumber(anyInt())).thenReturn(build); + when(build.details()).thenReturn(build); + when(build.getArtifacts()).thenReturn(Arrays.asList(artifact)); + when(build.downloadArtifact(artifact)).thenReturn(fs); + String artifactName = "test"; + when(artifact.getFileName()).thenReturn(artifactName); + InputStream artifactStream = artifactsService.streamArtifact(server, "artifact", 1, artifactName); + + assertThat(artifactStream).isNotNull(); + } + + + @Test + public void shouldSaveFileArtifactWithResults() throws Exception { + Artifact artifact = mock(Artifact.class); + FileInputStream fs = mock(FileInputStream.class); + JobWithDetails job = mock(JobWithDetails.class); + BuildWithDetails build = mock(BuildWithDetails.class); + String artifactName = "test"; + when(server.getJob(anyString())).thenReturn(job); + when(job.getBuildByNumber(anyInt())).thenReturn(build); + when(build.details()).thenReturn(build); + when(build.getArtifacts()).thenReturn(Arrays.asList(artifact)); + when(artifact.getFileName()).thenReturn(artifactName); + when(build.downloadArtifact(artifact)).thenReturn(fs); + when(fs.read(Matchers.anyObject())).thenReturn(-1); + when(build.details()).thenReturn(build); + File outputFile = new File("test.out"); + artifactsService.saveArtifact(server, "artifact", 1, artifactName, outputFile); + + assertThat(outputFile.exists()).isTrue(); + outputFile.delete(); + } +} diff --git a/src/test/java/com/redhat/digkins/services/JobServiceTests.java b/src/test/java/com/redhat/digkins/services/JobServiceTests.java new file mode 100644 index 0000000..96bc0cc --- /dev/null +++ b/src/test/java/com/redhat/digkins/services/JobServiceTests.java @@ -0,0 +1,39 @@ +package com.redhat.digkins.services; + +import com.offbytwo.jenkins.JenkinsServer; +import com.offbytwo.jenkins.model.BuildWithDetails; +import com.offbytwo.jenkins.model.JobWithDetails; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class JobServiceTests { + + @Mock + private JenkinsServer server; + private JobService jobService; + + @Before + public void beforeTests() { + jobService = new JobService(); + } + + @Test + public void shouldCreateJob() throws Exception { + JobWithDetails job = mock(JobWithDetails.class); + BuildWithDetails build = mock(BuildWithDetails.class); + when(job.getBuildByNumber(anyInt())).thenReturn(build); + when(build.details()).thenReturn(build); + jobService.create(server, "name", "repo", "branch"); + verify(server, times(1)).createJob(anyString(), anyString()); + } + +}