Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ A java integration library for AeroGear Digger

## Usage

Build a default client:
```
DiggerClient client = DiggerClient.createDefaultWithAuth("https://digger.com", "admin", "password");
```

Build a customized client:
```
DiggerClient client = DiggerClient.builder()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome idea!

.createJobService(new CreateJobService())
.triggerBuildService(new TriggerBuildService(10000, 100))
.withAuth("https://digger.com", "admin", "password")
.build();
```

Create job:

```
Expand Down
71 changes: 55 additions & 16 deletions src/main/java/com/redhat/digkins/DiggerClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,71 @@ public class DiggerClient {

public static final long DEFAULT_BUILD_TIMEOUT = 60 * 1000;

private final JenkinsServer jenkins;
private JenkinsServer jenkinsServer;

public DiggerClient(JenkinsAuth auth) throws URISyntaxException {
this.jenkins = new JenkinsServer(new URI(auth.getUrl()), auth.getUser(), auth.getPassword());
private CreateJobService createJobService;
private TriggerBuildService triggerBuildService;

private DiggerClient() {
}

/**
* Create client using provided url and credentials
* Create a client with defaults using provided url and credentials.
* <p>
* This client will use the defaults for the services. This is perfectly fine for majorith of the cases.
*
* @param url Jenkins url
* @param user Jenkins user
* @param password Jenkins password
* @return client instance
* @throws DiggerClientException if something goes wrong
*/
public static DiggerClient from(String url, String user, String password) throws DiggerClientException {
try {
JenkinsAuth jenkinsAuth = new JenkinsAuth(url, user, password);
return new DiggerClient(jenkinsAuth);
} catch (URISyntaxException e) {
throw new DiggerClientException("Invalid jenkins url format.");
public static DiggerClient createDefaultWithAuth(String url, String user, String password) throws DiggerClientException {
return DiggerClient.builder()
.createJobService(new CreateJobService())
.triggerBuildService(new TriggerBuildService(TriggerBuildService.DEFAULT_FIRST_CHECK_DELAY, TriggerBuildService.DEFAULT_POLL_PERIOD))
.withAuth(url, user, password)
.build();
}

public static DiggerClientBuilder builder() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO there's no point on having this method since DiggerClientBuilder is already public.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, right..

I personally think that DiggerClient.builder().foo().bar().build() is more fluent than new DiggerClientBuilder().foo().bar().build().
I am inspired by Guava in this idea.

I prefer keeping it for now, unless more objections arise.

return new DiggerClientBuilder();
}

public static class DiggerClientBuilder {
private JenkinsAuth auth;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like it. That would be easy to test.

private CreateJobService createJobService;
private TriggerBuildService triggerBuildService;

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;
return this;
}

public DiggerClientBuilder triggerBuildService(TriggerBuildService triggerBuildService) {
this.triggerBuildService = triggerBuildService;
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.triggerBuildService = this.triggerBuildService;
return client;
} catch (URISyntaxException e) {
throw new DiggerClientException("Invalid jenkins url format.");
}
}
}


/**
* Create new Digger job on Jenkins platform
*
Expand All @@ -55,9 +96,8 @@ public static DiggerClient from(String url, String user, String password) throws
* @throws DiggerClientException if something goes wrong
*/
public void createJob(String name, String gitRepo, String gitBranch) throws DiggerClientException {
CreateJobService service = new CreateJobService(this.jenkins);
try {
service.create(name, gitRepo, gitBranch);
createJobService.create(this.jenkinsServer, name, gitRepo, gitBranch);
} catch (Throwable e) {
throw new DiggerClientException(e);
}
Expand All @@ -72,22 +112,21 @@ public void createJob(String name, String gitRepo, String gitBranch) throws Digg
* This method will block until there is a build number, or the given timeout period is passed. If the build is still in the queue
* after the given timeout period, a {@code BuildStatus} is returned with state {@link BuildStatus.State#TIMED_OUT}.
* <p>
* Please note that timeout period is never meant to be very precise. It has the resolution of {@link TriggerBuildService#POLL_PERIOD} because
* Please note that timeout period is never meant to be very precise. It has the resolution of {@link TriggerBuildService#DEFAULT_POLL_PERIOD} because
* timeout is checked before every pull.
* <p>
* Similarly, {@link BuildStatus.State#CANCELLED_IN_QUEUE} is returned if the build is cancelled on Jenkins side and
* {@link BuildStatus.State#STUCK_IN_QUEUE} is returned if the build is stuck.
*
* @param jobName name of the job to trigger the build
* @param timeout how many milliseconds should this call block before returning {@link BuildStatus.State#TIMED_OUT}.
* Should be larger than {@link TriggerBuildService#FIRST_CHECK_DELAY}
* Should be larger than {@link TriggerBuildService#DEFAULT_FIRST_CHECK_DELAY}
* @return the build status
* @throws DiggerClientException if connection problems occur during connecting to Jenkins
*/
public BuildStatus build(String jobName, long timeout) throws DiggerClientException {
final TriggerBuildService triggerBuildService = new TriggerBuildService(jenkins);
try {
return triggerBuildService.build(jobName, timeout);
return triggerBuildService.build(this.jenkinsServer, jobName, timeout);
} catch (IOException e) {
LOG.debug("Exception while connecting to Jenkins", e);
throw new DiggerClientException(e);
Expand Down
22 changes: 8 additions & 14 deletions src/main/java/com/redhat/digkins/services/CreateJobService.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,20 @@ public class CreateJobService {
private final static String GIT_REPO_URL = "GIT_REPO_URL";
private final static String GIT_REPO_BRANCH = "GIT_REPO_BRANCH";

private JenkinsServer jenkins;

/**
* @param jenkins jenkins api instance
*/
public CreateJobService(JenkinsServer jenkins) {
this.jenkins = jenkins;
}
private static final String JOB_TEMPLATE_PATH = "templates/job.xml";

/**
* Create new digger job on jenkins platform
*
* @param name job name that can be used later to reference job
* @param gitRepo git repository url (full git repository url. e.g git@github.com:digger/helloworld.git
* @param gitBranch git repository branch (default branch used to checkout source code)
* @param jenkinsServer Jenkins server client
* @param name job name that can be used later to reference job
* @param gitRepo git repository url (full git repository url. e.g git@github.com:digger/helloworld.git
* @param gitBranch git repository branch (default branch used to checkout source code)
*/
public void create(String name, String gitRepo, String gitBranch) throws IOException {
JtwigTemplate template = JtwigTemplate.classpathTemplate("templates/job.xml");
public void create(JenkinsServer jenkinsServer, String name, String gitRepo, String gitBranch) throws IOException {
JtwigTemplate template = JtwigTemplate.classpathTemplate(JOB_TEMPLATE_PATH);
JtwigModel model = JtwigModel.newModel().with(GIT_REPO_URL, gitRepo).with(GIT_REPO_BRANCH, gitBranch);
jenkins.createJob(name, template.render(model));
jenkinsServer.createJob(name, template.render(model));
}

}
34 changes: 19 additions & 15 deletions src/main/java/com/redhat/digkins/services/TriggerBuildService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,40 @@ public class TriggerBuildService {
private static final Logger LOG = LoggerFactory.getLogger(TriggerBuildService.class);

/**
* How long should we wait before we start checking the queue item status.
* Default value of {@link #firstCheckDelay}
*/
public static final long FIRST_CHECK_DELAY = 5 * 1000L;
public static final long DEFAULT_FIRST_CHECK_DELAY = 5 * 1000L;

/**
* How long should we wait before checking the queue item status for next time.
* Default value of {@link #pollPeriod}
*/
public static final long POLL_PERIOD = 2 * 1000L;
public static final long DEFAULT_POLL_PERIOD = 2 * 1000L;


private JenkinsServer jenkinsServer;
private long firstCheckDelay;
private long pollPeriod;

/**
* @param jenkinsServer jenkins api instance
* @param firstCheckDelay how long should we wait (in milliseconds) before we start checking the queue item status
* @param pollPeriod how long should we wait (in milliseconds) before checking the queue item status for next time
*/
public TriggerBuildService(JenkinsServer jenkinsServer) {
this.jenkinsServer = jenkinsServer;
public TriggerBuildService(long firstCheckDelay, long pollPeriod) {
this.firstCheckDelay = firstCheckDelay;
this.pollPeriod = pollPeriod;
}

/**
* See the documentation in {@link com.redhat.digkins.DiggerClient#build(String, long)}
*
* @param jobName name of the job
* @param timeout timeout
* @param jenkinsServer Jenkins server client
* @param jobName name of the job
* @param timeout timeout
* @return the build status
* @throws IOException if connection problems occur during connecting to Jenkins
* @throws InterruptedException if a problem occurs during sleeping between checks
* @see com.redhat.digkins.DiggerClient#build(String, long)
*/
public BuildStatus build(String jobName, long timeout) throws IOException, InterruptedException {
public BuildStatus build(JenkinsServer jenkinsServer, String jobName, long timeout) throws IOException, InterruptedException {
final long whenToTimeout = System.currentTimeMillis() + timeout;

LOG.debug("Going to build job with name: {}", jobName);
Expand All @@ -72,8 +76,8 @@ public BuildStatus build(String jobName, long timeout) throws IOException, Inter
// do it until we have an executable.
// we would have an executable when the build leaves queue and starts building.

LOG.debug("Going to sleep {} msecs", FIRST_CHECK_DELAY);
Thread.sleep(FIRST_CHECK_DELAY);
LOG.debug("Going to sleep {} msecs", firstCheckDelay);
Thread.sleep(firstCheckDelay);

QueueItem queueItem;
while (true) {
Expand Down Expand Up @@ -107,8 +111,8 @@ public BuildStatus build(String jobName, long timeout) throws IOException, Inter
} else {
LOG.debug("Build did not start executing yet.");
if (whenToTimeout > System.currentTimeMillis()) {
LOG.debug("Timeout period has not exceeded yet. Sleeping for {} msecs", POLL_PERIOD);
Thread.sleep(POLL_PERIOD);
LOG.debug("Timeout period has not exceeded yet. Sleeping for {} msecs", pollPeriod);
Thread.sleep(pollPeriod);
} else {
LOG.debug("Timeout period has exceeded. Returning TIMED_OUT.");
return new BuildStatus(BuildStatus.State.TIMED_OUT, -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;

import static org.assertj.core.api.Assertions.*;

import org.mockito.runners.MockitoJUnitRunner;

import static org.assertj.core.api.Assertions.assertThat;


@RunWith(MockitoJUnitRunner.class)
public class TriggerBuildServiceTest {
Expand All @@ -32,27 +31,27 @@ public class TriggerBuildServiceTest {

@Before
public void setUp() throws Exception {
service = new TriggerBuildService(jenkinsServer);
service = new TriggerBuildService(300, 50); // wait for 300 msecs for initial build, check every 50 msecs

Mockito.when(jenkinsServer.getJob("TEST")).thenReturn(mockJob);
}

@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionIfJobCannotBeFound() throws Exception {
service.build("UNKNOWN", 10000);
service.build(jenkinsServer, "UNKNOWN", 10000);
}

@Test(expected = IllegalStateException.class)
public void shouldThrowExceptionIfJenkinsDoesNotReturnQueueReference() throws Exception {
Mockito.when(mockJob.build()).thenReturn(null);
service.build("TEST", 10000);
service.build(jenkinsServer, "TEST", 10000);
}

@Test(expected = IllegalStateException.class)
public void shouldThrowExceptionIfQueueItemIsNullForReference() throws Exception {
Mockito.when(mockJob.build()).thenReturn(queueReference);
Mockito.when(jenkinsServer.getQueueItem(queueReference)).thenReturn(null);
service.build("TEST", 10000);
service.build(jenkinsServer, "TEST", 10000);
}

@Test
Expand All @@ -63,7 +62,7 @@ public void shouldReturnCancelledStatus() throws Exception {
Mockito.when(mockJob.build()).thenReturn(queueReference);
Mockito.when(jenkinsServer.getQueueItem(queueReference)).thenReturn(queueItem);

final BuildStatus buildStatus = service.build("TEST", 10000);
final BuildStatus buildStatus = service.build(jenkinsServer, "TEST", 10000);
assertThat(buildStatus).isNotNull();
assertThat(buildStatus.getState()).isEqualTo(BuildStatus.State.CANCELLED_IN_QUEUE);
}
Expand All @@ -76,7 +75,7 @@ public void shouldReturnStuckStatus() throws Exception {
Mockito.when(mockJob.build()).thenReturn(queueReference);
Mockito.when(jenkinsServer.getQueueItem(queueReference)).thenReturn(queueItem);

final BuildStatus buildStatus = service.build("TEST", 10000);
final BuildStatus buildStatus = service.build(jenkinsServer, "TEST", 10000);
assertThat(buildStatus).isNotNull();
assertThat(buildStatus.getState()).isEqualTo(BuildStatus.State.STUCK_IN_QUEUE);
}
Expand All @@ -90,7 +89,7 @@ public void shouldReturnBuildNumber() throws Exception {

Mockito.when(mockJob.build()).thenReturn(queueReference);
Mockito.when(jenkinsServer.getQueueItem(queueReference)).thenReturn(queueItem);
final BuildStatus buildStatus = service.build("TEST", 10000);
final BuildStatus buildStatus = service.build(jenkinsServer, "TEST", 10000);

assertThat(buildStatus).isNotNull();
assertThat(buildStatus.getState()).isEqualTo(BuildStatus.State.BUILDING);
Expand All @@ -108,7 +107,7 @@ public void shouldReturnBuildNumber_whenDidNotStartExecutingImmediately() throws
Mockito.when(mockJob.build()).thenReturn(queueReference);
// return `not-building` for the first 2 checks, then return `building`
Mockito.when(jenkinsServer.getQueueItem(queueReference)).thenReturn(queueItemNotBuildingYet, queueItemNotBuildingYet, queueItemBuilding);
final BuildStatus buildStatus = service.build("TEST", 20000L);
final BuildStatus buildStatus = service.build(jenkinsServer, "TEST", 10000L);

assertThat(buildStatus).isNotNull();
assertThat(buildStatus.getState()).isEqualTo(BuildStatus.State.BUILDING);
Expand All @@ -123,7 +122,7 @@ public void shouldReturnTimeout() throws Exception {

Mockito.when(mockJob.build()).thenReturn(queueReference);
Mockito.when(jenkinsServer.getQueueItem(queueReference)).thenReturn(queueItemNotBuildingYet);
final BuildStatus buildStatus = service.build("TEST", 10000L);
final BuildStatus buildStatus = service.build(jenkinsServer, "TEST", 500L);

assertThat(buildStatus).isNotNull();
assertThat(buildStatus.getState()).isEqualTo(BuildStatus.State.TIMED_OUT);
Expand Down