├── .travis.yml ├── doc └── images │ ├── jenkins-build-triggers.png │ ├── amazon-create-topic-for-queue.png │ ├── plugin-queue-configuration-empty.png │ └── plugin-queue-configuration-success.png ├── src ├── main │ ├── resources │ │ ├── io │ │ │ └── relution │ │ │ │ └── jenkins │ │ │ │ └── scmsqs │ │ │ │ ├── SQSTrigger │ │ │ │ ├── help-queueUuid.html │ │ │ │ ├── help-queueUuid_de.html │ │ │ │ ├── config_de.properties │ │ │ │ ├── global_de.properties │ │ │ │ ├── help.html │ │ │ │ ├── help_de.html │ │ │ │ ├── config.jelly │ │ │ │ └── global.jelly │ │ │ │ ├── SQSTriggerQueue │ │ │ │ ├── help-secretKey.html │ │ │ │ ├── help-accessKey.html │ │ │ │ ├── help-accessKey_de.html │ │ │ │ ├── help-secretKey_de.html │ │ │ │ ├── config_de.properties │ │ │ │ ├── help-nameOrUrl.html │ │ │ │ ├── help-nameOrUrl_de.html │ │ │ │ ├── help-waitTimeSeconds.html │ │ │ │ ├── help-waitTimeSeconds_de.html │ │ │ │ ├── help-maxNumberOfMessages.html │ │ │ │ ├── help-maxNumberOfMessages_de.html │ │ │ │ └── config.jelly │ │ │ │ └── i18n │ │ │ │ ├── sqstrigger │ │ │ │ ├── Messages_de.properties │ │ │ │ └── Messages.properties │ │ │ │ └── sqstriggerqueue │ │ │ │ ├── Messages_de.properties │ │ │ │ └── Messages.properties │ │ └── index.jelly │ └── java │ │ └── io │ │ └── relution │ │ └── jenkins │ │ └── scmsqs │ │ ├── model │ │ ├── events │ │ │ ├── ConfigurationChangedEvent.java │ │ │ └── EventBroker.java │ │ ├── SQSQueueProviderImpl.java │ │ ├── entities │ │ │ └── codecommit │ │ │ │ ├── CodeCommit.java │ │ │ │ ├── Reference.java │ │ │ │ ├── Records.java │ │ │ │ ├── CodeCommitEvent.java │ │ │ │ ├── MessageBody.java │ │ │ │ └── Record.java │ │ ├── constants │ │ │ └── ErrorCode.java │ │ ├── CodeCommitMessageParser.java │ │ └── EventTriggerMatcherImpl.java │ │ ├── net │ │ ├── SQSChannel.java │ │ ├── RequestFactory.java │ │ ├── RequestFactoryImpl.java │ │ └── SQSChannelImpl.java │ │ ├── factories │ │ ├── ThreadFactoryImpl.java │ │ ├── MessageParserFactoryImpl.java │ │ ├── ExecutorFactoryImpl.java │ │ └── SQSFactoryImpl.java │ │ ├── interfaces │ │ ├── ExecutorFactory.java │ │ ├── MessageParser.java │ │ ├── EventTriggerMatcher.java │ │ ├── SQSQueueProvider.java │ │ ├── MessageParserFactory.java │ │ ├── SQSQueueListener.java │ │ ├── ExecutorProvider.java │ │ ├── Event.java │ │ ├── SQSQueueMonitorScheduler.java │ │ ├── SQSQueue.java │ │ ├── SQSFactory.java │ │ └── SQSQueueMonitor.java │ │ ├── threading │ │ ├── ExecutorProviderImpl.java │ │ ├── SQSQueueMonitorSchedulerImpl.java │ │ └── SQSQueueMonitorImpl.java │ │ ├── util │ │ ├── ErrorType.java │ │ └── ThrowIf.java │ │ ├── logging │ │ └── Log.java │ │ ├── SQSTriggerBuilder.java │ │ ├── Context.java │ │ ├── SQSTrigger.java │ │ └── SQSTriggerQueue.java └── test │ └── java │ └── io │ └── relution │ └── jenkins │ └── scmsqs │ ├── SQSTriggerQueueDescriptorImplTest.java │ ├── net │ └── SQSQueueImplTest.java │ ├── SQSTriggerQueueTest.java │ └── threading │ ├── SQSQueueMonitorImplTest.java │ └── SQSQueueMonitorSchedulerImplTest.java ├── .gitignore ├── .project ├── publish.sh ├── pom.xml ├── LICENSE └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk7 5 | - oraclejdk7 6 | - oraclejdk8 7 | -------------------------------------------------------------------------------- /doc/images/jenkins-build-triggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/doc/images/jenkins-build-triggers.png -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/help-queueUuid.html: -------------------------------------------------------------------------------- 1 |
2 | The Amazon SQS queue to monitor for messages. 3 |
-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-secretKey.html: -------------------------------------------------------------------------------- 1 |
2 | The AWS secret key for the message queue. 3 |
-------------------------------------------------------------------------------- /doc/images/amazon-create-topic-for-queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/doc/images/amazon-create-topic-for-queue.png -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-accessKey.html: -------------------------------------------------------------------------------- 1 |
2 | The AWS access key ID for the message queue. 3 |
-------------------------------------------------------------------------------- /doc/images/plugin-queue-configuration-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/doc/images/plugin-queue-configuration-empty.png -------------------------------------------------------------------------------- /doc/images/plugin-queue-configuration-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/doc/images/plugin-queue-configuration-success.png -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/help-queueUuid_de.html: -------------------------------------------------------------------------------- 1 |
2 | Amazon SQS Queue die auf Nachrichten überwacht werden soll. 3 |
-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-accessKey_de.html: -------------------------------------------------------------------------------- 1 |
2 | AWS Access Key ID (Zugangsschlüssel) für eine Nachrichtenwarteschlange. 3 |
-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-secretKey_de.html: -------------------------------------------------------------------------------- 1 |
2 | AWS Secret Key (geheimer Zugangsschlüssel) für die Nachrichtenwarteschlange. 3 |
-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/config_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/config_de.properties -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/global_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/global_de.properties -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/config_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/config_de.properties -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/i18n/sqstrigger/Messages_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/src/main/resources/io/relution/jenkins/scmsqs/i18n/sqstrigger/Messages_de.properties -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/i18n/sqstriggerqueue/Messages_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/scm-sqs-plugin/HEAD/src/main/resources/io/relution/jenkins/scmsqs/i18n/sqstriggerqueue/Messages_de.properties -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | build/ 3 | gen/ 4 | target/ 5 | work/ 6 | pom.xml.tag 7 | pom.xml.releaseBackup 8 | pom.xml.versionsBackup 9 | pom.xml.next 10 | release.properties 11 | local.properties 12 | dependency-reduced-pom.xml 13 | buildNumber.properties 14 | .DS_Store 15 | .mvn/timing.properties 16 | .settings/ 17 | -------------------------------------------------------------------------------- /src/main/java/io/relution/jenkins/scmsqs/model/events/ConfigurationChangedEvent.java: -------------------------------------------------------------------------------- 1 | 2 | package io.relution.jenkins.scmsqs.model.events; 3 | 4 | import io.relution.jenkins.scmsqs.SQSTrigger; 5 | 6 | 7 | /** 8 | * Event that is sent by the {@link SQSTrigger.DescriptorImpl} when its configuration is changed. 9 | */ 10 | public class ConfigurationChangedEvent {} 11 | -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/i18n/sqstrigger/Messages.properties: -------------------------------------------------------------------------------- 1 | displayName=Trigger build when a message is published to an Amazon SQS queue 2 | 3 | errorQueueUnavailable=No queues have been configured. Add at least one queue to the global \ 4 | Jenkins configuration. 5 | 6 | errorQueueUuidUnknown=The previously selected queue no longer exists. Select a new queue and \ 7 | save the configuration. 8 | 9 | infoQueueDefault=Selected first available queue. Verify the selection and save the configuration. 10 | -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/i18n/sqstriggerqueue/Messages.properties: -------------------------------------------------------------------------------- 1 | displayName=An Amazon SQS queue configuration 2 | errorWaitTimeSeconds=Wait Time must be a number between 1 and 20 3 | errorMaxNumberOfMessages=Max. number of messages must be a number between 1 and 10 4 | infoUrlSqs=You can use \"%s\" instead of the full URL 5 | warningUrl=Name or URL of an SQS queue is required 6 | errorUrlCodecommit=This is a CodeCommit URL, please provide a queue name or SQS URL 7 | errorUrlUnknown=This is not an SQS URL, please provide a queue name or SQS URL -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-nameOrUrl.html: -------------------------------------------------------------------------------- 1 |
2 | The name or URL of a message queue that exists on the Amazon Simple Queue Service (SQS). The 3 | plugin will trigger builds whenever a message is posted to this queue. 4 |

