getReferences() {
32 | return this.references;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/ExecutorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import java.util.concurrent.ThreadPoolExecutor;
20 |
21 |
22 | /**
23 | * Interface definition for factories that can create a {@link ThreadPoolExecutor}.
24 | */
25 | public interface ExecutorFactory {
26 |
27 | /**
28 | * Returns a new instance of a {@link ThreadPoolExecutor}.
29 | * @return A new {@link ThreadPoolExecutor}.
30 | */
31 | public ThreadPoolExecutor createExecutor();
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/constants/ErrorCode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model.constants;
18 |
19 | /**
20 | * Defines constants for error codes returned by Amazon Web Services (AWS).
21 | */
22 | public final class ErrorCode {
23 |
24 | /**
25 | * The X.509 certificate or AWS access key ID provided does not exist on AWS.
26 | *
27 | * HTTP Status Code: 403
28 | */
29 | public static final String INVALID_CLIENT_TOKEN_ID = "InvalidClientTokenId";
30 |
31 | private ErrorCode() {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/entities/codecommit/Reference.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model.entities.codecommit;
18 |
19 | import com.google.gson.annotations.Expose;
20 | import com.google.gson.annotations.SerializedName;
21 |
22 |
23 | public class Reference {
24 |
25 | @Expose
26 | @SerializedName("commit")
27 | private String commit;
28 |
29 | @Expose
30 | @SerializedName("ref")
31 | private String reference;
32 |
33 | public String getCommit() {
34 | return this.commit;
35 | }
36 |
37 | public String getName() {
38 | return this.reference;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/entities/codecommit/Records.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model.entities.codecommit;
18 |
19 | import com.google.gson.annotations.Expose;
20 | import com.google.gson.annotations.SerializedName;
21 |
22 | import java.util.Iterator;
23 | import java.util.List;
24 |
25 |
26 | public class Records implements Iterable {
27 |
28 | @Expose
29 | @SerializedName("Records")
30 | private List records;
31 |
32 | @Override
33 | public Iterator iterator() {
34 | return this.records.iterator();
35 | }
36 |
37 | public int size() {
38 | return this.records.size();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/MessageParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 |
21 | import java.util.List;
22 |
23 |
24 | /**
25 | * Interface definition for classes that parse {@link Message}s that are returned by a message
26 | * request to an Amazon SQS queue into SCM {@link Event}s.
27 | */
28 | public interface MessageParser {
29 |
30 | /**
31 | * Parses the specified message into one or more events.
32 | * @param message The {@link Message} to parse.
33 | * @return The collection of {@link Event} items contained in the message.
34 | */
35 | List parseMessage(Message message);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/factories/MessageParserFactoryImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.factories;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 |
21 | import io.relution.jenkins.scmsqs.interfaces.MessageParser;
22 | import io.relution.jenkins.scmsqs.interfaces.MessageParserFactory;
23 | import io.relution.jenkins.scmsqs.model.CodeCommitMessageParser;
24 |
25 |
26 | public class MessageParserFactoryImpl implements MessageParserFactory {
27 |
28 | @Override
29 | public MessageParser createParser(final Message message) {
30 | return this.createCodeCommitParser();
31 | }
32 |
33 | @Override
34 | public MessageParser createCodeCommitParser() {
35 | return new CodeCommitMessageParser();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/net/RequestFactory.java:
--------------------------------------------------------------------------------
1 |
2 | package io.relution.jenkins.scmsqs.net;
3 |
4 | import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
5 | import com.amazonaws.services.sqs.model.Message;
6 | import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
7 |
8 | import java.util.List;
9 |
10 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
11 |
12 |
13 | public interface RequestFactory {
14 |
15 | /**
16 | * Returns a new request that can be used to receive messages from the specified queue.
17 | * @param queue The {@link SQSQueue} for which to create the request.
18 | * @return A {@link ReceiveMessageRequest} that can be used to request messages from the
19 | * specified queue.
20 | */
21 | ReceiveMessageRequest createReceiveMessageRequest(final SQSQueue queue);
22 |
23 | /**
24 | * Returns a new request that can be used to delete previously received messages from the
25 | * specified queue.
26 | *
27 | * The specified messages must have been received by a previous receive message request to
28 | * the same queue.
29 | * @param queue The {@link SQSQueue} from which to delete the specified messages.
30 | * @param messages The collection of {@link Message}s to delete.
31 | * @return A {@link DeleteMessageBatchRequest} that can be used to delete messages from the
32 | * specified queue.
33 | */
34 | DeleteMessageBatchRequest createDeleteMessageBatchRequest(final SQSQueue queue, final List messages);
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/EventTriggerMatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import java.util.List;
20 |
21 | import hudson.model.AbstractProject;
22 |
23 |
24 | /**
25 | * Interface definition for classes that match events to {@link AbstractProject}s. If an event
26 | * matches a project its build process should be triggered.
27 | */
28 | public interface EventTriggerMatcher {
29 |
30 | /**
31 | * Returns a value indicating whether any of the specified events matches the specified job.
32 | * @param events The collection of {@link Event}s to test against the job.
33 | * @param job The {@link AbstractProject} to test against.
34 | * @return {@code true} if any of the specified events matches the specified job; otherwise,
35 | * {@code false}.
36 | */
37 | boolean matches(List events, AbstractProject, ?> job);
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/SQSQueueProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import java.util.List;
20 |
21 |
22 | /**
23 | * Interface definition for classes that provide access to a collection of {@link SQSQueue}
24 | * instances.
25 | */
26 | public interface SQSQueueProvider {
27 |
28 | /**
29 | * Returns the collection of {@link SQSQueue} instances associated with this provider.
30 | * @return A collection of {@link SQSQueue} instances.
31 | */
32 | List extends SQSQueue> getSqsQueues();
33 |
34 | /**
35 | * Returns the SQS queue configuration associated with the specified identifier.
36 | * @param uuid The unique identifier of the configuration to get.
37 | * @return The {@link SQSQueue} associated with the specified identifier, or {@code null} if
38 | * no such configuration exists.
39 | */
40 | SQSQueue getSqsQueue(String uuid);
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/MessageParserFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 |
21 |
22 | /**
23 | * Interface definition for factories that can create {@link MessageParser}s suitable for parsing
24 | * {@link Message}s returns by an Amazon SQS queue, based on the message type.
25 | */
26 | public interface MessageParserFactory {
27 |
28 | /**
29 | * Returns a new parser based on the type of the message that is specified.
30 | * @param message The {@link Message} for which to create a parser.
31 | * @return A {@link MessageParser} that can be used to parse the message.
32 | */
33 | MessageParser createParser(Message message);
34 |
35 | /**
36 | * Returns a new parser that can be used to parse messages created by CodeCommit events.
37 | * @return A {@link MessageParser} suitable for parsing CodeCommit events.
38 | */
39 | MessageParser createCodeCommitParser();
40 | }
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/SQSQueueListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 |
21 | import java.util.List;
22 |
23 |
24 | /**
25 | * Interface definition for classes that listen for {@link Message}s that are returned by a request
26 | * to an Amazon SQS queue.
27 | */
28 | public interface SQSQueueListener {
29 |
30 | /**
31 | * The unique identifier of an Amazon SQS configuration that identifies the queue
32 | * this listener is associated with.
33 | * @return The unique identifier of the {@link SQSQueue} this listener is associated with.
34 | */
35 | String getQueueUuid();
36 |
37 | /**
38 | * The method to be invoked when new messages arrive in the SQS queue this listener is
39 | * associated with.
40 | * @param messages The collection of {@link Message} instances that were posted to the queue.
41 | */
42 | void handleMessages(List messages);
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/events/EventBroker.java:
--------------------------------------------------------------------------------
1 |
2 | package io.relution.jenkins.scmsqs.model.events;
3 |
4 | import com.google.common.eventbus.EventBus;
5 |
6 |
7 | /**
8 | * Provides a single instance of {@link EventBus}.
9 | */
10 | public class EventBroker {
11 |
12 | private static EventBroker instance;
13 |
14 | private final EventBus eventBus = new EventBus();
15 |
16 | public synchronized static EventBroker getInstance() {
17 | if (instance == null) {
18 | instance = new EventBroker();
19 | }
20 | return instance;
21 | }
22 |
23 | /**
24 | * Registers all handler methods on {@code object} to receive events.
25 | * @param object The object whose handler methods should be registered.
26 | * @see EventBus#register(Object)
27 | */
28 | public void register(final Object object) {
29 | this.eventBus.register(object);
30 | }
31 |
32 | /**
33 | * Unregisters all handler methods on a registered object.
34 | * @param object The object whose handler methods should be unregistered.
35 | * @throws IllegalArgumentException if the object was not previously registered.
36 | * @see EventBus#unregister(Object)
37 | */
38 | public void unregister(final Object object) {
39 | this.eventBus.unregister(object);
40 | }
41 |
42 | /**
43 | * Posts an event to all registered handlers. This method will return successfully after the
44 | * event has been posted to all handlers, and regardless of any exceptions thrown by handlers.
45 | * @param event The event to post
46 | * @see EventBus#post(Object)
47 | */
48 | public void post(final Object event) {
49 | this.eventBus.post(event);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/threading/ExecutorProviderImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.threading;
18 |
19 | import com.google.inject.Inject;
20 |
21 | import java.util.concurrent.ExecutorService;
22 | import java.util.concurrent.ThreadPoolExecutor;
23 |
24 | import io.relution.jenkins.scmsqs.interfaces.ExecutorFactory;
25 | import io.relution.jenkins.scmsqs.interfaces.ExecutorProvider;
26 |
27 |
28 | public class ExecutorProviderImpl implements ExecutorProvider {
29 |
30 | private final ThreadPoolExecutor executor;
31 |
32 | @Inject
33 | public ExecutorProviderImpl(final ExecutorFactory factory) {
34 | this.executor = factory.createExecutor();
35 | }
36 |
37 | @Override
38 | public int getCorePoolSize() {
39 | return this.executor.getCorePoolSize();
40 | }
41 |
42 | @Override
43 | public void setCorePoolSize(final int corePoolSize) throws IllegalArgumentException {
44 | this.executor.setCorePoolSize(corePoolSize);
45 | }
46 |
47 | @Override
48 | public ExecutorService get() {
49 | return this.executor;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/global.jelly:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
32 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/ExecutorProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.google.inject.Provider;
20 |
21 | import java.util.concurrent.ExecutorService;
22 |
23 |
24 | /**
25 | * Interface definition for classes that provide access to an {@link ExecutorService} instance.
26 | */
27 | public interface ExecutorProvider extends Provider {
28 |
29 | /**
30 | * Returns the core number of threads.
31 | * @return The core number of threads.
32 | * @see #setCorePoolSize(int)
33 | */
34 | int getCorePoolSize();
35 |
36 | /**
37 | * Sets the core number of threads. This overrides any value set in the constructor. If the new
38 | * value is smaller than the current value, excess existing threads will be terminated when they
39 | * next become idle. If larger, new threads will, if needed, be started to execute any queued
40 | * tasks.
41 | * @param corePoolSize The new core size to set.
42 | * @throws IllegalArgumentException If {@code corePoolSize} is less than zero.
43 | * @see #getCorePoolSize()
44 | */
45 | void setCorePoolSize(int corePoolSize) throws IllegalArgumentException;
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/config.jelly:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
36 |
37 |
38 |
41 |
42 |
43 |
47 |
48 |
51 |
52 |
53 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/net/RequestFactoryImpl.java:
--------------------------------------------------------------------------------
1 |
2 | package io.relution.jenkins.scmsqs.net;
3 |
4 | import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
5 | import com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry;
6 | import com.amazonaws.services.sqs.model.Message;
7 | import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
13 |
14 |
15 | public class RequestFactoryImpl implements RequestFactory {
16 |
17 | @Override
18 | public ReceiveMessageRequest createReceiveMessageRequest(final SQSQueue queue) {
19 | final ReceiveMessageRequest request = new ReceiveMessageRequest(queue.getUrl());
20 | request.setMaxNumberOfMessages(queue.getMaxNumberOfMessages());
21 | request.setWaitTimeSeconds(queue.getWaitTimeSeconds());
22 | return request;
23 | }
24 |
25 | @Override
26 | public DeleteMessageBatchRequest createDeleteMessageBatchRequest(final SQSQueue queue, final List messages) {
27 | final List entries = new ArrayList<>(messages.size());
28 |
29 | for (final Message message : messages) {
30 | final DeleteMessageBatchRequestEntry entry = this.createDeleteMessageBatchRequestEntry(message);
31 | entries.add(entry);
32 | }
33 |
34 | final DeleteMessageBatchRequest request = new DeleteMessageBatchRequest(queue.getUrl());
35 | request.setEntries(entries);
36 | return request;
37 | }
38 |
39 | private DeleteMessageBatchRequestEntry createDeleteMessageBatchRequestEntry(final Message message) {
40 | final DeleteMessageBatchRequestEntry entry = new DeleteMessageBatchRequestEntry();
41 | entry.setReceiptHandle(message.getReceiptHandle());
42 | entry.setId(message.getMessageId());
43 | return entry;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # --------------------------------------------------------------------------------
3 | NAME=scm-sqs-plugin
4 | echo Preparing $NAME for release
5 |
6 | # Query user for info
7 | read -p "Enter version number to use for the release: " VERSION_RELEASE
8 | read -p "Enter name for the release tag [$NAME-$VERSION_RELEASE]: " TAG_RELEASE
9 | read -p "Enter version number for next development iteration: " VERSION_SNAPSHOT
10 |
11 | if [[ -z "$TAG_RELEASE" ]]; then
12 | TAG_RELEASE=$NAME-$VERSION_RELEASE
13 | fi
14 |
15 | if [[ "$VERSION_SNAPSHOT" != *-SNAPSHOT ]]; then
16 | VERSION_SNAPSHOT=$VERSION_SNAPSHOT-SNAPSHOT
17 | fi
18 |
19 | # Show info for review
20 | echo
21 | echo "Release version : $VERSION_RELEASE"
22 | echo "Tag release with : $TAG_RELEASE"
23 | echo "Next iteration : $VERSION_SNAPSHOT"
24 | read -p "Continue (y/N)? " -s -n1 KEY
25 | echo
26 |
27 | if [[ "$KEY" != "y" ]]; then
28 | echo "Aborted"
29 | exit
30 | fi
31 |
32 | # Set the new version, commit, create a tag
33 | mvn versions:set -DnewVersion=$VERSION_RELEASE
34 | if [[ $? -ne 0 ]]; then
35 | echo
36 | echo "Error setting release version. Please verify and fix any errors reported above."
37 | echo
38 | exit
39 | fi
40 |
41 | git add pom.xml
42 | git commit -m "prepare release $TAG_RELEASE"
43 | git tag $TAG_RELEASE
44 |
45 | # Deploy the new version
46 | mvn deploy
47 | if [[ $? -ne 0 ]]; then
48 | echo
49 | echo "Error deploying version. Please verify and fix any errors reported above."
50 | echo
51 | exit
52 | fi
53 |
54 | # Set the new snapshot version, commit
55 | mvn versions:set -DnewVersion=$VERSION_SNAPSHOT
56 | if [[ $? -ne 0 ]]; then
57 | echo
58 | echo "Error setting snapshot version. Please verify and fix any errors reported above."
59 | echo
60 | exit
61 | fi
62 |
63 | git add pom.xml
64 | git commit -m "prepare for next development iteration"
65 |
66 | # Push changes and tags
67 | # git push
68 | # git push --tags
69 |
70 | # Clean up backup files
71 | # rm pom.xml.versionsBackup
72 |
73 | echo
74 | echo "done."
75 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/util/ErrorType.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.util;
18 |
19 | import com.amazonaws.AmazonServiceException;
20 |
21 | import org.apache.commons.lang3.StringUtils;
22 |
23 |
24 | /**
25 | * Provides static methods that can be used to filter exceptions based on their properties.
26 | */
27 | public class ErrorType {
28 |
29 | /**
30 | * Returns a value indicating whether the specified service exception has the specified error
31 | * code and HTTP status.
32 | * @param e The {@link AmazonServiceException} to test.
33 | * @param errorCode The error code to match, can be {@code null}.
34 | * @param httpStatus The HTTP status to match. Use {@code -1} to match any HTTP status.
35 | * @return {@code true} if the specified exception has the specified error code and HTTP status;
36 | * otherwise, {@code false}.
37 | */
38 | public static boolean is(final AmazonServiceException e, final String errorCode, final int httpStatus) {
39 | if (e == null) {
40 | return false;
41 | }
42 |
43 | if (errorCode != null && !StringUtils.equals(errorCode, e.getErrorCode())) {
44 | return false;
45 | }
46 |
47 | if (httpStatus != -1 && e.getStatusCode() != httpStatus) {
48 | return false;
49 | }
50 |
51 | return true;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/Event.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import org.eclipse.jgit.transport.URIish;
20 |
21 |
22 | /**
23 | * Interface definition for classes that represent source code management (SCM) events posted to an
24 | * Amazon SQS queue.
25 | */
26 | public interface Event {
27 |
28 | /**
29 | * Returns the host of the repository that raised the event.
30 | * @return The name of the host.
31 | */
32 | String getHost();
33 |
34 | /**
35 | * Returns the path of the repository that raised the event.
36 | * @return The path of the repository on {@code host}.
37 | */
38 | String getPath();
39 |
40 | /**
41 | * Returns the user that caused the event to be raised.
42 | * @return The name of the user that caused the event to be raised.
43 | */
44 | String getUser();
45 |
46 | /**
47 | * Returns the branch affected by the changed the caused the event to be raised.
48 | * @return The name of the branch that caused the event to be raised.
49 | */
50 | String getBranch();
51 |
52 | /**
53 | * Returns a value indicating whether the specified URI matches the events host and path
54 | * information.
55 | * @param uri The {@link URIish} to be tested.
56 | * @return {@code true} if the event matches the specified URI; otherwise, {@code false}.
57 | */
58 | boolean isMatch(URIish uri);
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/factories/ExecutorFactoryImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.factories;
18 |
19 | import com.google.inject.Inject;
20 |
21 | import java.util.concurrent.LinkedBlockingQueue;
22 | import java.util.concurrent.ThreadFactory;
23 | import java.util.concurrent.ThreadPoolExecutor;
24 | import java.util.concurrent.TimeUnit;
25 |
26 | import io.relution.jenkins.scmsqs.interfaces.ExecutorFactory;
27 |
28 |
29 | public class ExecutorFactoryImpl implements ExecutorFactory {
30 |
31 | /**
32 | * The number of threads to start by default. Cannot exceed the maximum number of threads.
33 | * Beware: If you reduce this number and do not place a limit on the queue no additional
34 | * threads will ever be started.
35 | */
36 | private final static int CORE_POOL_SIZE = 10;
37 | private final static int MAXIMUM_POOL_SIZE = 50;
38 |
39 | private final static int KEEP_ALIVE_TIME = 5;
40 | private final static TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.MINUTES;
41 |
42 | final ThreadFactory threadFactory;
43 |
44 | @Inject
45 | public ExecutorFactoryImpl(final ThreadFactory threadFactory) {
46 | this.threadFactory = threadFactory;
47 | }
48 |
49 | @Override
50 | public ThreadPoolExecutor createExecutor() {
51 | final ThreadPoolExecutor executor = new ThreadPoolExecutor(
52 | CORE_POOL_SIZE,
53 | MAXIMUM_POOL_SIZE,
54 | KEEP_ALIVE_TIME,
55 | KEEP_ALIVE_TIME_UNIT,
56 | new LinkedBlockingQueue(),
57 | this.threadFactory);
58 |
59 | executor.allowCoreThreadTimeOut(false);
60 | return executor;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/entities/codecommit/CodeCommitEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model.entities.codecommit;
18 |
19 | import org.apache.commons.lang3.StringUtils;
20 | import org.eclipse.jgit.transport.URIish;
21 |
22 | import io.relution.jenkins.scmsqs.interfaces.Event;
23 |
24 |
25 | public class CodeCommitEvent implements Event {
26 |
27 | private final static String HOST = "git-codecommit.%s.amazonaws.com";
28 | private final static String PATH = "/v1/repos/%s";
29 |
30 | private final String host;
31 | private final String path;
32 |
33 | private final String branch;
34 |
35 | public CodeCommitEvent(final Record record, final Reference reference) {
36 | final String arn = record.getEventSourceARN();
37 | final String[] tokens = arn.split(":", 6);
38 |
39 | this.host = String.format(HOST, tokens[3]);
40 | this.path = String.format(PATH, tokens[5]);
41 |
42 | final String name = reference.getName();
43 | this.branch = StringUtils.stripStart(name, "refs/");
44 | }
45 |
46 | @Override
47 | public String getHost() {
48 | return this.host;
49 | }
50 |
51 | @Override
52 | public String getPath() {
53 | return this.path;
54 | }
55 |
56 | @Override
57 | public String getUser() {
58 | return null;
59 | }
60 |
61 | @Override
62 | public String getBranch() {
63 | return this.branch;
64 | }
65 |
66 | @Override
67 | public boolean isMatch(final URIish uri) {
68 | if (uri == null) {
69 | return false;
70 | }
71 |
72 | if (!StringUtils.equals(this.host, uri.getHost())) {
73 | return false;
74 | }
75 |
76 | if (!StringUtils.equals(this.path, uri.getPath())) {
77 | return false;
78 | }
79 |
80 | return true;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/SQSQueueMonitorScheduler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.google.common.eventbus.Subscribe;
20 |
21 | import io.relution.jenkins.scmsqs.model.events.ConfigurationChangedEvent;
22 |
23 |
24 | /**
25 | * Interface definition for classes that schedule the execution of {@link SQSQueueMonitor}
26 | * instances.
27 | */
28 | public interface SQSQueueMonitorScheduler {
29 |
30 | /**
31 | * Registers the specified listener with the scheduler. The listener is notified when new
32 | * messages arrive in the associated queue.
33 | *
34 | * The listener may be registered with a new monitor or an existing instance at the scheduler's
35 | * discretion.
36 | * @param listener The {@link SQSQueueListener} to be registered.
37 | * @return {@code true} if the listener was registered. {@code false} if no queue configuration
38 | * associated with the listener could be found.
39 | * @throws IllegalArgumentException The specified listener is {@code null}.
40 | */
41 | boolean register(SQSQueueListener listener);
42 |
43 | /**
44 | * Unregisters the specified listener from the scheduler. The listener will no longer
45 | * be notified of new messages.
46 | * @param listener The {@link SQSQueueListener} to be unregistered.
47 | * @return {@code true} if the listener was unregistered. {@code false} if the specified
48 | * listener is not associated with a monitor.
49 | */
50 | boolean unregister(SQSQueueListener listener);
51 |
52 | /**
53 | * Notifies the scheduler that the global configuration was changed. It should shut down all
54 | * monitors for which the associated queue configuration was removed.
55 | * @param event The configuration changed event.
56 | */
57 | @Subscribe
58 | void onConfigurationChanged(ConfigurationChangedEvent event);
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/SQSQueue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.amazonaws.auth.AWSCredentials;
20 |
21 |
22 | /**
23 | * Interface definition for classes that represent the necessary configuration that is required to
24 | * access an Amazon SQS queue.
25 | */
26 | public interface SQSQueue extends AWSCredentials {
27 |
28 | /**
29 | * Returns the identifier used to uniquely identify the queue configuration.
30 | * @return The unique identifier of this configuration.
31 | */
32 | String getUuid();
33 |
34 | /**
35 | * Returns the URL of the queue the configuration is associated with.
36 | * @return The URL of a queue.
37 | */
38 | String getUrl();
39 |
40 | /**
41 | * Returns the name of the queue the configuration is associated with.
42 | * @return The name of a queue.
43 | */
44 | String getName();
45 |
46 | /**
47 | * Returns the endpoint of the queue the configuration is associated with.
48 | * @return The endpoint of a queue.
49 | */
50 | String getEndpoint();
51 |
52 | /**
53 | * Returns the time, in seconds, requests should wait for new messages to arrive in the queue.
54 | * @return The wait time, in seconds, before a receive message request should time out.
55 | */
56 | int getWaitTimeSeconds();
57 |
58 | /**
59 | * Returns the maximum number of messages that a request should request.
60 | * @return The maximum number of messages a receive message request should request from the
61 | * queue.
62 | */
63 | int getMaxNumberOfMessages();
64 |
65 | /**
66 | * Returns a value indicating whether the configuration is valid.
67 | *
68 | * A configuration is considered valid if all information required to access the associated
69 | * queue has been defined.
70 | * @return {@code true} if the configuration is valid; otherwise, {@code false}.
71 | */
72 | boolean isValid();
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/logging/Log.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.logging;
18 |
19 | import java.util.logging.Level;
20 | import java.util.logging.Logger;
21 |
22 |
23 | public class Log {
24 |
25 | private static final Logger LOGGER;
26 |
27 | static {
28 | LOGGER = Logger.getLogger(Log.class.getName());
29 | }
30 |
31 | private static String format(final String message, final Object... args) {
32 | final String formatted = String.format(message, args);
33 | final long id = Thread.currentThread().getId();
34 | return String.format("%06X %s", id, formatted);
35 | }
36 |
37 | private static void write(final Level level, final String message, final Object... args) {
38 | final String msg = format(message, args);
39 | LOGGER.log(level, msg);
40 | }
41 |
42 | private static void write(final Level level, final Throwable thrown, final String message, final Object... args) {
43 | final String msg = format(message, args);
44 | LOGGER.log(level, msg, thrown);
45 | }
46 |
47 | public static void finest(final String message, final Object... args) {
48 | write(Level.FINEST, message, args);
49 | }
50 |
51 | public static void finer(final String message, final Object... args) {
52 | write(Level.FINER, message, args);
53 | }
54 |
55 | public static void fine(final String message, final Object... args) {
56 | write(Level.FINE, message, args);
57 | }
58 |
59 | public static void info(final String message, final Object... args) {
60 | write(Level.INFO, message, args);
61 | }
62 |
63 | public static void warning(final String message, final Object... args) {
64 | write(Level.WARNING, message, args);
65 | }
66 |
67 | public static void severe(final String message, final Object... args) {
68 | write(Level.SEVERE, message, args);
69 | }
70 |
71 | public static void severe(final Throwable thrown, final String message, final Object... args) {
72 | write(Level.SEVERE, thrown, message, args);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/io/relution/jenkins/scmsqs/SQSTriggerQueueDescriptorImplTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | import org.junit.Test;
22 |
23 | import hudson.util.FormValidation;
24 | import io.relution.jenkins.scmsqs.SQSTriggerQueue.DescriptorImpl;
25 |
26 |
27 | public class SQSTriggerQueueDescriptorImplTest {
28 |
29 | @Test
30 | public void shouldAcceptNameAsNameOrUrl() {
31 | final DescriptorImpl descriptor = new SQSTriggerQueue.DescriptorImpl();
32 | final FormValidation validation = descriptor.doCheckNameOrUrl("test-queue");
33 |
34 | assertThat(validation.kind).isEqualTo(FormValidation.Kind.OK);
35 | }
36 |
37 | @Test
38 | public void shouldAcceptSqsUrlAsNameOrUrl() {
39 | final DescriptorImpl descriptor = new SQSTriggerQueue.DescriptorImpl();
40 | final FormValidation validation = descriptor.doCheckNameOrUrl("https://sqs.us-east-1.amazonaws.com/929548749884/relution-queue-mytest7");
41 |
42 | assertThat(validation.kind).isEqualTo(FormValidation.Kind.OK);
43 | }
44 |
45 | @Test
46 | public void shouldNotAcceptEmptyUrlAsNameOrUrl() {
47 | final DescriptorImpl descriptor = new SQSTriggerQueue.DescriptorImpl();
48 | final FormValidation validation = descriptor.doCheckNameOrUrl(null);
49 |
50 | assertThat(validation.kind).isEqualTo(FormValidation.Kind.WARNING);
51 | }
52 |
53 | @Test
54 | public void shouldNotAcceptCodeCommitUrlAsNameOrUrl() {
55 | final DescriptorImpl descriptor = new SQSTriggerQueue.DescriptorImpl();
56 | final FormValidation validation = descriptor.doCheckNameOrUrl("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/relution-mytest7");
57 |
58 | assertThat(validation.kind).isEqualTo(FormValidation.Kind.ERROR);
59 | }
60 |
61 | @Test
62 | public void shouldNotAcceptAnyUrlAsNameOrUrl() {
63 | final DescriptorImpl descriptor = new SQSTriggerQueue.DescriptorImpl();
64 | final FormValidation validation = descriptor.doCheckNameOrUrl("http://www.google.de");
65 |
66 | assertThat(validation.kind).isEqualTo(FormValidation.Kind.ERROR);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/entities/codecommit/MessageBody.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model.entities.codecommit;
18 |
19 | import com.google.gson.annotations.Expose;
20 | import com.google.gson.annotations.SerializedName;
21 |
22 |
23 | public class MessageBody {
24 |
25 | @Expose
26 | @SerializedName("Type")
27 | private String type;
28 |
29 | @Expose
30 | @SerializedName("MessageId")
31 | private String messageId;
32 |
33 | @Expose
34 | @SerializedName("TopicArn")
35 | private String topicArn;
36 |
37 | @Expose
38 | @SerializedName("Subject")
39 | private String subject;
40 |
41 | @Expose
42 | @SerializedName("Message")
43 | private String message;
44 |
45 | @Expose
46 | @SerializedName("Timestamp")
47 | private String timestamp;
48 |
49 | @Expose
50 | @SerializedName("SignatureVersion")
51 | private String signatureVersion;
52 |
53 | @Expose
54 | @SerializedName("Signature")
55 | private String signature;
56 |
57 | @Expose
58 | @SerializedName("SigningCertURL")
59 | private String signingCertURL;
60 |
61 | @Expose
62 | @SerializedName("UnsubscribeURL")
63 | private String unsubscribeURL;
64 |
65 | public String getType() {
66 | return this.type;
67 | }
68 |
69 | public String getMessageId() {
70 | return this.messageId;
71 | }
72 |
73 | public String getTopicArn() {
74 | return this.topicArn;
75 | }
76 |
77 | public String getSubject() {
78 | return this.subject;
79 | }
80 |
81 | public String getMessage() {
82 | return this.message;
83 | }
84 |
85 | public String getTimestamp() {
86 | return this.timestamp;
87 | }
88 |
89 | public String getSignatureVersion() {
90 | return this.signatureVersion;
91 | }
92 |
93 | public String getSignature() {
94 | return this.signature;
95 | }
96 |
97 | public String getSigningCertURL() {
98 | return this.signingCertURL;
99 | }
100 |
101 | public String getUnsubscribeURL() {
102 | return this.unsubscribeURL;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/util/ThrowIf.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.util;
18 |
19 | import java.util.Objects;
20 |
21 |
22 | /**
23 | * This class consists of {@code static} utility methods usable for argument validation.
24 | */
25 | public class ThrowIf {
26 |
27 | private static final String IS_NULL = "The specified argument is null: %s";
28 |
29 | private static final String NOT_EQUAL = "The specified argument does not match the expected value of \"%s\", actual value: \"%s\"";
30 |
31 | /**
32 | * Throws an {@link IllegalArgumentException} if {@code o} is {@code null}.
33 | * @param o The object to test for {@code null}.
34 | * @param name The name of the argument.
35 | * @throws IllegalArgumentException If {@code o} is {@code null}.
36 | */
37 | public static void isNull(final Object o, final String name) {
38 | if (o == null) {
39 | final String msg = String.format(IS_NULL, name);
40 | throw new IllegalArgumentException(msg);
41 | }
42 | }
43 |
44 | /**
45 | * Throws an {@link IllegalArgumentException} if {@code argument} is not equal to {@code value}.
46 | * @param argument The argument to test for equality with {@code value}.
47 | * @param value The value {@code argument} needs to be equal to.
48 | * @throws IllegalArgumentException If {@code argument} is not equal to {@code value}.
49 | * @see #notEqual(Object, Object, String, Object...)
50 | */
51 | public static void notEqual(final Object argument, final Object value) {
52 | notEqual(argument, value, NOT_EQUAL, value, argument);
53 | }
54 |
55 | /**
56 | * Throws an {@link IllegalArgumentException} if {@code argument} is not equal to {@code value}.
57 | * @param argument The argument to test for equality with {@code value}.
58 | * @param value The value {@code argument} needs to be equal to.
59 | * @param format The detail message for the exception.
60 | * @param args Optional arguments supplied to the detail message.
61 | * @throws IllegalArgumentException If {@code argument} is not equal to {@code value}.
62 | * @see #notEqual(Object, Object)
63 | */
64 | public static void notEqual(final Object argument, final Object value, final String format, final Object... args) {
65 | if (!Objects.equals(argument, value)) {
66 | final String msg = String.format(format, args);
67 | throw new IllegalArgumentException(msg);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/SQSTriggerBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.io.PrintStream;
22 | import java.text.DateFormat;
23 | import java.util.Date;
24 |
25 | import hudson.Util;
26 | import hudson.model.AbstractProject;
27 | import hudson.model.Cause;
28 | import hudson.util.StreamTaskListener;
29 | import io.relution.jenkins.scmsqs.logging.Log;
30 |
31 |
32 | public class SQSTriggerBuilder implements Runnable {
33 |
34 | private final SQSTrigger trigger;
35 | private final AbstractProject, ?> job;
36 |
37 | private final DateFormat formatter = DateFormat.getDateTimeInstance();
38 |
39 | public SQSTriggerBuilder(final SQSTrigger trigger, final AbstractProject, ?> job) {
40 | this.trigger = trigger;
41 | this.job = job;
42 | }
43 |
44 | @Override
45 | public void run() {
46 | final File log = this.trigger.getLogFile();
47 |
48 | try (final StreamTaskListener listener = new StreamTaskListener(log)) {
49 | this.buildIfChanged(listener);
50 |
51 | } catch (final IOException e) {
52 | Log.severe(e, "Failed to record SCM polling");
53 |
54 | }
55 | }
56 |
57 | private void buildIfChanged(final StreamTaskListener listener) {
58 | final PrintStream logger = listener.getLogger();
59 | final long now = System.currentTimeMillis();
60 |
61 | logger.format("Started on %s", this.toDateTime(now));
62 | final boolean hasChanges = this.job.poll(listener).hasChanges();
63 | logger.println("Done. Took " + this.toTimeSpan(now));
64 |
65 | if (!hasChanges) {
66 | logger.println("No changes");
67 | } else {
68 | logger.println("Changes found");
69 | this.build(logger, now);
70 | }
71 | }
72 |
73 | private void build(final PrintStream logger, final long now) {
74 | final String note = "SQS poll initiated on " + this.toDateTime(now);
75 | final Cause cause = new Cause.RemoteCause("SQS trigger", note);
76 |
77 | if (this.job.scheduleBuild(cause)) {
78 | logger.println("Job queued");
79 | } else {
80 | logger.println("Job NOT queued - it was determined that this job has been queued already.");
81 | }
82 | }
83 |
84 | private String toDateTime(final long timestamp) {
85 | final Date date = new Date(timestamp);
86 | return this.formatter.format(date);
87 | }
88 |
89 | private String toTimeSpan(final long timestamp) {
90 | final long now = System.currentTimeMillis();
91 | return Util.getTimeSpanString(now - timestamp);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/SQSFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.amazonaws.services.sqs.AmazonSQS;
20 | import com.amazonaws.services.sqs.AmazonSQSAsync;
21 |
22 | import java.util.concurrent.ExecutorService;
23 |
24 | import io.relution.jenkins.scmsqs.net.SQSChannel;
25 |
26 |
27 | /**
28 | * Interface definition for factories that can create {@link SQSQueueMonitor} instances and related
29 | * classes. The instances returned by a factory implementation can be used to monitor a particular
30 | * {@link SQSQueue} by polling the queue for new messages.
31 | */
32 | public interface SQSFactory {
33 |
34 | /**
35 | * Returns a new Amazon SQS instance that can be used to access the specified queue.
36 | * @param queue The {@link SQSQueue} for which to create a client.
37 | * @return A new instance of an {@link AmazonSQS} that is suitable for synchronous access to
38 | * the specified queue.
39 | */
40 | AmazonSQS createSQS(final SQSQueue queue);
41 |
42 | /**
43 | * Returns a new Amazon SQS instance that can be used to access the specified queue.
44 | * @param queue The {@link SQSQueue} for which to create a client.
45 | * @return A new instance of an {@link AmazonSQSAsync} that is suitable for asynchronous access
46 | * to the specified queue.
47 | */
48 | AmazonSQSAsync createSQSAsync(final SQSQueue queue);
49 |
50 | /**
51 | * Returns a new channel instance that can be used to communicate with the specified queue.
52 | * @param queue The {@link SQSQueue} for which to create the channel.
53 | * @return A new {@link SQSChannel} for the specified queue.
54 | */
55 | SQSChannel createChannel(final SQSQueue queue);
56 |
57 | /**
58 | * Returns a new monitor instance that can be used to poll the specified queue for new
59 | * messages.
60 | * @param executor The {@link ExecutorService} used to execute the monitor.
61 | * @param queue The {@link SQSQueue} for which to create a monitor.
62 | * @return A new {@link SQSQueueMonitor} instance suitable for monitoring the specified queue.
63 | */
64 | SQSQueueMonitor createMonitor(final ExecutorService executor, final SQSQueue queue);
65 |
66 | /**
67 | * Returns a new monitor instance that can be used to poll the specified queue for new
68 | * messages.
69 | *
70 | * The new monitor has the same listeners as the specified monitor. This should be used to
71 | * create a new monitor instance in case a queue was reconfigured.
72 | * @param monitor The monitor used to initialize internal fields of the new instance.
73 | * @param queue The {@link SQSQueue} for which to create a monitor.
74 | * @return A new {@link SQSQueueMonitor} instance suitable for monitoring the specified queue,
75 | * that has the same listeners as the specified monitor.
76 | */
77 | SQSQueueMonitor createMonitor(final SQSQueueMonitor monitor, final SQSQueue queue);
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/entities/codecommit/Record.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model.entities.codecommit;
18 |
19 | import com.google.gson.annotations.Expose;
20 | import com.google.gson.annotations.SerializedName;
21 |
22 |
23 | public class Record {
24 |
25 | @Expose
26 | @SerializedName("awsRegion")
27 | private String awsRegion;
28 |
29 | @Expose
30 | @SerializedName("codecommit")
31 | private CodeCommit codeCommit;
32 |
33 | @Expose
34 | @SerializedName("eventId")
35 | private String eventId;
36 |
37 | @Expose
38 | @SerializedName("eventName")
39 | private String eventName;
40 |
41 | @Expose
42 | @SerializedName("eventPartNumber")
43 | private int eventPartNumber;
44 |
45 | @Expose
46 | @SerializedName("eventSource")
47 | private String eventSource;
48 |
49 | @Expose
50 | @SerializedName("eventSourceARN")
51 | private String eventSourceARN;
52 |
53 | @Expose
54 | @SerializedName("eventTime")
55 | private String eventTime;
56 |
57 | @Expose
58 | @SerializedName("eventTotalParts")
59 | private int eventTotalParts;
60 |
61 | @Expose
62 | @SerializedName("eventTriggerConfigId")
63 | private String eventTriggerConfigId;
64 |
65 | @Expose
66 | @SerializedName("eventTriggerName")
67 | private String eventTriggerName;
68 |
69 | @Expose
70 | @SerializedName("eventVersion")
71 | private String eventVersion;
72 |
73 | @Expose
74 | @SerializedName("userIdentityARN")
75 | private String userIdentityARN;
76 |
77 | public String getAwsRegion() {
78 | return this.awsRegion;
79 | }
80 |
81 | public CodeCommit getCodeCommit() {
82 | return this.codeCommit;
83 | }
84 |
85 | public String getEventId() {
86 | return this.eventId;
87 | }
88 |
89 | public String getEventName() {
90 | return this.eventName;
91 | }
92 |
93 | public int getEventPartNumber() {
94 | return this.eventPartNumber;
95 | }
96 |
97 | public String getEventSource() {
98 | return this.eventSource;
99 | }
100 |
101 | public String getEventSourceARN() {
102 | return this.eventSourceARN;
103 | }
104 |
105 | public String getEventTime() {
106 | return this.eventTime;
107 | }
108 |
109 | public int getEventTotalParts() {
110 | return this.eventTotalParts;
111 | }
112 |
113 | public String getEventTriggerConfigId() {
114 | return this.eventTriggerConfigId;
115 | }
116 |
117 | public String getEventTriggerName() {
118 | return this.eventTriggerName;
119 | }
120 |
121 | public String getEventVersion() {
122 | return this.eventVersion;
123 | }
124 |
125 | public String getUserIdentityARN() {
126 | return this.userIdentityARN;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/interfaces/SQSQueueMonitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.interfaces;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 |
21 | import io.relution.jenkins.scmsqs.net.SQSChannel;
22 |
23 |
24 | /**
25 | * Interface definition for classes that can be used to monitor an Amazon {@link SQSQueue} for new
26 | * {@link Message}s that arrive in the queue.
27 | */
28 | public interface SQSQueueMonitor extends Runnable {
29 |
30 | /**
31 | * Returns a new instance for the specified queue and channel that has the same listeners as
32 | * this instance.
33 | * @param queue The {@link SQSQueue} to monitor.
34 | * @param channel The {@link SQSChannel} used to access the queue.
35 | * @return A new {@link SQSQueueMonitor} instance.
36 | */
37 | SQSQueueMonitor clone(SQSQueue queue, SQSChannel channel);
38 |
39 | /**
40 | * Registers a new listener with the monitor. The listener is notified when new messages arrive
41 | * in the queue.
42 | *
43 | * Note: If a new listener is registered with a monitor that is already processing
44 | * received messages the listener may not be notified until the next time new messages arrive
45 | * in the queue.
46 | * @param listener The {@link SQSQueueListener} to register with the monitor.
47 | * @return {@code true} if the call caused monitoring to be started (i.e. the first listener was
48 | * registered); otherwise, {@code false}.
49 | * @throws IllegalArgumentException The specified listener is {@code null}
50 | * @throws IllegalArgumentException The specified listener is associated with a different
51 | * queue.
52 | */
53 | boolean add(SQSQueueListener listener);
54 |
55 | /**
56 | * Unregisters a previously registered listener from the monitor. The listener will no longer
57 | * be notified when new messages arrive in the queue.
58 | * @param listener The {@link SQSQueueListener} to unregister from the monitor.
59 | * @return {@code true} if the call caused monitoring to be stopped (i.e. the last listener was
60 | * unregistered); otherwise, {@code false}.
61 | */
62 | boolean remove(SQSQueueListener listener);
63 |
64 | /**
65 | * Stops the monitor.
66 | *
67 | * Prevents the monitor from making any further requests to the associated queue. Requests that
68 | * are already in flight may not necessarily be cancelled.
69 | */
70 | void shutDown();
71 |
72 | /**
73 | * Returns a value indicating whether the monitor is stopped.
74 | * @return {@code true} if the monitor is stopped; otherwise, {@code false}.
75 | */
76 | boolean isShutDown();
77 |
78 | /**
79 | * Returns the SQS queue this monitor is associated with.
80 | * @return The {@link SQSQueue} this monitor is associated with.
81 | */
82 | SQSQueue getQueue();
83 |
84 | /**
85 | * Returns the SQS channel this monitor is associated with.
86 | * @return The {@link SQSChannel} this monitor is associated with.
87 | */
88 | SQSChannel getChannel();
89 | }
--------------------------------------------------------------------------------
/src/test/java/io/relution/jenkins/scmsqs/net/SQSQueueImplTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.net;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | import com.amazonaws.services.sqs.AmazonSQS;
22 | import com.amazonaws.services.sqs.model.BatchResultErrorEntry;
23 | import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
24 | import com.amazonaws.services.sqs.model.DeleteMessageBatchResult;
25 | import com.amazonaws.services.sqs.model.DeleteMessageBatchResultEntry;
26 | import com.amazonaws.services.sqs.model.Message;
27 | import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
28 | import com.amazonaws.services.sqs.model.ReceiveMessageResult;
29 |
30 | import org.junit.Before;
31 | import org.junit.Test;
32 | import org.mockito.Mock;
33 | import org.mockito.Mockito;
34 | import org.mockito.MockitoAnnotations;
35 |
36 | import java.util.ArrayList;
37 | import java.util.Collections;
38 | import java.util.List;
39 |
40 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
41 |
42 |
43 | public class SQSQueueImplTest {
44 |
45 | @Mock
46 | private RequestFactory factory;
47 |
48 | @Mock
49 | private AmazonSQS sqs;
50 |
51 | @Mock
52 | private SQSQueue queue;
53 |
54 | private SQSChannel channel;
55 |
56 | private final List messages = Collections.singletonList(new Message());
57 |
58 | @Before
59 | public void init() {
60 | MockitoAnnotations.initMocks(this);
61 |
62 | this.channel = new SQSChannelImpl(this.sqs, this.queue, this.factory);
63 | }
64 |
65 | @Test
66 | public void shouldReturnMessages() {
67 | final ReceiveMessageRequest request = Mockito.mock(ReceiveMessageRequest.class);
68 | final ReceiveMessageResult result = Mockito.mock(ReceiveMessageResult.class);
69 |
70 | Mockito.when(this.factory.createReceiveMessageRequest(this.queue)).thenReturn(request);
71 | Mockito.when(this.sqs.receiveMessage(request)).thenReturn(result);
72 | Mockito.when(result.getMessages()).thenReturn(this.messages);
73 |
74 | final List messages = this.channel.getMessages();
75 |
76 | assertThat(messages).isSameAs(this.messages);
77 | }
78 |
79 | @Test
80 | public void shouldDeleteMessages() {
81 | final DeleteMessageBatchRequest request = Mockito.mock(DeleteMessageBatchRequest.class);
82 | final DeleteMessageBatchResult result = Mockito.mock(DeleteMessageBatchResult.class);
83 |
84 | Mockito.when(this.factory.createDeleteMessageBatchRequest(this.queue, this.messages)).thenReturn(request);
85 | Mockito.when(this.sqs.deleteMessageBatch(request)).thenReturn(result);
86 | Mockito.when(result.getSuccessful()).thenReturn(new ArrayList());
87 | Mockito.when(result.getFailed()).thenReturn(new ArrayList());
88 |
89 | this.channel.deleteMessages(this.messages);
90 |
91 | Mockito.verify(this.factory).createDeleteMessageBatchRequest(this.queue, this.messages);
92 | Mockito.verify(this.sqs).deleteMessageBatch(request);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/Context.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs;
18 |
19 | import com.google.inject.Guice;
20 | import com.google.inject.Injector;
21 |
22 | import java.util.concurrent.ExecutorService;
23 | import java.util.concurrent.ThreadFactory;
24 |
25 | import io.relution.jenkins.scmsqs.factories.ExecutorFactoryImpl;
26 | import io.relution.jenkins.scmsqs.factories.MessageParserFactoryImpl;
27 | import io.relution.jenkins.scmsqs.factories.SQSFactoryImpl;
28 | import io.relution.jenkins.scmsqs.factories.ThreadFactoryImpl;
29 | import io.relution.jenkins.scmsqs.interfaces.EventTriggerMatcher;
30 | import io.relution.jenkins.scmsqs.interfaces.ExecutorFactory;
31 | import io.relution.jenkins.scmsqs.interfaces.ExecutorProvider;
32 | import io.relution.jenkins.scmsqs.interfaces.MessageParserFactory;
33 | import io.relution.jenkins.scmsqs.interfaces.SQSFactory;
34 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitorScheduler;
35 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueProvider;
36 | import io.relution.jenkins.scmsqs.model.EventTriggerMatcherImpl;
37 | import io.relution.jenkins.scmsqs.model.SQSQueueProviderImpl;
38 | import io.relution.jenkins.scmsqs.net.RequestFactory;
39 | import io.relution.jenkins.scmsqs.net.RequestFactoryImpl;
40 | import io.relution.jenkins.scmsqs.threading.ExecutorProviderImpl;
41 | import io.relution.jenkins.scmsqs.threading.SQSQueueMonitorSchedulerImpl;
42 |
43 |
44 | public class Context extends com.google.inject.AbstractModule {
45 |
46 | private static Injector injector;
47 |
48 | public synchronized static Injector injector() {
49 | if (injector == null) {
50 | injector = Guice.createInjector(new Context());
51 | }
52 | return injector;
53 | }
54 |
55 | @Override
56 | protected void configure() {
57 | this.bind(ThreadFactory.class)
58 | .to(ThreadFactoryImpl.class)
59 | .in(com.google.inject.Singleton.class);
60 |
61 | this.bind(ExecutorFactory.class)
62 | .to(ExecutorFactoryImpl.class)
63 | .in(com.google.inject.Singleton.class);
64 |
65 | this.bind(ExecutorProvider.class)
66 | .to(ExecutorProviderImpl.class)
67 | .in(com.google.inject.Singleton.class);
68 |
69 | this.bind(ExecutorService.class)
70 | .toProvider(ExecutorProvider.class)
71 | .in(com.google.inject.Singleton.class);
72 |
73 | this.bind(SQSFactory.class)
74 | .to(SQSFactoryImpl.class)
75 | .in(com.google.inject.Singleton.class);
76 |
77 | this.bind(RequestFactory.class)
78 | .to(RequestFactoryImpl.class)
79 | .in(com.google.inject.Singleton.class);
80 |
81 | this.bind(SQSQueueProvider.class)
82 | .to(SQSQueueProviderImpl.class)
83 | .in(com.google.inject.Singleton.class);
84 |
85 | this.bind(SQSQueueMonitorScheduler.class)
86 | .to(SQSQueueMonitorSchedulerImpl.class)
87 | .in(com.google.inject.Singleton.class);
88 |
89 | this.bind(MessageParserFactory.class)
90 | .to(MessageParserFactoryImpl.class)
91 | .in(com.google.inject.Singleton.class);
92 |
93 | this.bind(EventTriggerMatcher.class)
94 | .to(EventTriggerMatcherImpl.class)
95 | .in(com.google.inject.Singleton.class);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/CodeCommitMessageParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 | import com.google.gson.Gson;
21 | import com.google.gson.GsonBuilder;
22 |
23 | import org.apache.commons.lang3.StringUtils;
24 |
25 | import java.util.ArrayList;
26 | import java.util.Collections;
27 | import java.util.List;
28 |
29 | import io.relution.jenkins.scmsqs.interfaces.Event;
30 | import io.relution.jenkins.scmsqs.interfaces.MessageParser;
31 | import io.relution.jenkins.scmsqs.logging.Log;
32 | import io.relution.jenkins.scmsqs.model.entities.codecommit.CodeCommit;
33 | import io.relution.jenkins.scmsqs.model.entities.codecommit.CodeCommitEvent;
34 | import io.relution.jenkins.scmsqs.model.entities.codecommit.MessageBody;
35 | import io.relution.jenkins.scmsqs.model.entities.codecommit.Record;
36 | import io.relution.jenkins.scmsqs.model.entities.codecommit.Records;
37 | import io.relution.jenkins.scmsqs.model.entities.codecommit.Reference;
38 |
39 |
40 | public class CodeCommitMessageParser implements MessageParser {
41 |
42 | private static final String EVENT_SOURCE_CODECOMMIT = "aws:codecommit";
43 |
44 | private final Gson gson;
45 |
46 | public CodeCommitMessageParser() {
47 | this.gson = new GsonBuilder()
48 | .excludeFieldsWithoutExposeAnnotation()
49 | .create();
50 | }
51 |
52 | @Override
53 | public List parseMessage(final Message message) {
54 | try {
55 | final MessageBody body = this.gson.fromJson(message.getBody(), MessageBody.class);
56 | Log.info("Got message with subject: %s", body.getSubject());
57 | final String json = body.getMessage();
58 |
59 | if (StringUtils.isEmpty(json)) {
60 | Log.warning("Message contains no text");
61 | return Collections.emptyList();
62 | }
63 |
64 | if (!json.startsWith("{") || !json.endsWith("}")) {
65 | Log.warning("Message text is no JSON");
66 | return Collections.emptyList();
67 | }
68 |
69 | return this.parseRecords(json);
70 | } catch (final com.google.gson.JsonSyntaxException e) {
71 | Log.warning("JSON syntax exception, cannot parse message: %s", e);
72 | }
73 | return Collections.emptyList();
74 | }
75 |
76 | private List parseRecords(final String json) {
77 | final Records records = this.gson.fromJson(json, Records.class);
78 | final List events = new ArrayList<>(records.size());
79 |
80 | for (final Record record : records) {
81 | this.parseEvents(events, record);
82 | }
83 |
84 | return events;
85 | }
86 |
87 | private void parseEvents(final List events, final Record record) {
88 | if (!this.isCodeCommitEvent(record)) {
89 | return;
90 | }
91 |
92 | final CodeCommit codeCommit = record.getCodeCommit();
93 |
94 | for (final Reference reference : codeCommit.getReferences()) {
95 | final Event event = new CodeCommitEvent(record, reference);
96 | events.add(event);
97 | }
98 | }
99 |
100 | private boolean isCodeCommitEvent(final Record record) {
101 | return StringUtils.equals(EVENT_SOURCE_CODECOMMIT, record.getEventSource());
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/factories/SQSFactoryImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.factories;
18 |
19 | import com.amazonaws.ClientConfiguration;
20 | import com.amazonaws.services.sqs.AmazonSQS;
21 | import com.amazonaws.services.sqs.AmazonSQSAsync;
22 | import com.amazonaws.services.sqs.AmazonSQSAsyncClient;
23 | import com.amazonaws.services.sqs.AmazonSQSClient;
24 | import com.amazonaws.services.sqs.buffered.AmazonSQSBufferedAsyncClient;
25 | import com.amazonaws.services.sqs.buffered.QueueBufferConfig;
26 | import com.google.inject.Inject;
27 |
28 | import java.util.concurrent.ExecutorService;
29 |
30 | import io.relution.jenkins.scmsqs.interfaces.SQSFactory;
31 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
32 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitor;
33 | import io.relution.jenkins.scmsqs.net.RequestFactory;
34 | import io.relution.jenkins.scmsqs.net.SQSChannel;
35 | import io.relution.jenkins.scmsqs.net.SQSChannelImpl;
36 | import io.relution.jenkins.scmsqs.threading.SQSQueueMonitorImpl;
37 |
38 |
39 | public class SQSFactoryImpl implements SQSFactory {
40 |
41 | private final ExecutorService executor;
42 | private final RequestFactory factory;
43 |
44 | @Inject
45 | public SQSFactoryImpl(final ExecutorService executor, final RequestFactory factory) {
46 | this.executor = executor;
47 | this.factory = factory;
48 | }
49 |
50 | @Override
51 | public AmazonSQS createSQS(final SQSQueue queue) {
52 | final ClientConfiguration clientConfiguration = this.getClientConfiguration(queue);
53 | final AmazonSQS sqs = new AmazonSQSClient(queue, clientConfiguration);
54 |
55 | if (queue.getEndpoint() != null) {
56 | sqs.setEndpoint(queue.getEndpoint());
57 | }
58 |
59 | return sqs;
60 | }
61 |
62 | @Override
63 | public AmazonSQSAsync createSQSAsync(final SQSQueue queue) {
64 | final ClientConfiguration clientConfiguration = this.getClientConfiguration(queue);
65 | final AmazonSQSAsyncClient sqsAsync = new AmazonSQSAsyncClient(queue, clientConfiguration, this.executor);
66 |
67 | if (queue.getEndpoint() != null) {
68 | sqsAsync.setEndpoint(queue.getEndpoint());
69 | }
70 |
71 | final QueueBufferConfig queueBufferConfig = this.getQueueBufferConfig(queue);
72 | final AmazonSQSBufferedAsyncClient sqsBufferedAsync = new AmazonSQSBufferedAsyncClient(sqsAsync, queueBufferConfig);
73 |
74 | return sqsBufferedAsync;
75 | }
76 |
77 | @Override
78 | public SQSChannel createChannel(final SQSQueue queue) {
79 | final AmazonSQS sqs = this.createSQS(queue);
80 | return new SQSChannelImpl(sqs, queue, this.factory);
81 | }
82 |
83 | @Override
84 | public SQSQueueMonitor createMonitor(final ExecutorService executor, final SQSQueue queue) {
85 | final SQSChannel channel = this.createChannel(queue);
86 | return new SQSQueueMonitorImpl(executor, queue, channel);
87 | }
88 |
89 | @Override
90 | public SQSQueueMonitor createMonitor(final SQSQueueMonitor monitor, final SQSQueue queue) {
91 | final SQSChannel channel = this.createChannel(queue);
92 | return monitor.clone(queue, channel);
93 | }
94 |
95 | private ClientConfiguration getClientConfiguration(final SQSQueue queue) {
96 | final ClientConfiguration config = new ClientConfiguration();
97 |
98 | // TODO Add support for proxy
99 |
100 | return config;
101 | }
102 |
103 | private QueueBufferConfig getQueueBufferConfig(final SQSQueue queue) {
104 | final QueueBufferConfig config = new QueueBufferConfig();
105 |
106 | // TODO Add more options
107 |
108 | config.setLongPollWaitTimeoutSeconds(queue.getWaitTimeSeconds());
109 | config.setLongPoll(true);
110 |
111 | return config;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/net/SQSChannelImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.net;
18 |
19 | import com.amazonaws.services.sqs.AmazonSQS;
20 | import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
21 | import com.amazonaws.services.sqs.model.DeleteMessageBatchResult;
22 | import com.amazonaws.services.sqs.model.Message;
23 | import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
24 | import com.amazonaws.services.sqs.model.ReceiveMessageResult;
25 |
26 | import org.apache.commons.httpclient.HttpStatus;
27 |
28 | import java.util.Collections;
29 | import java.util.List;
30 |
31 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
32 | import io.relution.jenkins.scmsqs.logging.Log;
33 | import io.relution.jenkins.scmsqs.model.constants.ErrorCode;
34 | import io.relution.jenkins.scmsqs.util.ErrorType;
35 | import io.relution.jenkins.scmsqs.util.ThrowIf;
36 |
37 |
38 | public class SQSChannelImpl implements SQSChannel {
39 |
40 | private final AmazonSQS sqs;
41 | private final SQSQueue queue;
42 | private final RequestFactory factory;
43 |
44 | /**
45 | * Number of requests that were sent (for logging)
46 | */
47 | private int requestCount;
48 |
49 | public SQSChannelImpl(final AmazonSQS sqs, final SQSQueue queue, final RequestFactory factory) {
50 | ThrowIf.isNull(sqs, "sqs");
51 | ThrowIf.isNull(queue, "queue");
52 | ThrowIf.isNull(factory, "factory");
53 |
54 | this.sqs = sqs;
55 | this.queue = queue;
56 | this.factory = factory;
57 | }
58 |
59 | @Override
60 | public List getMessages() {
61 | try {
62 | this.logRequestCount();
63 |
64 | final ReceiveMessageRequest request = this.factory.createReceiveMessageRequest(this.queue);
65 | final ReceiveMessageResult result = this.sqs.receiveMessage(request);
66 |
67 | if (result == null) {
68 | return Collections.emptyList();
69 | }
70 |
71 | return result.getMessages();
72 |
73 | } catch (final com.amazonaws.services.sqs.model.QueueDoesNotExistException e) {
74 | Log.warning("Failed to send receive message request for %s, queue does not exist", this.queue);
75 | throw e;
76 |
77 | } catch (final com.amazonaws.AmazonServiceException e) {
78 | if (ErrorType.is(e, ErrorCode.INVALID_CLIENT_TOKEN_ID, HttpStatus.SC_FORBIDDEN)) {
79 | Log.warning("Failed to send receive message request for %s, %s", this.queue, e.getMessage());
80 | throw e;
81 | }
82 |
83 | Log.severe(e, "Failed to send receive message request for %s", this.queue);
84 | }
85 | return Collections.emptyList();
86 | }
87 |
88 | @Override
89 | public void deleteMessages(final List messages) {
90 | if (messages == null || messages.size() == 0) {
91 | return;
92 | }
93 |
94 | final DeleteMessageBatchResult result = this.deleteMessageBatch(messages);
95 |
96 | if (result == null) {
97 | return;
98 | }
99 |
100 | final List> failed = result.getFailed();
101 | final List> success = result.getSuccessful();
102 | Log.info("Deleted %d message(s) (%d failed) from %s", success.size(), failed.size(), this.queue);
103 | }
104 |
105 | @Override
106 | public String getQueueUuid() {
107 | return this.queue.getUuid();
108 | }
109 |
110 | @Override
111 | public String toString() {
112 | return this.queue.toString();
113 | }
114 |
115 | private void logRequestCount() {
116 | this.requestCount++;
117 | Log.fine("Send receive message request #%d for %s", this.requestCount, this.queue);
118 | }
119 |
120 | private DeleteMessageBatchResult deleteMessageBatch(final List messages) {
121 | try {
122 | final DeleteMessageBatchRequest request = this.factory.createDeleteMessageBatchRequest(this.queue, messages);
123 | Log.info("Send delete request for %d message(s) to %s", messages.size(), this.queue);
124 | return this.sqs.deleteMessageBatch(request);
125 |
126 | } catch (final com.amazonaws.AmazonServiceException e) {
127 | Log.severe(e, "Delete from %s failed", this.queue);
128 |
129 | }
130 | return null;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/test/java/io/relution/jenkins/scmsqs/SQSTriggerQueueTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | import com.amazonaws.services.sqs.AmazonSQSAsync;
22 | import com.amazonaws.services.sqs.model.GetQueueUrlResult;
23 |
24 | import org.junit.Before;
25 | import org.junit.Test;
26 | import org.mockito.Matchers;
27 | import org.mockito.Mock;
28 | import org.mockito.Mockito;
29 | import org.mockito.MockitoAnnotations;
30 |
31 | import io.relution.jenkins.scmsqs.interfaces.SQSFactory;
32 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
33 |
34 |
35 | public class SQSTriggerQueueTest {
36 |
37 | @Mock
38 | private SQSFactory sqsFactory;
39 |
40 | @Mock
41 | private AmazonSQSAsync sqs;
42 |
43 | @Mock
44 | private GetQueueUrlResult getQueueUrlResult;
45 |
46 | @Before
47 | public void init() {
48 | MockitoAnnotations.initMocks(this);
49 |
50 | Mockito.when(this.sqsFactory.createSQS(Matchers.any(SQSQueue.class))).thenReturn(this.sqs);
51 | Mockito.when(this.sqsFactory.createSQSAsync(Matchers.any(SQSQueue.class))).thenReturn(this.sqs);
52 |
53 | Mockito.when(this.sqs.getQueueUrl(Matchers.anyString())).thenReturn(this.getQueueUrlResult);
54 |
55 | Mockito.when(this.getQueueUrlResult.getQueueUrl()).thenReturn("mock://sqs.url");
56 | }
57 |
58 | @Test
59 | public void shouldSetDefaults() {
60 | // Cannot mock or create an instance of final hudson.util.Secret, so null it is
61 | final SQSTriggerQueue queue = new SQSTriggerQueue(null, "name", "accessKey", null, 0, 0);
62 | queue.setFactory(this.sqsFactory);
63 |
64 | assertThat(queue.getUuid()).isNotEmpty();
65 |
66 | assertThat(queue.getUrl()).isEqualTo("mock://sqs.url");
67 | assertThat(queue.getName()).isEqualTo("name");
68 | assertThat(queue.getEndpoint()).isNull();
69 | assertThat(queue.getNameOrUrl()).isEqualTo("name");
70 |
71 | assertThat(queue.getAccessKey()).isEqualTo("accessKey");
72 | assertThat(queue.getAWSAccessKeyId()).isEqualTo("accessKey");
73 |
74 | assertThat(queue.getSecretKey()).isNull();
75 | assertThat(queue.getAWSSecretKey()).isNull();
76 |
77 | assertThat(queue.getWaitTimeSeconds()).isEqualTo(20);
78 | assertThat(queue.getMaxNumberOfMessages()).isEqualTo(10);
79 | }
80 |
81 | @Test
82 | public void shouldHaveNoExplicitEndpoint() {
83 | final SQSTriggerQueue queue = new SQSTriggerQueue(null, "test-queue", "accessKey", null, 0, 0);
84 | queue.setFactory(this.sqsFactory);
85 |
86 | assertThat(queue.getUrl()).isEqualTo("mock://sqs.url");
87 | assertThat(queue.getName()).isEqualTo("test-queue");
88 | assertThat(queue.getEndpoint()).isNull();
89 | assertThat(queue.getNameOrUrl()).isEqualTo("test-queue");
90 | }
91 |
92 | @Test
93 | public void shouldHaveExplicitEndpoint() {
94 | final SQSTriggerQueue queue = new SQSTriggerQueue(
95 | null,
96 | "https://sqs.us-east-1.amazonaws.com/929548749884/test-queue",
97 | "accessKey",
98 | null,
99 | 0,
100 | 0);
101 | queue.setFactory(this.sqsFactory);
102 |
103 | assertThat(queue.getUrl()).isEqualTo("https://sqs.us-east-1.amazonaws.com/929548749884/test-queue");
104 | assertThat(queue.getName()).isEqualTo("test-queue");
105 | assertThat(queue.getEndpoint()).isEqualTo("sqs.us-east-1.amazonaws.com");
106 | assertThat(queue.getNameOrUrl()).isEqualTo("https://sqs.us-east-1.amazonaws.com/929548749884/test-queue");
107 | }
108 |
109 | @Test
110 | public void shouldAcceptAnyUrl() {
111 | final SQSTriggerQueue queue = new SQSTriggerQueue(
112 | null,
113 | "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test",
114 | "accessKey",
115 | null,
116 | 0,
117 | 0);
118 | queue.setFactory(this.sqsFactory);
119 |
120 | assertThat(queue.getUrl()).isEqualTo("mock://sqs.url");
121 | assertThat(queue.getName()).isEqualTo("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test");
122 | assertThat(queue.getEndpoint()).isNull();
123 | assertThat(queue.getNameOrUrl()).isEqualTo("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test");
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/model/EventTriggerMatcherImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.model;
18 |
19 | import org.eclipse.jgit.transport.RemoteConfig;
20 | import org.eclipse.jgit.transport.URIish;
21 | import org.jenkinsci.plugins.multiplescms.MultiSCM;
22 |
23 | import java.util.List;
24 |
25 | import hudson.model.AbstractProject;
26 | import hudson.plugins.git.BranchSpec;
27 | import hudson.plugins.git.GitSCM;
28 | import hudson.scm.SCM;
29 | import io.relution.jenkins.scmsqs.interfaces.Event;
30 | import io.relution.jenkins.scmsqs.interfaces.EventTriggerMatcher;
31 | import io.relution.jenkins.scmsqs.logging.Log;
32 | import jenkins.model.Jenkins;
33 |
34 |
35 | public class EventTriggerMatcherImpl implements EventTriggerMatcher {
36 |
37 | @Override
38 | public boolean matches(final List events, final AbstractProject, ?> job) {
39 | if (events == null || job == null) {
40 | return false;
41 | }
42 |
43 | Log.info("Test if any event matches job %s", job.getName());
44 |
45 | for (final Event event : events) {
46 | if (this.matches(event, job.getScm())) {
47 | Log.info("Job %s matches event %s%s (%s)", job.getName(), event.getHost(), event.getPath(), event.getBranch());
48 | return true;
49 | }
50 | }
51 |
52 | Log.info("Event(s) did not match job.");
53 | return false;
54 | }
55 |
56 | private boolean matches(final Event event, final SCM scm) {
57 | if (event == null || scm == null) {
58 | return false;
59 | }
60 |
61 | if (this.isGitScmAvailable() && this.matchesGitSCM(event, scm)) {
62 | return true;
63 |
64 | } else if (this.isMultiScmAvailable() && this.matchesMultiSCM(event, scm)) {
65 | return true;
66 |
67 | } else {
68 | return false;
69 |
70 | }
71 | }
72 |
73 | private boolean matchesGitSCM(final Event event, final SCM scmProvider) {
74 | if (!(scmProvider instanceof hudson.plugins.git.GitSCM)) {
75 | return false;
76 | }
77 |
78 | final GitSCM git = (GitSCM) scmProvider;
79 | final List configs = git.getRepositories();
80 | final List branches = git.getBranches();
81 |
82 | return this.matchesConfigs(event, configs) && this.matchesBranches(event, branches);
83 | }
84 |
85 | private boolean matchesMultiSCM(final Event event, final SCM scmProvider) {
86 | if (!(scmProvider instanceof org.jenkinsci.plugins.multiplescms.MultiSCM)) {
87 | return false;
88 | }
89 |
90 | final MultiSCM multiSCM = (MultiSCM) scmProvider;
91 | final List scms = multiSCM.getConfiguredSCMs();
92 |
93 | for (final SCM scm : scms) {
94 | if (this.matches(event, scm)) {
95 | return true;
96 | }
97 | }
98 |
99 | return false;
100 | }
101 |
102 | private boolean matchesBranches(final Event event, final List branches) {
103 | for (final BranchSpec branch : branches) {
104 | if (this.matchesBranch(event, branch)) {
105 | return true;
106 | }
107 | }
108 | return false;
109 | }
110 |
111 | private boolean matchesBranch(final Event event, final BranchSpec branch) {
112 | return branch.matches(event.getBranch());
113 | }
114 |
115 | private boolean matchesConfigs(final Event event, final List configs) {
116 | for (final RemoteConfig config : configs) {
117 | if (this.matchesConfig(event, config)) {
118 | return true;
119 | }
120 | }
121 | return false;
122 | }
123 |
124 | private boolean matchesConfig(final Event event, final RemoteConfig config) {
125 | for (final URIish uri : config.getURIs()) {
126 | if (event.isMatch(uri)) {
127 | return true;
128 | }
129 | }
130 | return false;
131 | }
132 |
133 | private boolean isMultiScmAvailable() {
134 | final Jenkins jenkins = Jenkins.getInstance();
135 |
136 | if (jenkins == null) {
137 | return false;
138 | }
139 |
140 | return jenkins.getPlugin("multiple-scms") != null;
141 | }
142 |
143 | private boolean isGitScmAvailable() {
144 | final Jenkins jenkins = Jenkins.getInstance();
145 |
146 | if (jenkins == null) {
147 | return false;
148 | }
149 |
150 | return jenkins.getPlugin("git") != null;
151 | }
152 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | 4.0.0
8 |
9 |
10 | org.jenkins-ci.plugins
11 | plugin
12 | 2.3
13 |
14 |
15 |
16 | io.relution.jenkins
17 | https://wiki.jenkins-ci.org/display/JENKINS/SCM+SQS+Plugin
18 | scm-sqs
19 | 1.5-SNAPSHOT
20 | hpi
21 |
22 | AWS SQS Build Trigger Plugin
23 |
24 | This plugin triggers builds on events from CodeCommit that are published via Amazon Web
25 | Services Simple Queue Service (AWS SQS).
26 |
27 |
28 |
29 | scm:git:ssh://github.com/jenkinsci/${project.artifactId}-plugin.git
30 | scm:git:ssh://git@github.com/jenkinsci/${project.artifactId}-plugin.git
31 | https://github.com/jenkinsci/${project.artifactId}-plugin
32 |
33 |
34 |
35 | 1.626
36 | 7
37 | 2.2
38 |
39 |
40 |
41 |
42 | Apache License, Version 2.0
43 | http://www.apache.org/licenses/LICENSE-2.0
44 |
45 |
46 |
47 |
48 |
49 | mwaylabs
50 | M-Way Solutions GmbH
51 | jenkins.ios.bot@mwaysolutions.com
52 |
53 |
54 |
55 |
56 |
57 | repo.jenkins-ci.org
58 | http://repo.jenkins-ci.org/public/
59 |
60 |
61 |
62 |
63 |
64 | repo.jenkins-ci.org
65 | http://repo.jenkins-ci.org/public/
66 |
67 |
68 |
69 |
70 |
71 |
72 | com.amazonaws
73 | aws-java-sdk-sqs
74 | 1.11.7
75 |
76 |
77 |
78 | com.google.inject
79 | guice
80 | 4.0
81 |
82 |
83 |
84 | com.google.code.gson
85 | gson
86 | 2.6.2
87 |
88 |
89 |
90 | org.jenkins-ci.plugins
91 | git
92 | true
93 | 2.4.4
94 |
95 |
96 |
97 | org.jenkins-ci.plugins
98 | multiple-scms
99 | true
100 | 0.5
101 |
102 |
103 |
104 | org.apache.commons
105 | commons-lang3
106 | 3.4
107 |
108 |
109 |
110 | net.sourceforge.htmlunit
111 | htmlunit
112 | 2.20
113 | test
114 |
115 |
116 |
117 | org.mockito
118 | mockito-core
119 | 1.10.19
120 | test
121 |
122 |
123 |
124 | org.assertj
125 | assertj-core
126 | 2.0.0
127 | test
128 |
129 |
130 |
131 |
132 |
133 |
134 | only-eclipse
135 |
136 |
137 | m2e.version
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | org.eclipse.m2e
146 | lifecycle-mapping
147 | 1.0.0
148 |
149 |
150 |
151 |
152 |
153 | org.apache.maven.plugins
154 | maven-javadoc-plugin
155 | [2.10.1,)
156 |
157 | javadoc
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/threading/SQSQueueMonitorSchedulerImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.threading;
18 |
19 | import com.google.common.eventbus.Subscribe;
20 | import com.google.inject.Inject;
21 |
22 | import org.apache.commons.lang3.StringUtils;
23 |
24 | import java.util.HashMap;
25 | import java.util.Iterator;
26 | import java.util.Map;
27 | import java.util.Map.Entry;
28 | import java.util.concurrent.ExecutorService;
29 |
30 | import io.relution.jenkins.scmsqs.interfaces.SQSFactory;
31 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
32 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueListener;
33 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitor;
34 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitorScheduler;
35 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueProvider;
36 | import io.relution.jenkins.scmsqs.logging.Log;
37 | import io.relution.jenkins.scmsqs.model.events.ConfigurationChangedEvent;
38 | import io.relution.jenkins.scmsqs.model.events.EventBroker;
39 | import io.relution.jenkins.scmsqs.util.ThrowIf;
40 |
41 |
42 | public class SQSQueueMonitorSchedulerImpl implements SQSQueueMonitorScheduler {
43 |
44 | private final ExecutorService executor;
45 | private final SQSQueueProvider provider;
46 | private final SQSFactory factory;
47 |
48 | private final Map monitors = new HashMap<>();
49 |
50 | @Inject
51 | public SQSQueueMonitorSchedulerImpl(final ExecutorService executor, final SQSQueueProvider provider, final SQSFactory factory) {
52 | this.executor = executor;
53 | this.provider = provider;
54 | this.factory = factory;
55 |
56 | EventBroker.getInstance().register(this);
57 | }
58 |
59 | @Override
60 | public boolean register(final SQSQueueListener listener) {
61 | ThrowIf.isNull(listener, "listener");
62 |
63 | Log.info("Register SQS listener");
64 | final String uuid = listener.getQueueUuid();
65 | final SQSQueue queue = this.provider.getSqsQueue(uuid);
66 |
67 | if (queue == null) {
68 | Log.warning("No queue for {%s}, aborted", uuid);
69 | return false;
70 | }
71 |
72 | this.register(listener, uuid, queue);
73 | return true;
74 | }
75 |
76 | @Override
77 | public synchronized boolean unregister(final SQSQueueListener listener) {
78 | if (listener == null) {
79 | return false;
80 | }
81 |
82 | Log.info("Unregister SQS listener");
83 | final String uuid = listener.getQueueUuid();
84 | final SQSQueueMonitor monitor = this.monitors.get(uuid);
85 |
86 | if (monitor == null) {
87 | Log.warning("No monitor for {%s}, aborted", uuid);
88 | return false;
89 | }
90 |
91 | Log.info("Remove listener from monitor for {%s}", uuid);
92 | if (monitor.remove(listener)) {
93 | monitor.shutDown();
94 | }
95 |
96 | if (monitor.isShutDown()) {
97 | Log.info("Monitor is shut down, remove monitor for {%s}", uuid);
98 | this.monitors.remove(uuid);
99 | }
100 |
101 | return true;
102 | }
103 |
104 | @Override
105 | @Subscribe
106 | public synchronized void onConfigurationChanged(final ConfigurationChangedEvent event) {
107 | final Iterator> entries = this.monitors.entrySet().iterator();
108 |
109 | while (entries.hasNext()) {
110 | final Entry entry = entries.next();
111 | this.reconfigure(entries, entry);
112 | }
113 | }
114 |
115 | private synchronized void register(final SQSQueueListener listener, final String uuid, final SQSQueue queue) {
116 | SQSQueueMonitor monitor = this.monitors.get(uuid);
117 |
118 | if (monitor == null) {
119 | Log.info("No monitor exists, creating new monitor for %s", queue);
120 | monitor = this.factory.createMonitor(this.executor, queue);
121 | this.monitors.put(uuid, monitor);
122 | }
123 |
124 | Log.info("Add listener to monitor for %s", queue);
125 | monitor.add(listener);
126 | }
127 |
128 | private void reconfigure(final Iterator> entries, final Entry entry) {
129 | final String uuid = entry.getKey();
130 | SQSQueueMonitor monitor = entry.getValue();
131 | final SQSQueue queue = this.provider.getSqsQueue(uuid);
132 |
133 | if (queue == null) {
134 | Log.info("Queue {%s} removed, shut down monitor", uuid);
135 | monitor.shutDown();
136 | entries.remove();
137 | } else if (monitor.isShutDown() || this.hasQueueChanged(monitor, queue)) {
138 | Log.info("Queue {%s} changed or monitor stopped, create new monitor", uuid);
139 | monitor = this.factory.createMonitor(monitor, queue);
140 | entry.setValue(monitor).shutDown();
141 | this.executor.execute(monitor);
142 | }
143 | }
144 |
145 | private boolean hasQueueChanged(final SQSQueueMonitor monitor, final SQSQueue queue) {
146 | try {
147 | final SQSQueue current = monitor.getQueue();
148 |
149 | if (!StringUtils.equals(current.getUrl(), queue.getUrl())) {
150 | return true;
151 | }
152 |
153 | if (!StringUtils.equals(current.getAWSAccessKeyId(), queue.getAWSAccessKeyId())) {
154 | return true;
155 | }
156 |
157 | if (!StringUtils.equals(current.getAWSSecretKey(), queue.getAWSSecretKey())) {
158 | return true;
159 | }
160 |
161 | if (current.getMaxNumberOfMessages() != queue.getMaxNumberOfMessages()) {
162 | return true;
163 | }
164 |
165 | if (current.getWaitTimeSeconds() != queue.getWaitTimeSeconds()) {
166 | return true;
167 | }
168 |
169 | return false;
170 | } catch (final com.amazonaws.AmazonServiceException e) {
171 | Log.warning("Cannot compare queues: %s", e.getMessage());
172 | } catch (final Exception e) {
173 | Log.severe(e, "Cannot compare queues, unknown error");
174 | }
175 | return true;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/threading/SQSQueueMonitorImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.threading;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 | import java.util.concurrent.ExecutorService;
24 | import java.util.concurrent.atomic.AtomicBoolean;
25 |
26 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
27 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueListener;
28 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitor;
29 | import io.relution.jenkins.scmsqs.logging.Log;
30 | import io.relution.jenkins.scmsqs.net.SQSChannel;
31 | import io.relution.jenkins.scmsqs.util.ThrowIf;
32 |
33 |
34 | public class SQSQueueMonitorImpl implements SQSQueueMonitor {
35 |
36 | private final static String ERROR_WRONG_QUEUE = "The specified listener is associated with another queue.";
37 |
38 | private final ExecutorService executor;
39 |
40 | private final SQSQueue queue;
41 | private final SQSChannel channel;
42 |
43 | private final Object listenersLock = new Object();
44 | private final List listeners;
45 |
46 | private final AtomicBoolean isRunning = new AtomicBoolean();
47 | private volatile boolean isShutDown;
48 |
49 | public SQSQueueMonitorImpl(final ExecutorService executor, final SQSQueue queue, final SQSChannel channel) {
50 | ThrowIf.isNull(executor, "executor");
51 | ThrowIf.isNull(channel, "channel");
52 |
53 | this.executor = executor;
54 |
55 | this.queue = queue;
56 | this.channel = channel;
57 |
58 | this.listeners = new ArrayList<>();
59 | }
60 |
61 | private SQSQueueMonitorImpl(final ExecutorService executor,
62 | final SQSQueue queue,
63 | final SQSChannel channel,
64 | final List listeners) {
65 | ThrowIf.isNull(executor, "executor");
66 | ThrowIf.isNull(channel, "channel");
67 |
68 | this.executor = executor;
69 |
70 | this.queue = queue;
71 | this.channel = channel;
72 |
73 | this.listeners = listeners;
74 | }
75 |
76 | @Override
77 | public SQSQueueMonitor clone(final SQSQueue queue, final SQSChannel channel) {
78 | synchronized (this.listenersLock) {
79 | return new SQSQueueMonitorImpl(this.executor, queue, channel, this.listeners);
80 | }
81 | }
82 |
83 | @Override
84 | public boolean add(final SQSQueueListener listener) {
85 | ThrowIf.isNull(listener, "listener");
86 | ThrowIf.notEqual(listener.getQueueUuid(), this.channel.getQueueUuid(), ERROR_WRONG_QUEUE);
87 |
88 | synchronized (this.listenersLock) {
89 | if (this.listeners.add(listener) && this.listeners.size() == 1) {
90 | this.isShutDown = false;
91 | this.execute();
92 | return true;
93 | }
94 | }
95 | return false;
96 | }
97 |
98 | @Override
99 | public boolean remove(final SQSQueueListener listener) {
100 | if (listener == null) {
101 | return false;
102 | }
103 |
104 | synchronized (this.listenersLock) {
105 | if (this.listeners.remove(listener) && this.listeners.isEmpty()) {
106 | this.shutDown();
107 | return true;
108 | }
109 | }
110 | return false;
111 | }
112 |
113 | @Override
114 | public void run() {
115 | try {
116 | if (this.isShutDown) {
117 | return;
118 | }
119 |
120 | if (!this.isRunning.compareAndSet(false, true)) {
121 | Log.warning("Monitor for %s already started", this.channel);
122 | return;
123 | }
124 |
125 | Log.fine("Start synchronous monitor for %s", this.channel);
126 | this.processMessages();
127 |
128 | } catch (final com.amazonaws.services.sqs.model.QueueDoesNotExistException e) {
129 | Log.warning("Queue %s does not exist, monitor stopped", this.channel);
130 | this.isShutDown = true;
131 |
132 | } catch (final com.amazonaws.AmazonServiceException e) {
133 | Log.warning("Service error for queue %s, monitor stopped", this.channel);
134 | this.isShutDown = true;
135 |
136 | } catch (final Exception e) {
137 | Log.severe(e, "Unknown error, monitor for queue %s stopped", this.channel);
138 | this.isShutDown = true;
139 |
140 | } finally {
141 | if (!this.isRunning.compareAndSet(true, false)) {
142 | Log.warning("Monitor for %s already stopped", this.channel);
143 | }
144 | this.execute();
145 | }
146 | }
147 |
148 | @Override
149 | public void shutDown() {
150 | Log.info("Shut down monitor for %s", this.channel);
151 | this.isShutDown = true;
152 | }
153 |
154 | @Override
155 | public boolean isShutDown() {
156 | return this.isShutDown;
157 | }
158 |
159 | @Override
160 | public SQSQueue getQueue() {
161 | return this.queue;
162 | }
163 |
164 | @Override
165 | public SQSChannel getChannel() {
166 | return this.channel;
167 | }
168 |
169 | private void execute() {
170 | if (!this.isShutDown) {
171 | this.executor.execute(this);
172 | }
173 | }
174 |
175 | private void processMessages() {
176 | final List messages = this.channel.getMessages();
177 |
178 | if (this.isShutDown) {
179 | return;
180 | }
181 |
182 | if (this.notifyListeners(messages)) {
183 | this.channel.deleteMessages(messages);
184 | }
185 | }
186 |
187 | private boolean notifyListeners(final List messages) {
188 | if (messages.isEmpty()) {
189 | Log.fine("Received no messages from %s", this.channel);
190 | return false;
191 | }
192 |
193 | Log.info("Received %d message(s) from %s", messages.size(), this.channel);
194 | final List listeners = this.getListeners();
195 |
196 | for (final SQSQueueListener listener : listeners) {
197 | listener.handleMessages(messages);
198 | }
199 |
200 | return true;
201 | }
202 |
203 | private List getListeners() {
204 | synchronized (this.listenersLock) {
205 | return new ArrayList<>(this.listeners);
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/test/java/io/relution/jenkins/scmsqs/threading/SQSQueueMonitorImplTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.threading;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
21 |
22 | import com.amazonaws.services.sqs.model.Message;
23 |
24 | import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
25 | import org.junit.Before;
26 | import org.junit.Test;
27 | import org.mockito.Mock;
28 | import org.mockito.Mockito;
29 | import org.mockito.MockitoAnnotations;
30 |
31 | import java.util.ArrayList;
32 | import java.util.Collections;
33 | import java.util.List;
34 | import java.util.concurrent.ExecutorService;
35 |
36 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
37 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueListener;
38 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitor;
39 | import io.relution.jenkins.scmsqs.net.SQSChannel;
40 |
41 |
42 | public class SQSQueueMonitorImplTest {
43 |
44 | private static final String UUID_A = "uuid-a";
45 | private static final String UUID_B = "uuid-b";
46 |
47 | @Mock
48 | private ExecutorService executor;
49 |
50 | @Mock
51 | private SQSQueue queue;
52 |
53 | @Mock
54 | private SQSChannel channel;
55 |
56 | @Mock
57 | private SQSQueueListener listener;
58 |
59 | private SQSQueueMonitor monitor;
60 |
61 | private final List messages = new ArrayList<>();
62 |
63 | @Before
64 | public void init() {
65 | MockitoAnnotations.initMocks(this);
66 |
67 | final Message message = new Message();
68 | this.messages.add(message);
69 |
70 | Mockito.when(this.channel.getMessages()).thenReturn(this.messages);
71 |
72 | Mockito.when(this.listener.getQueueUuid()).thenReturn(UUID_A);
73 | Mockito.when(this.channel.getQueueUuid()).thenReturn(UUID_A);
74 |
75 | this.monitor = new SQSQueueMonitorImpl(this.executor, this.queue, this.channel);
76 | }
77 |
78 | @Test
79 | public void shouldThrowIfRegisterNullListener() {
80 | assertThatThrownBy(new ThrowingCallable() {
81 |
82 | @Override
83 | public void call() throws Throwable {
84 | SQSQueueMonitorImplTest.this.monitor.add(null);
85 | }
86 |
87 | }).isInstanceOf(IllegalArgumentException.class);
88 | }
89 |
90 | @Test
91 | public void shouldNotThrowIfUnregisterNullListener() {
92 | assertThat(this.monitor.remove(null)).isFalse();
93 | assertThat(this.monitor.isShutDown()).isFalse();
94 | }
95 |
96 | @Test
97 | public void shouldNotThrowIfUnregisterUnknown() {
98 | assertThat(this.monitor.remove(this.listener)).isFalse();
99 | assertThat(this.monitor.isShutDown()).isFalse();
100 | }
101 |
102 | @Test
103 | public void shouldThrowIfWrongQueue() {
104 | Mockito.when(this.listener.getQueueUuid()).thenReturn(UUID_A);
105 | Mockito.when(this.channel.getQueueUuid()).thenReturn(UUID_B);
106 |
107 | assertThatThrownBy(new ThrowingCallable() {
108 |
109 | @Override
110 | public void call() throws Throwable {
111 | SQSQueueMonitorImplTest.this.monitor.add(SQSQueueMonitorImplTest.this.listener);
112 | }
113 |
114 | }).isInstanceOf(IllegalArgumentException.class);
115 | }
116 |
117 | @Test
118 | public void shouldStartMonitorForFirstListener() {
119 | final boolean result = this.monitor.add(this.listener);
120 |
121 | assertThat(result).isTrue();
122 | Mockito.verify(this.executor).execute(this.monitor);
123 | assertThat(this.monitor.isShutDown()).isFalse();
124 | }
125 |
126 | @Test
127 | public void shouldNotStartMultipleTimesForMultipleListeners() {
128 | this.monitor.add(this.listener);
129 |
130 | final boolean result = this.monitor.add(this.listener);
131 |
132 | assertThat(result).isFalse();
133 | Mockito.verify(this.executor).execute(this.monitor);
134 | assertThat(this.monitor.isShutDown()).isFalse();
135 | }
136 |
137 | @Test
138 | public void shouldNotStopBeforeLastListenerRemoved() {
139 | this.monitor.add(this.listener);
140 | this.monitor.add(this.listener);
141 | Mockito.verify(this.executor).execute(this.monitor);
142 |
143 | final boolean result = this.monitor.remove(this.listener);
144 |
145 | assertThat(result).isFalse();
146 | Mockito.verifyNoMoreInteractions(this.executor);
147 | assertThat(this.monitor.isShutDown()).isFalse();
148 | }
149 |
150 | @Test
151 | public void shouldStopIfLastListenerRemoved() {
152 | this.monitor.add(this.listener);
153 | this.monitor.add(this.listener);
154 | this.monitor.remove(this.listener);
155 | Mockito.verify(this.executor).execute(this.monitor);
156 |
157 | final boolean result = this.monitor.remove(this.listener);
158 |
159 | assertThat(result).isTrue();
160 | Mockito.verifyNoMoreInteractions(this.executor);
161 | assertThat(this.monitor.isShutDown()).isTrue();
162 | }
163 |
164 | @Test
165 | public void shouldQueryQueue() {
166 | this.monitor.add(this.listener);
167 | Mockito.verify(this.channel).getQueueUuid();
168 | Mockito.verify(this.listener).getQueueUuid();
169 | Mockito.verify(this.executor).execute(this.monitor);
170 |
171 | this.monitor.run();
172 |
173 | Mockito.verify(this.channel).getMessages();
174 | Mockito.verify(this.listener).handleMessages(this.messages);
175 | Mockito.verifyNoMoreInteractions(this.listener);
176 | Mockito.verify(this.channel).deleteMessages(this.messages);
177 | Mockito.verifyNoMoreInteractions(this.channel);
178 | Mockito.verify(this.executor, Mockito.times(2)).execute(this.monitor);
179 | }
180 |
181 | @Test
182 | public void shouldNotSendDeleteRequestIfResultIsEmpty() {
183 | final List messages = Collections.emptyList();
184 | Mockito.when(this.channel.getMessages()).thenReturn(messages);
185 | assertThat(this.monitor.add(this.listener)).isTrue();
186 | Mockito.verify(this.channel).getQueueUuid();
187 | Mockito.verify(this.listener).getQueueUuid();
188 | Mockito.verify(this.executor).execute(this.monitor);
189 |
190 | this.monitor.run();
191 |
192 | Mockito.verify(this.channel).getMessages();
193 | Mockito.verifyNoMoreInteractions(this.listener);
194 | Mockito.verifyNoMoreInteractions(this.channel);
195 | Mockito.verify(this.executor, Mockito.times(2)).execute(this.monitor);
196 | }
197 |
198 | @Test
199 | public void shouldNotRunIfAlreadyShutDown() {
200 | this.monitor.add(this.listener);
201 | Mockito.verify(this.channel).getQueueUuid();
202 | Mockito.verify(this.listener).getQueueUuid();
203 | Mockito.verify(this.executor).execute(this.monitor);
204 |
205 | this.monitor.shutDown();
206 | this.monitor.run();
207 |
208 | assertThat(this.monitor.isShutDown()).isTrue();
209 | Mockito.verifyNoMoreInteractions(this.channel);
210 | Mockito.verifyNoMoreInteractions(this.listener);
211 | Mockito.verifyNoMoreInteractions(this.executor);
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2016 M-Way Solutions GmbH
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 | You may obtain a copy of the License at
183 |
184 | http://www.apache.org/licenses/LICENSE-2.0
185 |
186 | Unless required by applicable law or agreed to in writing, software
187 | distributed under the License is distributed on an "AS IS" BASIS,
188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 | See the License for the specific language governing permissions and
190 | limitations under the License.
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/jenkinsci/scm-sqs-plugin)
2 |
3 | #SCM SQS Plugin for Jenkins
4 | A Jenkins plugin that allows to use the Amazon Simple Queue Service (SQS) as a build trigger. Currently supports messages sent by Git repositories hosted on Amazon's CodeCommit. In the future additional services may be supported.
5 |
6 | To use this plugin you will need to have the following:
7 |
8 | 1. An Amazon Web Services (AWS) account
9 | 2. A Git repository that is hosted on CodeCommit
10 | 3. A Simple Notification Service (SNS) topic
11 | 4. A Simple Queue Service (SQS) queue
12 | 5. A user that is allowed to access the queue
13 |
14 | #Table of contents
15 | 1. [Using the plugin](#using-the-plugin)
16 | 1. [Install the plugin](#install-the-plugin-on-jenkins)
17 | 2. [Set up AWS users](#create-a-jenkins-user-on-aws)
18 | 3. [Create a repository](#create-a-codecommit-repository)
19 | 4. [Create an SQS queue](#create-an-sqs-queue-on-aws)
20 | 5. [Test access to the queue](#test-whether-jenkins-can-access-the-queue)
21 | 6. [Create an SNS topic](#create-an-sns-topic-on-aws)
22 | 7. [Link SNS topic and SQS queue](#link-sns-topic-and-sqs-queue)
23 | 8. [Link AWS CodeCommit and SNS topic](#link-aws-codecommit-and-sns-topic)
24 | 9. [Configure Jenkins jobs](#configure-jobs-to-use-the-queue-on-jenkins)
25 | 10. [Test your setup](#test-your-setup)
26 | 2. [Development](#development)
27 | 3. [License](#license)
28 | 4. [Maintainer](#maintainers)
29 |
30 | ##Using the plugin
31 |
32 | This setup assumes that you already have an AWS account and that you're able to log in to your AWS account. You must also be able to manage users and groups and you must be able to create a CodeCommit repository, an SNS topic and an SQS queue. If you don't have the necessary permissions find someone who does.
33 |
34 | ###Install the plugin on Jenkins
35 |
36 | 1. Go to `Jenkins > Manage Jenkins > Manage Plugins`.
37 | 2. Go to `Available` and search for `scm-sqs` or `aws sqs`.
38 | 3. Install the plugin and restart your Jenkins.
39 |
40 | If you've built the plugin from source go to `Advanced` and upload the plugin manually. Don't forget to check the plugin's Wiki page on Jenkins-CI.org: https://wiki.jenkins-ci.org/display/JENKINS/SCM+SQS+Plugin.
41 |
42 | After you've successfully installed the plugin you should see a new entry in your global Jenkins configuration. Go to `Jenkins > Manage Jenkins > Configure System` to verify. You should be able to find an entry similar to the one below.
43 |
44 | 
45 |
46 | ###Create a Jenkins user on AWS
47 |
48 | 1. Log in to your [Amazon Web Services](https://aws.amazon.com/) account.
49 | 2. Go to `Services > Security & Identity > IAM`
50 | 3. **Create** a **new group** called *Jenkins*
51 | 4. Assign the following managed policies to the user:
52 |
53 | * AmazonSQSFullAccess
54 | * AWSCodeCommitReadOnly
55 |
56 | The `AmazonSQSFullAccess` policy is required for Jenkins to be able to read messages from queues and to delete messages from queues once they've been processed.
57 |
58 | The `AWSCodeCommitReadOnly` permission is required for Jenkins to be able to check out code to build.
59 |
60 | 5. **Create** a **new user** called *Jenkins*
61 | 6. Assign the *Jenkins* user to the *Jenkins* group
62 | 7. Go to `IAM > Users > Jenkins > Security Credentials`
63 | 8. **Create** a new **Access Key** for the Jenins user
64 |
65 | **Important:** You will need the `Access Key ID` and `Secret Key` for Jenkins to be able to access the SQS queue. Make sure to save both values in a secure place.
66 |
67 | ###Create a CodeCommit repository
68 |
69 | Before you start to configure the plugin you should have at least one Git repository on CodeCommit. If you do not already have a repository follow the steps below to create one.
70 |
71 | **Note:** At the time of writing CodeCommit is only available in the [**US East (N. Virginia)** region](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/). AWS will automatically switch to that region when you access CodeCommit. All services (CodeCommit, SNS, SQS) must be created in the same region, so do not switch regions after you've created the repository.
72 |
73 | 1. Go to `Services > Developer Tools > CodeCommit`
74 |
75 | 2. **Create** a **new repository**
76 |
77 | 3. Enter a name and description for the repository
78 |
79 | At the very least you'll need to enter a new name for the repository. For this plugin we would use something like *scm-sqs-plugin*. To be able to work with repositories your user account also needs permission to access CodeCommit:
80 |
81 | 1. Go to `Services > Security & Identity > IAM`
82 |
83 | 2. Find and open your user account
84 |
85 | 3. Go to `Permissions`
86 |
87 | 4. Click on `Attach Policy`
88 |
89 | 5. Find the developer policy for your Git repository
90 |
91 | When you create a repository AWS will automatically create a policy for it. In the example above the policy would be named *scm-sqs-plugin-developer*. Alternatively you could assign the policy *AmazonSQSFullAccess* which will automatically give your user access to all repositories on CodeCommit.
92 |
93 | In addition to the policy your account also needs a public SSH key assigned. Access to repositories on CodeCommit is only possible via SSH.
94 |
95 | 1. Switch to the tab `Security Credentials`
96 |
97 | 2. `Upload` an `SSH public key`
98 |
99 | 3. You will need the `SSH Key ID` to access the repository
100 |
101 | You should now be able to clone the repository and start working with it. The repository URL for our example would be *ssh://`ssh-key-id`@git-codecommit.us-east-1.amazonaws.com/v1/repos/scm_sqs_plugin*
102 |
103 | ###Create an SQS queue on AWS
104 |
105 | **Note:** The SQS queue must be created in the same region as your CodeCommit repository. At the time of writing CodeCommit is only available in the **US East (N. Virginia)** region. This means the SQS queue must also be created in the US East region.
106 |
107 | 1. Go to `Services > Application Services > SQS`
108 |
109 | 2. **Create** a **new queue**
110 |
111 | At the very least you'll need to enter a new name for the queue. If you already have a repository something like **_repository-name_-queue** is a good idea. So for the *scm-sqs-plugin* repository we would use *scm-sqs-plugin-queue*.
112 |
113 | Review the remaining options and adjust them to your needs. If you do not know what these options do just leave them at their defaults.
114 |
115 | 3. Copy the *ARN* of the queue for later
116 |
117 | ###Test whether Jenkins can access the queue
118 |
119 | 1. Go to `Jenkins > Manage Jenkins > Configure System` on your Jenkins
120 |
121 | 2. Go to `Configuration of Amazon SQS queues`
122 |
123 | 3. Configure a queue
124 |
125 | * Enter the name of the queue you just created
126 | * Enter the *Access key ID* of the Jenkins user on AWS
127 | * Enter the *Secret key* of the Jenkins user on AWS
128 |
129 | 4. Click on **Test access**
130 |
131 | You should see a success message as in the screenshot below. If you get an error message make sure you entered the credentials correctly. If you still see errors double check the user, group and permissions you set up on Amazon Web Services.
132 |
133 | 
134 |
135 | ###Create an SNS topic on AWS
136 |
137 | **Note:** The SNS topic must be created in the same region as your CodeCommit repository. At the time of writing CodeCommit is only available in the **US East (N. Virginia)** region. This means the SNS topic must also be created in the US East region.
138 |
139 | 1. Go to `Services > Mobile Services > SNS`
140 | 2. Go to `Topics`
141 | 3. **Create** a **new topic**
142 |
143 | Enter a new *topic name* (the display name is optional in our case). If you already have a repository something like **_repository-name_-topic** is a good idea. So for the *scm-sqs-plugin* repository we would use the *scm-sqs-plugin-topic*.
144 |
145 | The new topic should have an *ARN* similar to `arn:aws:sns:us-east-1:{id}:{topic-name}`.
146 |
147 | ###Link SNS topic and SQS queue
148 |
149 | 1. Click on the new topic you just created
150 | 2. **Create** a new **subcription**
151 | * The *Topic ARN* should be the ARN of the topic you just created.
152 | * Select **Amazon SQS** as the *protocol*
153 | * Use the *ARN* of the queue you created above as the endpoint
154 |
155 | These steps make sure that all notifications that are posted to this topic are placed in our SQS queue we created above. For testing purposes you could create an additional subscription that delivers all messages also to your inbox.
156 |
157 | 
158 |
159 | ###Link AWS CodeCommit and SNS topic
160 |
161 | 1. Go to `Services > Developer Tools > CodeCommit`
162 | 2. Select a repository (or create a new one)
163 | 3. Click on the repositry
164 | 4. Go to `Triggers`
165 | 5. **Create** a new **trigger**
166 | * Enter a new for your trigger (e.g. "send-to-sns-on-push")
167 | * Select **Push to existing branch** as *Events*
168 | * Select the branch(es) you want to monitor
169 | * Select *Send to Amazon SNS*
170 | * Select your SNS topic you created above
171 | 6. Click on **Create**
172 |
173 | These steps make sure that whenever someone pushes changes to this repository a message is sent to SNS. The subscription we created on the notification service makes sure the message is fordwared to the SQS queue. The Jenkins plugin uses the Amazon API to monitor this queue for new messages.
174 |
175 | ###Configure jobs to use the queue on Jenkins
176 |
177 | 1. Go to `Jenkins > $job`
178 | 2. Click on `Configure`
179 | 3. Scroll down to `Build Triggers`
180 | 4. Check `Trigger build when a message is published to an Amazon SQS queue`
181 | 5. Select the queue you created previously
182 |
183 | To reduce cost the Jenkins plugin does not start monitoring a queue until at least one job has been configured to listen to messages from a queue.
184 |
185 | You can use the same queue for multiple jobs or you can create a new queue for each job. Keep in mind that monitoring multiple queues will increase the amount of requests your Jenkins will have to send to AWS. Unless you have specific needs reusing the same queue and topic for multiple jobs is completely acceptable. For billing purposes it may be easier to use multiple queues, especially if you're running builds on behalf of a customer.
186 |
187 | ###Test your setup
188 |
189 | If you've set up everything correctly pushing a change to the Git repository on CodeCommit should now trigger a build on Jenkins. If nothing happens, make sure the job has been set to use messages posted to SQS as a build trigger.
190 |
191 | 
192 |
193 | #Development
194 | 1. Start the local Jenkins instance:
195 |
196 | mvn clean compile hpi:run
197 |
198 | 2. Wait until "Jenkins is fully up and running" is shown in the terminal
199 | (there may be more messages after that)
200 |
201 | 3. Open http://localhost:8080/jenkins/ in the browser
202 |
203 |
204 | #License
205 | Apache License, Version 2.0
206 | ```
207 | Copyright 2016 M-Way Solutions GmbH
208 |
209 | Licensed under the Apache License, Version 2.0 (the "License");
210 | you may not use this file except in compliance with the License.
211 | You may obtain a copy of the License at
212 |
213 | http://www.apache.org/licenses/LICENSE-2.0
214 |
215 | Unless required by applicable law or agreed to in writing, software
216 | distributed under the License is distributed on an "AS IS" BASIS,
217 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
218 | See the License for the specific language governing permissions and
219 | limitations under the License.
220 | ```
221 |
222 | #Maintainers
223 | - [Markus Pfeiffer](https://github.com/mpfeiffermway) (M-Way Solutions GmbH) – 2016
224 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/SQSTrigger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs;
18 |
19 | import com.amazonaws.services.sqs.model.Message;
20 | import com.google.common.collect.Maps;
21 | import com.google.inject.Inject;
22 |
23 | import net.sf.json.JSONObject;
24 |
25 | import org.apache.commons.jelly.XMLOutput;
26 | import org.apache.commons.lang3.StringUtils;
27 | import org.kohsuke.stapler.DataBoundConstructor;
28 | import org.kohsuke.stapler.QueryParameter;
29 | import org.kohsuke.stapler.StaplerRequest;
30 |
31 | import java.io.File;
32 | import java.io.IOException;
33 | import java.nio.charset.Charset;
34 | import java.util.Collection;
35 | import java.util.Collections;
36 | import java.util.List;
37 | import java.util.Map;
38 | import java.util.concurrent.ExecutorService;
39 | import java.util.concurrent.Executors;
40 |
41 | import hudson.DescriptorExtensionList;
42 | import hudson.Extension;
43 | import hudson.Util;
44 | import hudson.console.AnnotatedLargeText;
45 | import hudson.model.AbstractProject;
46 | import hudson.model.Action;
47 | import hudson.model.Item;
48 | import hudson.triggers.Trigger;
49 | import hudson.triggers.TriggerDescriptor;
50 | import hudson.util.FormValidation;
51 | import hudson.util.ListBoxModel;
52 | import hudson.util.SequentialExecutionQueue;
53 | import io.relution.jenkins.scmsqs.i18n.sqstrigger.Messages;
54 | import io.relution.jenkins.scmsqs.interfaces.Event;
55 | import io.relution.jenkins.scmsqs.interfaces.EventTriggerMatcher;
56 | import io.relution.jenkins.scmsqs.interfaces.MessageParser;
57 | import io.relution.jenkins.scmsqs.interfaces.MessageParserFactory;
58 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
59 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueListener;
60 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitorScheduler;
61 | import io.relution.jenkins.scmsqs.logging.Log;
62 | import io.relution.jenkins.scmsqs.model.events.ConfigurationChangedEvent;
63 | import io.relution.jenkins.scmsqs.model.events.EventBroker;
64 |
65 |
66 | public class SQSTrigger extends Trigger> implements SQSQueueListener, Runnable {
67 |
68 | private final String queueUuid;
69 |
70 | private transient SQSQueueMonitorScheduler scheduler;
71 |
72 | private transient MessageParserFactory messageParserFactory;
73 | private transient EventTriggerMatcher eventTriggerMatcher;
74 |
75 | private transient ExecutorService executor;
76 |
77 | @DataBoundConstructor
78 | public SQSTrigger(final String queueUuid) {
79 | this.queueUuid = queueUuid;
80 | }
81 |
82 | public File getLogFile() {
83 | return new File(this.job.getRootDir(), "sqs-polling.log");
84 | }
85 |
86 | @Override
87 | public void start(final AbstractProject, ?> project, final boolean newInstance) {
88 | super.start(project, newInstance);
89 |
90 | final DescriptorImpl descriptor = (DescriptorImpl) this.getDescriptor();
91 | descriptor.queue.execute(new Runnable() {
92 |
93 | @Override
94 | public void run() {
95 | Log.info("Start trigger for project %s", project);
96 | SQSTrigger.this.getScheduler().register(SQSTrigger.this);
97 | }
98 | });
99 | }
100 |
101 | @Override
102 | public void run() {
103 | final SQSTriggerBuilder builder = new SQSTriggerBuilder(this, this.job);
104 | builder.run();
105 | }
106 |
107 | @Override
108 | public void stop() {
109 | super.stop();
110 |
111 | final DescriptorImpl descriptor = (DescriptorImpl) this.getDescriptor();
112 | descriptor.queue.execute(new Runnable() {
113 |
114 | @Override
115 | public void run() {
116 | Log.info("Stop trigger (%s)", this);
117 | SQSTrigger.this.getScheduler().unregister(SQSTrigger.this);
118 | }
119 | });
120 | }
121 |
122 | @Override
123 | public Collection extends Action> getProjectActions() {
124 | return Collections.singleton(new SQSTriggerPollingAction());
125 | }
126 |
127 | @Override
128 | public void handleMessages(final List messages) {
129 | for (final Message message : messages) {
130 | this.handleMessage(message);
131 | }
132 | }
133 |
134 | @Override
135 | public String getQueueUuid() {
136 | return this.queueUuid;
137 | }
138 |
139 | @Inject
140 | public void setScheduler(final SQSQueueMonitorScheduler scheduler) {
141 | this.scheduler = scheduler;
142 | }
143 |
144 | public SQSQueueMonitorScheduler getScheduler() {
145 | if (this.scheduler == null) {
146 | Context.injector().injectMembers(this);
147 | }
148 | return this.scheduler;
149 | }
150 |
151 | @Inject
152 | public void setMessageParserFactory(final MessageParserFactory factory) {
153 | this.messageParserFactory = factory;
154 | }
155 |
156 | public MessageParserFactory getMessageParserFactory() {
157 | if (this.messageParserFactory == null) {
158 | Context.injector().injectMembers(this);
159 | }
160 | return this.messageParserFactory;
161 | }
162 |
163 | @Inject
164 | public void setEventTriggerMatcher(final EventTriggerMatcher matcher) {
165 | this.eventTriggerMatcher = matcher;
166 | }
167 |
168 | public EventTriggerMatcher getEventTriggerMatcher() {
169 | if (this.eventTriggerMatcher == null) {
170 | Context.injector().injectMembers(this);
171 | }
172 | return this.eventTriggerMatcher;
173 | }
174 |
175 | @Inject
176 | public void setExecutorService(final ExecutorService executor) {
177 | this.executor = executor;
178 | }
179 |
180 | public ExecutorService getExecutorService() {
181 | if (this.executor == null) {
182 | Context.injector().injectMembers(this);
183 | }
184 | return this.executor;
185 | }
186 |
187 | private void handleMessage(final Message message) {
188 | final MessageParser parser = this.messageParserFactory.createParser(message);
189 | final EventTriggerMatcher matcher = this.getEventTriggerMatcher();
190 | final List events = parser.parseMessage(message);
191 |
192 | if (matcher.matches(events, this.job)) {
193 | this.execute();
194 | }
195 | }
196 |
197 | private void execute() {
198 | Log.info("SQS event triggered build of %s", this.job.getFullDisplayName());
199 | this.executor.execute(this);
200 | }
201 |
202 | public final class SQSTriggerPollingAction implements Action {
203 |
204 | public AbstractProject, ?> getOwner() {
205 | return SQSTrigger.this.job;
206 | }
207 |
208 | @Override
209 | public String getIconFileName() {
210 | return "clipboard.png";
211 | }
212 |
213 | @Override
214 | public String getDisplayName() {
215 | return "SQS Activity Log";
216 | }
217 |
218 | @Override
219 | public String getUrlName() {
220 | return "SQSActivityLog";
221 | }
222 |
223 | public String getLog() throws IOException {
224 | return Util.loadFile(SQSTrigger.this.getLogFile());
225 | }
226 |
227 | public void writeLogTo(final XMLOutput out) throws IOException {
228 | final AnnotatedLargeText> log = new AnnotatedLargeText(
229 | SQSTrigger.this.getLogFile(),
230 | Charset.defaultCharset(),
231 | true,
232 | this);
233 |
234 | log.writeHtmlTo(0, out.asWriter());
235 | }
236 | }
237 |
238 | @Extension
239 | public static final class DescriptorImpl extends TriggerDescriptor {
240 |
241 | private static final String KEY_SQS_QUEUES = "sqsQueues";
242 | private volatile List sqsQueues;
243 |
244 | private volatile transient Map sqsQueueMap;
245 | private transient boolean isLoaded;
246 |
247 | private transient final SequentialExecutionQueue queue = new SequentialExecutionQueue(Executors.newSingleThreadExecutor());
248 |
249 | public static DescriptorImpl get() {
250 | final DescriptorExtensionList, TriggerDescriptor> triggers = Trigger.all();
251 | return triggers.get(DescriptorImpl.class);
252 | }
253 |
254 | public DescriptorImpl() {
255 | super(SQSTrigger.class);
256 | }
257 |
258 | @Override
259 | public synchronized void load() {
260 | super.load();
261 | this.initQueueMap();
262 | this.isLoaded = true;
263 | }
264 |
265 | @Override
266 | public boolean isApplicable(final Item item) {
267 | return item instanceof AbstractProject;
268 | }
269 |
270 | @Override
271 | public String getDisplayName() {
272 | return Messages.displayName();
273 | }
274 |
275 | public ListBoxModel doFillQueueUuidItems() {
276 | final List queues = this.getSqsQueues();
277 | final ListBoxModel items = new ListBoxModel();
278 |
279 | for (final SQSTriggerQueue queue : queues) {
280 | items.add(queue.getName(), queue.getUuid());
281 | }
282 |
283 | return items;
284 | }
285 |
286 | public FormValidation doCheckQueueUuid(@QueryParameter final String value) {
287 | if (this.getSqsQueues().size() == 0) {
288 | return FormValidation.error(Messages.errorQueueUnavailable());
289 | }
290 |
291 | if (StringUtils.isEmpty(value)) {
292 | return FormValidation.ok(Messages.infoQueueDefault());
293 | }
294 |
295 | final SQSQueue queue = this.getSqsQueue(value);
296 |
297 | if (queue == null) {
298 | return FormValidation.error(Messages.errorQueueUuidUnknown());
299 | }
300 |
301 | return FormValidation.ok();
302 | }
303 |
304 | @Override
305 | public boolean configure(final StaplerRequest req, final JSONObject json) throws FormException {
306 | final Object sqsQueues = json.get(KEY_SQS_QUEUES);
307 |
308 | this.sqsQueues = req.bindJSONToList(SQSTriggerQueue.class, sqsQueues);
309 | this.initQueueMap();
310 | this.save();
311 |
312 | EventBroker.getInstance().post(new ConfigurationChangedEvent());
313 | return true;
314 | }
315 |
316 | public List getSqsQueues() {
317 | if (!this.isLoaded) {
318 | this.load();
319 | }
320 | if (this.sqsQueues == null) {
321 | return Collections.emptyList();
322 | }
323 | return this.sqsQueues;
324 | }
325 |
326 | public SQSQueue getSqsQueue(final String uuid) {
327 | if (!this.isLoaded) {
328 | this.load();
329 | }
330 | if (this.sqsQueueMap == null) {
331 | return null;
332 | }
333 | return this.sqsQueueMap.get(uuid);
334 | }
335 |
336 | private void initQueueMap() {
337 | if (this.sqsQueues == null) {
338 | return;
339 | }
340 |
341 | this.sqsQueueMap = Maps.newHashMapWithExpectedSize(this.sqsQueues.size());
342 |
343 | for (final SQSTriggerQueue queue : this.sqsQueues) {
344 | this.sqsQueueMap.put(queue.getUuid(), queue);
345 | }
346 | }
347 | }
348 | }
349 |
--------------------------------------------------------------------------------
/src/main/java/io/relution/jenkins/scmsqs/SQSTriggerQueue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs;
18 |
19 | import com.amazonaws.AmazonServiceException;
20 | import com.amazonaws.services.sqs.AmazonSQS;
21 | import com.amazonaws.services.sqs.model.GetQueueUrlResult;
22 | import com.google.inject.Inject;
23 |
24 | import org.apache.commons.lang3.StringUtils;
25 | import org.kohsuke.stapler.DataBoundConstructor;
26 | import org.kohsuke.stapler.QueryParameter;
27 |
28 | import java.io.IOException;
29 | import java.util.UUID;
30 | import java.util.regex.Matcher;
31 | import java.util.regex.Pattern;
32 |
33 | import hudson.Extension;
34 | import hudson.model.AbstractDescribableImpl;
35 | import hudson.model.Descriptor;
36 | import hudson.util.FormValidation;
37 | import hudson.util.Secret;
38 | import io.relution.jenkins.scmsqs.i18n.sqstriggerqueue.Messages;
39 | import io.relution.jenkins.scmsqs.interfaces.SQSFactory;
40 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
41 | import io.relution.jenkins.scmsqs.logging.Log;
42 |
43 |
44 | public class SQSTriggerQueue extends AbstractDescribableImpl implements SQSQueue {
45 |
46 | public static final Pattern SQS_URL_PATTERN = Pattern
47 | .compile("^(?:http(?:s)?://)?(?sqs\\..+?\\.amazonaws\\.com)/(?.+?)/(?.*)$");
48 |
49 | public static final Pattern CODECOMMIT_URL_PATTERN = Pattern
50 | .compile("^(?:http(?:s)?://)?git-codecommit\\.(?.+?)\\.amazonaws\\.com/v1/repos/(?.*)$");
51 |
52 | private static final int WAIT_TIME_SECONDS_DEFAULT = 20;
53 | private static final int WAIT_TIME_SECONDS_MIN = 1;
54 | private static final int WAIT_TIME_SECONDS_MAX = 20;
55 |
56 | private static final int MAX_NUMBER_OF_MESSAGES_DEFAULT = 10;
57 | private static final int MAX_NUMBER_OF_MESSAGES_MIN = 1;
58 | private static final int MAX_NUMBER_OF_MESSAGES_MAX = 10;
59 |
60 | private final String uuid;
61 |
62 | private final String nameOrUrl;
63 | private final String accessKey;
64 | private final Secret secretKey;
65 |
66 | private final Integer waitTimeSeconds;
67 | private final Integer maxNumberOfMessages;
68 |
69 | private String url;
70 | private final String name;
71 | private final String endpoint;
72 |
73 | private transient SQSFactory factory;
74 | private transient AmazonSQS sqs;
75 |
76 | private transient String s;
77 |
78 | @DataBoundConstructor
79 | public SQSTriggerQueue(
80 | final String uuid,
81 | final String nameOrUrl,
82 | final String accessKey,
83 | final Secret secretKey,
84 | final Integer waitTimeSeconds,
85 | final Integer maxNumberOfMessages) {
86 | this.uuid = StringUtils.isBlank(uuid) ? UUID.randomUUID().toString() : uuid;
87 | this.accessKey = accessKey;
88 | this.secretKey = secretKey;
89 | this.nameOrUrl = nameOrUrl;
90 |
91 | this.waitTimeSeconds = this.limit(
92 | waitTimeSeconds,
93 | WAIT_TIME_SECONDS_MIN,
94 | WAIT_TIME_SECONDS_MAX,
95 | WAIT_TIME_SECONDS_DEFAULT);
96 |
97 | this.maxNumberOfMessages = this.limit(
98 | maxNumberOfMessages,
99 | MAX_NUMBER_OF_MESSAGES_MIN,
100 | MAX_NUMBER_OF_MESSAGES_MAX,
101 | MAX_NUMBER_OF_MESSAGES_DEFAULT);
102 |
103 | final Matcher sqsUrlMatcher = SQS_URL_PATTERN.matcher(nameOrUrl);
104 |
105 | if (sqsUrlMatcher.matches()) {
106 | this.url = nameOrUrl;
107 | this.name = sqsUrlMatcher.group("name");
108 | this.endpoint = sqsUrlMatcher.group("endpoint");
109 |
110 | } else {
111 | this.name = nameOrUrl;
112 | this.endpoint = null;
113 |
114 | }
115 |
116 | Log.info("Create new SQSTriggerQueue(%s, %s, %s)", this.uuid, nameOrUrl, accessKey);
117 | }
118 |
119 | public AmazonSQS getSQSClient() {
120 | if (this.sqs == null) {
121 | this.sqs = this.getFactory().createSQSAsync(this);
122 | }
123 | return this.sqs;
124 | }
125 |
126 | @Inject
127 | public void setFactory(final SQSFactory factory) {
128 | this.factory = factory;
129 | }
130 |
131 | public SQSFactory getFactory() {
132 | if (this.factory == null) {
133 | Context.injector().injectMembers(this);
134 | }
135 | return this.factory;
136 | }
137 |
138 | @Override
139 | public String getUuid() {
140 | return this.uuid;
141 | }
142 |
143 | public String getNameOrUrl() {
144 | return this.nameOrUrl;
145 | }
146 |
147 | public String getAccessKey() {
148 | return this.accessKey;
149 | }
150 |
151 | public Secret getSecretKey() {
152 | return this.secretKey;
153 | }
154 |
155 | @Override
156 | public int getWaitTimeSeconds() {
157 | if (this.waitTimeSeconds == null) {
158 | return WAIT_TIME_SECONDS_DEFAULT;
159 | }
160 | return this.waitTimeSeconds;
161 | }
162 |
163 | @Override
164 | public int getMaxNumberOfMessages() {
165 | if (this.maxNumberOfMessages == null) {
166 | return MAX_NUMBER_OF_MESSAGES_DEFAULT;
167 | }
168 | return this.maxNumberOfMessages;
169 | }
170 |
171 | @Override
172 | public String getUrl() {
173 | if (this.url == null) {
174 | final AmazonSQS client = this.getSQSClient();
175 | final GetQueueUrlResult result = client.getQueueUrl(this.name);
176 | this.url = result.getQueueUrl();
177 | }
178 | return this.url;
179 | }
180 |
181 | @Override
182 | public String getName() {
183 | return this.name;
184 | }
185 |
186 | @Override
187 | public String getEndpoint() {
188 | return this.endpoint;
189 | }
190 |
191 | @Override
192 | public String getAWSAccessKeyId() {
193 | return this.accessKey;
194 | }
195 |
196 | @Override
197 | public String getAWSSecretKey() {
198 | if (this.secretKey == null) {
199 | return null;
200 | }
201 | return this.secretKey.getPlainText();
202 | }
203 |
204 | @Override
205 | public boolean isValid() {
206 | if (StringUtils.isBlank(this.getName())) {
207 | return false;
208 | }
209 | if (StringUtils.isEmpty(this.getAWSAccessKeyId())) {
210 | return false;
211 | }
212 | if (StringUtils.isEmpty(this.getAWSSecretKey())) {
213 | return false;
214 | }
215 | return true;
216 | }
217 |
218 | @Override
219 | public int hashCode() {
220 | return this.uuid.hashCode();
221 | }
222 |
223 | @Override
224 | public boolean equals(final Object obj) {
225 | if (obj == this) {
226 | return true;
227 | }
228 | if (obj == null) {
229 | return false;
230 | }
231 | if (!(obj instanceof SQSTriggerQueue)) {
232 | return false;
233 | }
234 | final SQSTriggerQueue other = (SQSTriggerQueue) obj;
235 | if (!this.uuid.equals(other.uuid)) {
236 | return false;
237 | }
238 | return true;
239 | }
240 |
241 | @Override
242 | public String toString() {
243 | if (this.s == null) {
244 | final StringBuilder sb = new StringBuilder();
245 | sb.append(this.name);
246 |
247 | if (!StringUtils.isBlank(this.endpoint)) {
248 | sb.append(" (");
249 | sb.append(this.endpoint);
250 | sb.append(")");
251 | }
252 |
253 | sb.append(" {");
254 | sb.append(this.uuid);
255 | sb.append("}");
256 |
257 | this.s = sb.toString();
258 | }
259 | return this.s;
260 | }
261 |
262 | private int limit(final Integer value, final int min, final int max, final int fallbackValue) {
263 | if (value == null || value < min || value > max) {
264 | return fallbackValue;
265 | } else {
266 | return value;
267 | }
268 | }
269 |
270 | @Extension
271 | public static class DescriptorImpl extends Descriptor {
272 |
273 | @Override
274 | public String getDisplayName() {
275 | return Messages.displayName(); // unused
276 | }
277 |
278 | public FormValidation doCheckNameOrUrl(@QueryParameter final String value) {
279 | if (StringUtils.isBlank(value)) {
280 | return FormValidation.warning(Messages.warningUrl());
281 | }
282 |
283 | final Matcher sqsUrlMatcher = SQS_URL_PATTERN.matcher(value);
284 |
285 | if (sqsUrlMatcher.matches()) {
286 | final String name = sqsUrlMatcher.group("name");
287 | return FormValidation.ok(Messages.infoUrlSqs(), name);
288 | }
289 |
290 | final Matcher ccUrlMatcher = CODECOMMIT_URL_PATTERN.matcher(value);
291 |
292 | if (ccUrlMatcher.matches()) {
293 | return FormValidation.error(Messages.errorUrlCodecommit());
294 | }
295 |
296 | if (StringUtils.startsWith(value, "http://") || StringUtils.startsWith(value, "https://")) {
297 | return FormValidation.error(Messages.errorUrlUnknown());
298 | }
299 |
300 | return FormValidation.ok();
301 | }
302 |
303 | public FormValidation doCheckWaitTimeSeconds(@QueryParameter final String value) {
304 | return this.validateNumber(
305 | value,
306 | WAIT_TIME_SECONDS_MIN,
307 | WAIT_TIME_SECONDS_MAX,
308 | Messages.errorWaitTimeSeconds());
309 | }
310 |
311 | public FormValidation doCheckMaxNumberOfMessage(@QueryParameter final String value) {
312 | return this.validateNumber(
313 | value,
314 | MAX_NUMBER_OF_MESSAGES_MIN,
315 | MAX_NUMBER_OF_MESSAGES_MAX,
316 | Messages.errorMaxNumberOfMessages());
317 | }
318 |
319 | public FormValidation doValidate(
320 | @QueryParameter final String uuid,
321 | @QueryParameter final String nameOrUrl,
322 | @QueryParameter final String accessKey,
323 | @QueryParameter final Secret secretKey) throws IOException {
324 | try {
325 | final SQSTriggerQueue queue = new SQSTriggerQueue(uuid, nameOrUrl, accessKey, secretKey, 0, 0);
326 |
327 | if (StringUtils.isBlank(queue.getName())) {
328 | return FormValidation.warning("Name or URL of the queue must be set.");
329 | }
330 |
331 | if (StringUtils.isEmpty(queue.getAWSAccessKeyId())) {
332 | return FormValidation.warning("AWS access key ID must be set.");
333 | }
334 |
335 | if (StringUtils.isEmpty(queue.getAWSSecretKey())) {
336 | return FormValidation.warning("AWS secret key must be set.");
337 | }
338 |
339 | final AmazonSQS client = queue.getSQSClient();
340 |
341 | if (client == null) {
342 | return FormValidation.error("Failed to create SQS client");
343 | }
344 |
345 | final String queueName = queue.getName();
346 | final GetQueueUrlResult result = client.getQueueUrl(queueName);
347 |
348 | if (result == null) {
349 | return FormValidation.error("Failed to get SQS client queue URL");
350 | }
351 |
352 | final String url = result.getQueueUrl();
353 | return FormValidation.ok("Access to %s successful\n(%s)", queue.getName(), url);
354 |
355 | } catch (final AmazonServiceException ase) {
356 | return FormValidation.error(ase, ase.getMessage());
357 |
358 | } catch (final RuntimeException ex) {
359 | return FormValidation.error(ex, "Error validating SQS access");
360 | }
361 | }
362 |
363 | private FormValidation validateNumber(final String value, final int min, final int max, final String message) {
364 | try {
365 | if (StringUtils.isBlank(value)) {
366 | return FormValidation.error(message);
367 | }
368 |
369 | final int number = Integer.parseInt(value);
370 |
371 | if (number < min || number > max) {
372 | return FormValidation.error(message);
373 | }
374 |
375 | return FormValidation.ok();
376 |
377 | } catch (final NumberFormatException e) {
378 | return FormValidation.error(message);
379 | }
380 | }
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/src/test/java/io/relution/jenkins/scmsqs/threading/SQSQueueMonitorSchedulerImplTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 M-Way Solutions GmbH
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.relution.jenkins.scmsqs.threading;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
21 | import static org.mockito.Mockito.times;
22 |
23 | import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
24 | import org.junit.Before;
25 | import org.junit.Test;
26 | import org.mockito.Mock;
27 | import org.mockito.Mockito;
28 | import org.mockito.MockitoAnnotations;
29 |
30 | import java.util.concurrent.ExecutorService;
31 |
32 | import io.relution.jenkins.scmsqs.interfaces.SQSFactory;
33 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue;
34 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueListener;
35 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitor;
36 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueMonitorScheduler;
37 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueProvider;
38 | import io.relution.jenkins.scmsqs.model.events.ConfigurationChangedEvent;
39 |
40 |
41 | public class SQSQueueMonitorSchedulerImplTest {
42 |
43 | private static final String UUID_A = "uuid-a";
44 | private static final String UUID_B = "uuid-b";
45 |
46 | @Mock
47 | private ExecutorService executor;
48 |
49 | @Mock
50 | private SQSQueueProvider provider;
51 |
52 | @Mock
53 | private SQSFactory factory;
54 |
55 | @Mock
56 | private SQSQueueMonitor monitorA;
57 |
58 | @Mock
59 | private SQSQueueMonitor monitorB;
60 |
61 | @Mock
62 | private SQSQueueListener listenerA1;
63 |
64 | @Mock
65 | private SQSQueueListener listenerA2;
66 |
67 | @Mock
68 | private SQSQueueListener listenerB1;
69 |
70 | @Mock
71 | private SQSQueue queueA;
72 |
73 | @Mock
74 | private SQSQueue queueB;
75 |
76 | private SQSQueueMonitorScheduler scheduler;
77 |
78 | @Before
79 | public void init() {
80 | MockitoAnnotations.initMocks(this);
81 |
82 | Mockito.when(this.factory.createMonitor(this.executor, this.queueA)).thenReturn(this.monitorA);
83 | Mockito.when(this.factory.createMonitor(this.executor, this.queueB)).thenReturn(this.monitorB);
84 | Mockito.when(this.factory.createMonitor(this.monitorA, this.queueA)).thenReturn(this.monitorA);
85 | Mockito.when(this.factory.createMonitor(this.monitorB, this.queueB)).thenReturn(this.monitorB);
86 |
87 | Mockito.when(this.monitorA.getQueue()).thenReturn(this.queueA);
88 | Mockito.when(this.monitorB.getQueue()).thenReturn(this.queueB);
89 |
90 | Mockito.when(this.listenerA1.getQueueUuid()).thenReturn(UUID_A);
91 | Mockito.when(this.listenerA2.getQueueUuid()).thenReturn(UUID_A);
92 | Mockito.when(this.listenerB1.getQueueUuid()).thenReturn(UUID_B);
93 |
94 | Mockito.when(this.queueA.getUuid()).thenReturn(UUID_A);
95 | Mockito.when(this.queueA.getUrl()).thenReturn("url-a");
96 | Mockito.when(this.queueA.getAWSAccessKeyId()).thenReturn("access-key-a");
97 | Mockito.when(this.queueA.getAWSSecretKey()).thenReturn("secret-key-a");
98 | Mockito.when(this.queueA.getMaxNumberOfMessages()).thenReturn(20);
99 | Mockito.when(this.queueA.getWaitTimeSeconds()).thenReturn(10);
100 |
101 | Mockito.when(this.queueB.getUuid()).thenReturn(UUID_B);
102 | Mockito.when(this.queueB.getUrl()).thenReturn("url-b");
103 | Mockito.when(this.queueB.getAWSAccessKeyId()).thenReturn("access-key-b");
104 | Mockito.when(this.queueB.getAWSSecretKey()).thenReturn("secret-key-b");
105 | Mockito.when(this.queueB.getMaxNumberOfMessages()).thenReturn(20);
106 | Mockito.when(this.queueB.getWaitTimeSeconds()).thenReturn(10);
107 |
108 | Mockito.when(this.provider.getSqsQueue(UUID_A)).thenReturn(this.queueA);
109 | Mockito.when(this.provider.getSqsQueue(UUID_B)).thenReturn(this.queueB);
110 |
111 | this.scheduler = new SQSQueueMonitorSchedulerImpl(this.executor, this.provider, this.factory);
112 | }
113 |
114 | @Test
115 | public void shouldThrowIfRegisterNullListener() {
116 | assertThatThrownBy(new ThrowingCallable() {
117 |
118 | @Override
119 | public void call() throws Throwable {
120 | SQSQueueMonitorSchedulerImplTest.this.scheduler.register(null);
121 | }
122 |
123 | }).isInstanceOf(IllegalArgumentException.class);
124 | }
125 |
126 | @Test
127 | public void shouldNotThrowIfUnregisterNullListener() {
128 | assertThat(this.scheduler.unregister(null)).isFalse();
129 | }
130 |
131 | @Test
132 | public void shouldNotThrowIfUnregisterUnknownListener() {
133 | final SQSQueueListener listener = Mockito.mock(SQSQueueListener.class);
134 | Mockito.when(listener.getQueueUuid()).thenReturn("unknown");
135 |
136 | final boolean result = this.scheduler.unregister(listener);
137 |
138 | assertThat(result).isFalse();
139 | }
140 |
141 | @Test
142 | public void shouldStartMonitorForFirstListener() {
143 | final boolean result = this.scheduler.register(this.listenerA1);
144 |
145 | assertThat(result).isTrue();
146 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_A);
147 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
148 | Mockito.verify(this.monitorA).add(this.listenerA1);
149 | }
150 |
151 | @Test
152 | public void shouldUseSingleMonitorInstancePerQueue() {
153 | this.scheduler.register(this.listenerA1);
154 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_A);
155 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
156 | Mockito.verify(this.monitorA).add(this.listenerA1);
157 |
158 | final boolean result = this.scheduler.register(this.listenerA2);
159 |
160 | assertThat(result).isTrue();
161 | Mockito.verify(this.provider, times(2)).getSqsQueue(UUID_A);
162 | Mockito.verifyNoMoreInteractions(this.factory);
163 | Mockito.verify(this.monitorA).add(this.listenerA2);
164 | }
165 |
166 | @Test
167 | public void shouldUseSeparateMonitorInstanceForEachQueue() {
168 | this.scheduler.register(this.listenerA1);
169 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_A);
170 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
171 | Mockito.verify(this.monitorA).add(this.listenerA1);
172 |
173 | final boolean result = this.scheduler.register(this.listenerB1);
174 |
175 | assertThat(result).isTrue();
176 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_B);
177 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueB);
178 | Mockito.verify(this.monitorB).add(this.listenerB1);
179 | Mockito.verifyNoMoreInteractions(this.monitorA);
180 | }
181 |
182 | @Test
183 | public void shouldReuseMonitorInstaceForListenerOfSameQueue() {
184 | this.scheduler.register(this.listenerA1);
185 | this.scheduler.register(this.listenerB1);
186 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_A);
187 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_B);
188 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
189 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueB);
190 | Mockito.verify(this.monitorA).add(this.listenerA1);
191 | Mockito.verify(this.monitorB).add(this.listenerB1);
192 | Mockito.verifyNoMoreInteractions(this.monitorA);
193 |
194 | final boolean result = this.scheduler.register(this.listenerA2);
195 |
196 | assertThat(result).isTrue();
197 | Mockito.verify(this.provider, times(2)).getSqsQueue(UUID_A);
198 | Mockito.verifyNoMoreInteractions(this.factory);
199 | Mockito.verify(this.monitorA).add(this.listenerA2);
200 | Mockito.verifyNoMoreInteractions(this.monitorB);
201 | }
202 |
203 | @Test
204 | public void shouldNotCreateMonitorForUnknownQueue() {
205 | final SQSQueueListener listener = Mockito.mock(SQSQueueListener.class);
206 | Mockito.when(listener.getQueueUuid()).thenReturn("unknown");
207 |
208 | final boolean result = this.scheduler.register(listener);
209 |
210 | assertThat(result).isFalse();
211 | Mockito.verify(this.provider, times(1)).getSqsQueue("unknown");
212 | Mockito.verifyZeroInteractions(this.factory);
213 | }
214 |
215 | @Test
216 | public void shouldCreateNewMonitorAfterUnregisterLast() {
217 | this.scheduler.register(this.listenerA1);
218 | this.scheduler.unregister(this.listenerA1);
219 | Mockito.verify(this.provider, times(1)).getSqsQueue(UUID_A);
220 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
221 | Mockito.verify(this.monitorA).add(this.listenerA1);
222 | Mockito.verify(this.monitorA).remove(this.listenerA1);
223 |
224 | final boolean result = this.scheduler.register(this.listenerA2);
225 |
226 | assertThat(result).isTrue();
227 | Mockito.verify(this.provider, times(2)).getSqsQueue(UUID_A);
228 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
229 | Mockito.verify(this.monitorA).add(this.listenerA2);
230 | }
231 |
232 | @Test
233 | public void shouldNotCreateNewMonitorIfMoreListenersOnUnregister() {
234 | this.scheduler.register(this.listenerA1);
235 | this.scheduler.register(this.listenerA2);
236 | this.scheduler.unregister(this.listenerA1);
237 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
238 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA1);
239 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA2);
240 | Mockito.verify(this.monitorA, times(1)).remove(this.listenerA1);
241 |
242 | final boolean result = this.scheduler.register(this.listenerA1);
243 |
244 | assertThat(result).isTrue();
245 | Mockito.verifyNoMoreInteractions(this.factory);
246 | Mockito.verify(this.monitorA, times(2)).add(this.listenerA1);
247 | }
248 |
249 | @Test
250 | public void shouldDoNothingOnConfigurationChangedIfUnchanged() {
251 | this.scheduler.register(this.listenerA1);
252 | this.scheduler.register(this.listenerB1);
253 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
254 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueB);
255 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA1);
256 | Mockito.verify(this.monitorB, times(1)).add(this.listenerB1);
257 |
258 | this.scheduler.onConfigurationChanged(new ConfigurationChangedEvent());
259 |
260 | Mockito.verifyNoMoreInteractions(this.factory);
261 | Mockito.verify(this.monitorA).getQueue();
262 | Mockito.verify(this.monitorB).getQueue();
263 | Mockito.verify(this.monitorA).isShutDown();
264 | Mockito.verify(this.monitorB).isShutDown();
265 | Mockito.verifyNoMoreInteractions(this.monitorA);
266 | Mockito.verifyNoMoreInteractions(this.monitorB);
267 | }
268 |
269 | @Test
270 | public void shouldStartNewMonitorOnConfigurationChangedIfChanged() {
271 | final SQSQueue queueA_ = Mockito.mock(SQSQueue.class);
272 | final SQSQueueMonitor monitorA_ = Mockito.mock(SQSQueueMonitor.class);
273 | this.scheduler.register(this.listenerA1);
274 | this.scheduler.register(this.listenerA2);
275 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
276 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA1);
277 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA2);
278 | Mockito.when(this.monitorA.getQueue()).thenReturn(this.queueA);
279 | Mockito.when(this.provider.getSqsQueue(UUID_A)).thenReturn(queueA_);
280 | Mockito.when(this.factory.createMonitor(this.monitorA, queueA_)).thenReturn(monitorA_);
281 |
282 | this.scheduler.onConfigurationChanged(new ConfigurationChangedEvent());
283 |
284 | Mockito.verify(this.factory).createMonitor(this.monitorA, queueA_);
285 | Mockito.verify(this.monitorA).getQueue();
286 | Mockito.verify(this.monitorA).shutDown();
287 | Mockito.verify(this.monitorA).isShutDown();
288 | Mockito.verifyNoMoreInteractions(this.monitorA);
289 | Mockito.verifyNoMoreInteractions(monitorA_);
290 | }
291 |
292 | @Test
293 | public void shouldDoNothingOnConfigurationChangedIfPropertiesEqual() {
294 | final SQSQueue queueA_ = Mockito.mock(SQSQueue.class);
295 | final SQSQueueMonitor monitorA_ = Mockito.mock(SQSQueueMonitor.class);
296 | this.scheduler.register(this.listenerA1);
297 | this.scheduler.register(this.listenerA2);
298 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
299 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA1);
300 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA2);
301 | Mockito.when(this.monitorA.getQueue()).thenReturn(this.queueA);
302 | Mockito.when(this.provider.getSqsQueue(UUID_A)).thenReturn(queueA_);
303 | Mockito.when(this.factory.createMonitor(this.monitorA, queueA_)).thenReturn(monitorA_);
304 | Mockito.when(queueA_.getUuid()).thenReturn(UUID_A);
305 | Mockito.when(queueA_.getUrl()).thenReturn("url-a");
306 | Mockito.when(queueA_.getAWSAccessKeyId()).thenReturn("access-key-a");
307 | Mockito.when(queueA_.getAWSSecretKey()).thenReturn("secret-key-a");
308 | Mockito.when(queueA_.getMaxNumberOfMessages()).thenReturn(20);
309 | Mockito.when(queueA_.getWaitTimeSeconds()).thenReturn(10);
310 |
311 | this.scheduler.onConfigurationChanged(new ConfigurationChangedEvent());
312 |
313 | Mockito.verifyNoMoreInteractions(this.factory);
314 | Mockito.verify(this.monitorA).getQueue();
315 | Mockito.verify(this.monitorA).isShutDown();
316 | Mockito.verifyNoMoreInteractions(this.monitorA);
317 | }
318 |
319 | @Test
320 | public void shouldStopMonitorOnConfigurationChangedIfQueueRemoved() {
321 | this.scheduler.register(this.listenerA1);
322 | this.scheduler.register(this.listenerB1);
323 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueA);
324 | Mockito.verify(this.factory).createMonitor(this.executor, this.queueB);
325 | Mockito.verify(this.monitorA, times(1)).add(this.listenerA1);
326 | Mockito.verify(this.monitorB, times(1)).add(this.listenerB1);
327 | Mockito.when(this.provider.getSqsQueue(UUID_B)).thenReturn(null);
328 |
329 | this.scheduler.onConfigurationChanged(new ConfigurationChangedEvent());
330 |
331 | Mockito.verifyNoMoreInteractions(this.factory);
332 | Mockito.verify(this.monitorA).getQueue();
333 | Mockito.verify(this.monitorA).isShutDown();
334 | Mockito.verify(this.monitorB).shutDown();
335 | Mockito.verifyNoMoreInteractions(this.monitorA);
336 | Mockito.verifyNoMoreInteractions(this.monitorB);
337 | }
338 | }
339 |
--------------------------------------------------------------------------------