5 | The queue will not be actively monitored until you have configured at least one job to listen to 6 | messages from this queue. See the build trigger configuration of individual jobs. 7 |

8 | Further documentation is available in the 9 | SCM SQS Plugin repository on GitHub. 10 |

-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-nameOrUrl_de.html: -------------------------------------------------------------------------------- 1 |
2 | Name oder URL einer Nachrichtenwarteschlange die im Amazon Simple Queue Service (SQS) angelegt 3 | wurde. Das Plugin startet Builds wenn Nachrichten in dieser Queue abgelegt werden. 4 |

5 | Die Queue wird nicht aktiv überwacht, bis mindestens ein Job so konfiguriert wurde, dass er auf 6 | Nachrichten aus dieser Queue horcht. Siehe Build Trigger Konfiguration von Jobs. 7 |

8 | Weiterführende Dokumentation ist im 9 | SCM SQS Plugin Repository auf GitHub 10 | verfügbar. 11 |

-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/help.html: -------------------------------------------------------------------------------- 1 |
2 | Configure Jenkins to monitor an Amazon SQS queue for messages. Jenkins will start a new build 3 | if a message is posted to the queue and the message's content matches this job's repository and 4 | branch. The repository should be set up to post messages to the queue whenever changes are made. 5 |

6 | Available queues can be configured in the System Configuration of 7 | Jenkins. 8 |

9 | Further documentation is available in the 10 | SCM SQS Plugin repository on GitHub. 11 |

-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/help_de.html: -------------------------------------------------------------------------------- 1 |
2 | Konfiguriert Jenkins dafür eine Amazon SQS Queue auf Nachrichten zu überwachen. Jenkins wird ein 3 | neues Build starten, wenn eine Nachricht in der Queue abgelegt wurde und der Inhalt der Nachricht 4 | mit dem Repository und dem Branch dieses Jobs übereinstimmt. Das Repository sollte so konfiguriert 5 | sein, dass es bei Änderungen Nachrichten in der Queue ablegt. 6 |

7 | Verfügbare Queues können in der System Konfiguration von Jenkins 8 | konfiguriert werden. 9 |

10 | Weiterführende Dokumentation ist im 11 | SCM SQS Plugin Repository auf GitHub 12 | verfügbar. 13 |

-------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | scm-sqs 4 | TODO. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.m2e.core.maven2Nature 21 | org.eclipse.jdt.core.javanature 22 | org.eclipse.jdt.groovy.core.groovyNature 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-waitTimeSeconds.html: -------------------------------------------------------------------------------- 1 |
2 | The time, in seconds, to wait for a message to arrive in the queue (long polling). Values can be 3 | from 1 to 20. Default is 20. 4 |

5 | Larger values reduce the number of requests that need to be sent. It is recommended to use the 6 | maximum value. Reduce the value only if you experience issues and are certain that a shorter 7 | request timeout fixes them. 8 |

9 | See also: 10 | 11 | Amazon SQS Long Polling 12 |
13 | See also: 14 | Amazon SQS – Long 15 | Polling and Request Batching / Client-Side Buffering 16 |

-------------------------------------------------------------------------------- /src/main/java/io/relution/jenkins/scmsqs/model/SQSQueueProviderImpl.java: -------------------------------------------------------------------------------- 1 | 2 | package io.relution.jenkins.scmsqs.model; 3 | 4 | import java.util.List; 5 | 6 | import io.relution.jenkins.scmsqs.SQSTrigger; 7 | import io.relution.jenkins.scmsqs.interfaces.SQSQueue; 8 | import io.relution.jenkins.scmsqs.interfaces.SQSQueueProvider; 9 | 10 | 11 | public class SQSQueueProviderImpl implements SQSQueueProvider { 12 | 13 | @Override 14 | public List getSqsQueues() { 15 | final SQSTrigger.DescriptorImpl descriptor = SQSTrigger.DescriptorImpl.get(); 16 | return descriptor.getSqsQueues(); 17 | } 18 | 19 | @Override 20 | public SQSQueue getSqsQueue(final String uuid) { 21 | final SQSTrigger.DescriptorImpl descriptor = SQSTrigger.DescriptorImpl.get(); 22 | return descriptor.getSqsQueue(uuid); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-waitTimeSeconds_de.html: -------------------------------------------------------------------------------- 1 |
2 | Zeit, in Sekunden, die auf die Ankunft von Nachrichten in der Queue gewartet werden soll (Long 3 | Polling). Zulässige Werte gehen von 1 bis 20. Standard sind 20. 4 |

5 | Größere Werte reduzieren die Anzahl an Requests die gesendet werden müssen. Es wird empfohlen den 6 | maximalen Wert zu verwenden. Reduzieren Sie diesen Wert nur, wenn es zu Problemen kommt und Sie 7 | sicher sind, dass diese durch einen kleineren Wert behoben werden. 8 |

9 | Siehe auch (Englisch):
10 | 11 | Amazon SQS Long Polling 12 |
13 | See also: 14 | Amazon SQS – Long 15 | Polling and Request Batching / Client-Side Buffering 16 |

-------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 |
19 | This plugin triggers builds on events from CodeCommit that are published via Amazon Web 20 | Services Simple Queue Service (AWS SQS). 21 |
-------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-maxNumberOfMessages.html: -------------------------------------------------------------------------------- 1 |
2 | The maximum number of messages to receive per request. Amazon SQS never returns more messages than 3 | this value but may return fewer. Values can be from 1 to 10. Default is 10. 4 |

5 | All of the messages are not necessarily returned. 6 |

7 | Larger values can reduce the number of requests that need to be sent if multiple messages are 8 | available. It is recommended to use the maximum value. Reduce the value only if you experience 9 | issues and are certain that a smaller number fixes them. 10 |

11 | See also: 12 | 13 | API ReceiveMessage 14 |
15 | See also: 16 | Amazon SQS – Long 17 | Polling and Request Batching / Client-Side Buffering 18 |

-------------------------------------------------------------------------------- /src/main/java/io/relution/jenkins/scmsqs/net/SQSChannel.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.model.Message; 20 | 21 | import java.util.List; 22 | 23 | 24 | public interface SQSChannel { 25 | 26 | List getMessages(); 27 | 28 | void deleteMessages(List messages); 29 | 30 | String getQueueUuid(); 31 | } -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTrigger/config.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/resources/io/relution/jenkins/scmsqs/SQSTriggerQueue/help-maxNumberOfMessages_de.html: -------------------------------------------------------------------------------- 1 |
2 | Die maximale Anzahl an Nachrichten die pro Request empfangen werden soll. Amazon SQS liefert 3 | nie mehr Nachrichten als diesen Wert zurück, kann aber weniger zurückliefern. Zulässige Werte 4 | gehen von 1 bis 10. Standard sind 10. 5 |

6 | Es werden möglicherweise nicht immer alle Nachrichten zurückgeliefert. 7 |

8 | Größere Werte reduzieren die Anzahl an Requests die gesendet werden müssen, wenn mehrere Nachrichten 9 | verfügbar sind. Es wird empfohlen den maximalen Wert zu verwenden. Reduzieren Sie diesen Wert nur, 10 | wenn es zu Problemen kommt und Sie sicher sind, dass diese durch einen kleineren Wert behoben 11 | werden. 12 |

13 | Siehe auch: 14 | 15 | API ReceiveMessage 16 |
17 | Siehe auch: 18 | Amazon SQS – Long 19 | Polling and Request Batching / Client-Side Buffering 20 |

-------------------------------------------------------------------------------- /src/main/java/io/relution/jenkins/scmsqs/factories/ThreadFactoryImpl.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 java.util.concurrent.ThreadFactory; 20 | 21 | 22 | public class ThreadFactoryImpl implements ThreadFactory { 23 | 24 | @Override 25 | public Thread newThread(final Runnable r) { 26 | final Thread thread = new Thread(r); 27 | thread.setPriority(Thread.MIN_PRIORITY); 28 | 29 | return thread; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/relution/jenkins/scmsqs/model/entities/codecommit/CodeCommit.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.List; 23 | 24 | 25 | public class CodeCommit { 26 | 27 | @Expose 28 | @SerializedName("references") 29 | List references; 30 | 31 | public List 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 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 |
33 | 36 | 37 |
38 | 42 | 47 |
48 |
49 |
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 | [![Build Status](https://travis-ci.org/jenkinsci/scm-sqs-plugin.svg?branch=master)](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 | ![Empty Jenkins configuration](doc/images/plugin-queue-configuration-empty.png) 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 | ![Jenkins configuration test](doc/images/plugin-queue-configuration-success.png) 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 | ![Topic configuration](doc/images/amazon-create-topic-for-queue.png) 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 | ![Build trigger configuration](doc/images/jenkins-build-triggers.png) 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 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 | --------------------------------------------------------------------------------