From 3397082916d5e8b9ec6d7992a8f97e8f8040b939 Mon Sep 17 00:00:00 2001 From: Rodrigo Fernandes Date: Sat, 6 Aug 2016 12:42:53 +0100 Subject: [PATCH] Refactor code to support Elastic Beanstalk deploys --- .editorconfig | 16 + .idea/.name | 2 +- .idea/codeStyleSettings.xml | 17 - .idea/copyright/JetBrains_open_sourced.xml | 9 - .idea/copyright/profiles_settings.xml | 12 +- .idea/vcs.xml | 6 - .travis.yml | 1 - CONTRIBUTING.md | 59 +++ CREDITS.md | 19 + LICENSE.md | 13 + LICENSE.txt | 68 ---- README.md | 37 +- .../buildServer/util/PathMappings.java | 110 ----- .../buildServer/util/amazon/AWSClients.java | 36 +- .../util/amazon/AWSCommonParams.java | 59 ++- .../buildServer/util/amazon/AWSException.java | 43 +- .../buildServer/util/amazon/AWSRegions.java | 11 +- .../buildServer/util/amazon/AWSUtil.java | 34 -- .../src/test/java/AWSCommonParamsTest.java | 22 +- .../codedeploy/ApplicationRevision.java | 182 --------- .../codedeploy/LoggingDeploymentListener.java | 250 ------------ .../ApplicationRevisionTest.java | 379 ------------------ .../LoggingDeploymentListenerTest.java | 272 ------------- .../runner/codedeploy/AWSClient.java | 327 --------------- .../codedeploy/CodeDeployConstants.java | 105 ----- .../codedeploy/ParametersValidator.java | 183 --------- .../runner/codedeploy/CodeDeployUtilTest.java | 70 ---- .../codedeploy/ParametersValidatorTest.java | 148 ------- .../editCodeDeployParams.jsp | 157 -------- .../buildServerResources/paramsConstants.jspf | 59 --- .../build.gradle | 2 +- .../ElasticBeanstalkRunner.java | 79 +--- .../LoggingDeploymentListener.java | 183 +++++++++ .../SyncBuildProcessAdapter.java | 12 +- ...ild-agent-plugin-aws-codedeploy-plugin.xml | 2 +- .../LoggingDeploymentListenerTest.java | 185 +++++++++ .../LoggingTestCase.java | 7 +- .../teamcity-plugin.xml | 0 .../build.gradle | 0 .../runner/elasticbeanstalk/AWSClient.java | 241 +++++++++++ .../ElasticBeanstalkConstants.java | 58 +++ .../ElasticBeanstalkUtil.java | 62 +-- .../elasticbeanstalk/ParametersValidator.java | 126 ++++++ .../ParametersValidatorTest.java | 87 ++++ .../build.gradle | 2 +- .../ElasticBeanstalkBuildProblemTypes.java | 39 +- .../ElasticBeanstalkRunType.java | 28 +- ...ld-server-plugin-aws-codedeploy-plugin.xml | 6 +- .../editElasticBeanstalkParams.jsp | 91 +++++ .../buildServerResources/paramsConstants.jspf | 43 ++ .../viewElasticBeanstalkParams.jsp | 17 +- build.gradle | 6 +- build/build.gradle | 12 +- settings.gradle | 9 +- teamcity-plugin.xml | 12 +- 55 files changed, 1324 insertions(+), 2691 deletions(-) create mode 100644 .editorconfig delete mode 100644 .idea/codeStyleSettings.xml delete mode 100755 .idea/copyright/JetBrains_open_sourced.xml mode change 100755 => 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/vcs.xml create mode 100644 CONTRIBUTING.md create mode 100644 CREDITS.md create mode 100644 LICENSE.md delete mode 100644 LICENSE.txt delete mode 100644 amazon-util/src/main/java/jetbrains/buildServer/util/PathMappings.java delete mode 100644 amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSUtil.java delete mode 100644 aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/ApplicationRevision.java delete mode 100644 aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/LoggingDeploymentListener.java delete mode 100644 aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/ApplicationRevisionTest.java delete mode 100644 aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingDeploymentListenerTest.java delete mode 100644 aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/AWSClient.java delete mode 100644 aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployConstants.java delete mode 100644 aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/ParametersValidator.java delete mode 100644 aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtilTest.java delete mode 100644 aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/ParametersValidatorTest.java delete mode 100644 aws-codedeploy-server/src/main/resources/buildServerResources/editCodeDeployParams.jsp delete mode 100644 aws-codedeploy-server/src/main/resources/buildServerResources/paramsConstants.jspf rename {aws-codedeploy-agent => aws-elasticbeanstalk-agent}/build.gradle (93%) rename aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunner.java => aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunner.java (51%) create mode 100644 aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/LoggingDeploymentListener.java rename {aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy => aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk}/SyncBuildProcessAdapter.java (91%) rename {aws-codedeploy-agent => aws-elasticbeanstalk-agent}/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml (80%) create mode 100644 aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingDeploymentListenerTest.java rename {aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy => aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk}/LoggingTestCase.java (95%) rename {aws-codedeploy-agent => aws-elasticbeanstalk-agent}/teamcity-plugin.xml (100%) rename {aws-codedeploy-common => aws-elasticbeanstalk-common}/build.gradle (100%) create mode 100644 aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/AWSClient.java create mode 100644 aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkConstants.java rename aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtil.java => aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkUtil.java (50%) create mode 100644 aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidator.java create mode 100644 aws-elasticbeanstalk-common/src/test/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidatorTest.java rename {aws-codedeploy-server => aws-elasticbeanstalk-server}/build.gradle (93%) rename aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployBuildProblemTypes.java => aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkBuildProblemTypes.java (64%) rename aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunType.java => aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunType.java (82%) rename {aws-codedeploy-server => aws-elasticbeanstalk-server}/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml (78%) create mode 100644 aws-elasticbeanstalk-server/src/main/resources/buildServerResources/editElasticBeanstalkParams.jsp create mode 100644 aws-elasticbeanstalk-server/src/main/resources/buildServerResources/paramsConstants.jspf rename aws-codedeploy-server/src/main/resources/buildServerResources/viewCodeDeployParams.jsp => aws-elasticbeanstalk-server/src/main/resources/buildServerResources/viewElasticBeanstalkParams.jsp (74%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bc2c8a1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 +max_line_length = 120 + +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/.idea/.name b/.idea/.name index e3d38f0..d101259 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -aws-codedeploy-plugin \ No newline at end of file +aws-elasticbeanstalk-plugin \ No newline at end of file diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 2b94552..0000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/copyright/JetBrains_open_sourced.xml b/.idea/copyright/JetBrains_open_sourced.xml deleted file mode 100755 index ea69512..0000000 --- a/.idea/copyright/JetBrains_open_sourced.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml old mode 100755 new mode 100644 index a0ef6b2..e7bedf3 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,13 +1,3 @@ - - - - - - - - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f4db0ef..9bcf999 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ - language: java jdk: - oraclejdk8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..106cff3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +## How to contribute to teamcity-aws-elasticbeanstalk-plugin + +### Main rules + +* Before you open a ticket or send a pull request, [search](https://github.com/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. + +* If you're proposing a new feature, make sure you create an issue to let other contributors know what you are working on. + +* Before sending a pull request make sure your code is tested. + +* Before sending a pull request for a feature, be sure to run tests with `gradle test`. + +* Use the same coding style as the rest of the codebase. + +* Use `git rebase` (not `git merge`) to sync your work from time to time with the master branch. + +* After creating your pull request make sure the build is passing in [Travis](https://travis-ci.org/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin). + +### Commit Style + +Writing good commit logs is important. A commit log should describe what changed and why. +Follow these guidelines when writing one: + +1. The first line should be 50 characters or less and contain a short + description of the change prefixed with the name of the changed + subsystem (e.g. "net: add localAddress and localPort to Socket"). +2. Keep the second line blank. +3. Wrap all other lines at 72 columns. + +A good commit log can look something like this: + +``` +subsystem: explaining the commit in one line + +Body of commit message is a few lines of text, explaining things +in more detail, possibly giving some background about the issue +being fixed, etc. etc. + +The body of the commit message can be several paragraphs, and +please do proper word-wrap and keep columns shorter than about +72 characters or so. That way `git log` will show things +nicely even when it is indented. +``` + +### Developer's Certificate of Origin 1.0 + +By making a contribution to this project, I certify that: + +* (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license indicated + in the file; or +* (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under the + same open source license (unless I am permitted to submit under a + different license), as indicated in the file; or +* (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified it. diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..9663d81 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,19 @@ +# Credits + +This is the list of all the kind people that have contributed to this project, +directly or before it was forked from [teamcity-aws-codedeploy-plugin](https://github.com/JetBrains/teamcity-aws-codedeploy-plugin). + +This list is ordered by first contribution. + +Thanks, +@rtfpessoa + +---------- + +vbedrosova, [@vbedrosova](https://github.com/vbedrosova) + +Rod MacKenzie, [@rodm](https://github.com/rodm) + +Vladislav Rassokhin, [@VladRassokhin](https://github.com/VladRassokhin) + +Rodrigo Fernandes, [@rtfpessoa](https://github.com/rtfpessoa) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..274c734 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,13 @@ +Copyright 2016 rtfpessoa + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index a9389a3..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,68 +0,0 @@ -Apache License, Version 2.0 -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and -You must cause any modified files to carry prominent notices stating that You changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.md b/README.md index e9da82a..76e6ca6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -teamcity-aws-codedeploy-plugin -============================== +# Teamcity AWS Elastic Beanstalk Plugin (teamcity-aws-elasticbeanstalk-plugin) + +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/a88f10539dbd47b588cf444b57346d5b)](https://www.codacy.com/app/Codacy/teamcity-aws-elasticbeanstalk-plugin?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/teamcity-aws-elasticbeanstalk-plugin&utm_campaign=Badge_Grade) +[![Travis](https://travis-ci.org/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin.svg?branch=master)](https://travis-ci.org/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin) +[![Dependency Status](https://dependencyci.com/github/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin/badge)](https://dependencyci.com/github/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin) + +teamcity-aws-elasticbeanstalk-plugin is a **VERY EARLY STAGE** Teamcity plugin that allows deployments +to Elastic Beanstalk. + +This plugin started from the code in [teamcity-aws-codedeploy-plugin](https://github.com/JetBrains/teamcity-aws-codedeploy-plugin) +and was adapted for Elastic Beanstalk and slightly ajusted. + +## Features + +* S3 bundle deployment to Elastic Beanstalk + +## Release + +* 1.0.0-alpha1 [aws-elasticbeanstalk-plugin.zip](https://github.com/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin/releases/download/1.0.0-alpha1/aws-elasticbeanstalk-plugin.zip) + +## Contributions + +This is a developer friendly project, all the contributions are welcome. +To contribute just send a pull request with your changes following the guidelines described in `CONTRIBUTING.md`. +I will try to review them as soon as possible. + +## License + +Copyright 2016 Rodrigo Fernandes. Licensed under the Apache License, Version 2.0 (the "License"). + +## Thanks + +This project started from the codebase in [teamcity-aws-codedeploy-plugin](https://github.com/JetBrains/teamcity-aws-codedeploy-plugin). + +--- diff --git a/amazon-util/src/main/java/jetbrains/buildServer/util/PathMappings.java b/amazon-util/src/main/java/jetbrains/buildServer/util/PathMappings.java deleted file mode 100644 index e4a3f16..0000000 --- a/amazon-util/src/main/java/jetbrains/buildServer/util/PathMappings.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.util; - -import jetbrains.buildServer.util.pathMatcher.AntPatternFileCollector; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author vbedrosova - */ -public class PathMappings { - @NotNull - private final File myBaseDir; - @NotNull - private final Map myPathMappings; - - public PathMappings(@NotNull File baseDir, @NotNull Map pathMappings) { - myBaseDir = baseDir; - myPathMappings = pathMappings; - } - - @NotNull - public List collectFiles() { - return doCollectFiles(myPathMappings.keySet()); - } - - @NotNull - private List doCollectFiles(@NotNull Set paths) { - return AntPatternFileCollector.scanDir(myBaseDir, CollectionsUtil.toStringArray(paths), new AntPatternFileCollector.ScanOption[]{AntPatternFileCollector.ScanOption.NOT_FOLLOW_SYMLINK_DIRS, AntPatternFileCollector.ScanOption.INCLUDE_ALL_IF_NO_RULES}); - } - - private boolean matches(@NotNull String rule, @NotNull File file) { - return doCollectFiles(Collections.singleton(rule)).contains(file); - } - - @Nullable - public String mapPath(@NotNull File f) { - String relativePath = FileUtil.getRelativePath(myBaseDir, f); - - if (relativePath == null) return null; - - relativePath = FileUtil.toSystemIndependentName(relativePath); - - String result = null; - for (Map.Entry m : myPathMappings.entrySet()) { - if (m.getKey().startsWith("-:")) continue; - - final String from = m.getKey().startsWith("+:") ? m.getKey().substring(2) : m.getKey(); - if (relativePath.equals(from)) return doMap(f.getName(), m.getValue()); - - if (relativePath.startsWith(from)) { - result = doMap(relativePath.substring(StringUtil.commonPrefix(relativePath, from).length()), m.getValue()); - continue; - } - - if (isWildcard(from) && matches(from, f)) { - final String withoutWildcards = removeWildcards(from); - result = doMap( - StringUtil.isEmpty(withoutWildcards) ? - relativePath : - relativePath.substring(relativePath.lastIndexOf(withoutWildcards) + withoutWildcards.length()), - m.getValue()); - } - } - return result == null ? relativePath : result; - } - - @NotNull - private String doMap(@NotNull String path, @NotNull String dest) { - return (StringUtil.isEmpty(dest) ? StringUtil.EMPTY : dest + "/") + path; - } - - @NotNull - private String removeWildcards(@NotNull String path) { - final int lastMark = Math.max(path.lastIndexOf('*'), path.lastIndexOf('?')); - if (lastMark < 0) return path; - - int slash = path.indexOf('/', lastMark); - if (slash < 0 || path.length() - slash < 2) { - slash = path.lastIndexOf('/', lastMark); - return slash > 0 ? removeWildcards(path.substring(0, slash + 1)) : StringUtil.EMPTY; - } - return path.substring(slash); - } - - public static boolean isWildcard(@NotNull String path) { - return path.contains("*") || path.contains("?"); - } -} diff --git a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSClients.java b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSClients.java index 39808a7..aaef5bf 100644 --- a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSClients.java +++ b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSClients.java @@ -23,9 +23,7 @@ import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.regions.Region; -import com.amazonaws.services.codedeploy.AmazonCodeDeployClient; -import com.amazonaws.services.codepipeline.AWSCodePipelineClient; -import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalkClient; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; import com.amazonaws.services.securitytoken.model.Credentials; @@ -34,14 +32,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * @author vbedrosova - */ public class AWSClients { - @Nullable private final AWSCredentials myCredentials; - @NotNull private final Region myRegion; - @NotNull private final ClientConfiguration myClientConfiguration; + @Nullable + private final AWSCredentials myCredentials; + @NotNull + private final Region myRegion; + @NotNull + private final ClientConfiguration myClientConfiguration; private AWSClients(@Nullable AWSCredentials credentials, @NotNull String region) { myCredentials = credentials; @@ -58,29 +56,15 @@ public static AWSClients fromExistingCredentials(@NotNull AWSCredentials credent public static AWSClients fromDefaultCredentialProviderChain(@NotNull String region) { return new AWSClients(null, region); } + @NotNull public static AWSClients fromBasicCredentials(@NotNull String accessKeyId, @NotNull String secretAccessKey, @NotNull String region) { return fromExistingCredentials(new BasicAWSCredentials(accessKeyId, secretAccessKey), region); } @NotNull - public static AWSClients fromBasicSessionCredentials(@NotNull String accessKeyId, @NotNull String secretAccessKey, @NotNull String sessionToken, @NotNull String region) { - return fromExistingCredentials(new BasicSessionCredentials(accessKeyId, secretAccessKey, sessionToken), region); - } - - @NotNull - public AmazonS3Client createS3Client() { - return withRegion(myCredentials == null ? new AmazonS3Client(myClientConfiguration) : new AmazonS3Client(myCredentials, myClientConfiguration)); - } - - @NotNull - public AmazonCodeDeployClient createCodeDeployClient() { - return withRegion(myCredentials == null ? new AmazonCodeDeployClient(myClientConfiguration) : new AmazonCodeDeployClient(myCredentials, myClientConfiguration)); - } - - @NotNull - public AWSCodePipelineClient createCodePipeLineClient() { - return withRegion(myCredentials == null ? new AWSCodePipelineClient(myClientConfiguration) : new AWSCodePipelineClient(myCredentials, myClientConfiguration)); + public AWSElasticBeanstalkClient createElasticBeanstalkClient() { + return withRegion(myCredentials == null ? new AWSElasticBeanstalkClient(myClientConfiguration) : new AWSElasticBeanstalkClient(myCredentials, myClientConfiguration)); } @NotNull diff --git a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSCommonParams.java b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSCommonParams.java index 20d5558..cb9b890 100644 --- a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSCommonParams.java +++ b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSCommonParams.java @@ -29,41 +29,38 @@ import static jetbrains.buildServer.util.amazon.AWSClients.*; -/** - * @author vbedrosova - */ public final class AWSCommonParams { - // "codedeploy_" prefix is for backward compatibility + // "elasticbeanstalk_" prefix is for backward compatibility - public static final String REGION_NAME_PARAM = "codedeploy_region_name"; + public static final String REGION_NAME_PARAM = "elasticbeanstalk_region_name"; public static final String REGION_NAME_LABEL = "AWS region"; - public static final String CREDENTIALS_TYPE_PARAM = "codedeploy_credentials_type"; + public static final String CREDENTIALS_TYPE_PARAM = "elasticbeanstalk_credentials_type"; public static final String CREDENTIALS_TYPE_LABEL = "Credentials type"; - public static final String TEMP_CREDENTIALS_OPTION = "codedeploy_temp_credentials"; + public static final String TEMP_CREDENTIALS_OPTION = "elasticbeanstalk_temp_credentials"; public static final String TEMP_CREDENTIALS_LABEL = "Temporary credentials"; - public static final String ACCESS_KEYS_OPTION = "codedeploy_access_keys"; + public static final String ACCESS_KEYS_OPTION = "elasticbeanstalk_access_keys"; public static final String ACCESS_KEYS_LABEL = "Access keys"; public static final String USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN_PARAM = "use_default_credential_provider_chain"; public static final String USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN_LABEL = "Use default credential provider chain"; - public static final String ACCESS_KEY_ID_PARAM = "codedeploy_access_key_id"; + public static final String ACCESS_KEY_ID_PARAM = "elasticbeanstalk_access_key_id"; public static final String ACCESS_KEY_ID_LABEL = "Access key ID"; - public static final String SECURE_SECRET_ACCESS_KEY_PARAM = "secure:codedeploy_secret_access_key"; - public static final String SECRET_ACCESS_KEY_PARAM = "codedeploy_secret_access_key"; + public static final String SECURE_SECRET_ACCESS_KEY_PARAM = "secure:elasticbeanstalk_secret_access_key"; + public static final String SECRET_ACCESS_KEY_PARAM = "elasticbeanstalk_secret_access_key"; public static final String SECRET_ACCESS_KEY_LABEL = "Secret access key"; - public static final String IAM_ROLE_ARN_PARAM = "codedeploy_iam_role_arn"; + public static final String IAM_ROLE_ARN_PARAM = "elasticbeanstalk_iam_role_arn"; public static final String IAM_ROLE_ARN_LABEL = "IAM role ARN"; - public static final String EXTERNAL_ID_PARAM = "codedeploy_external_id"; + public static final String EXTERNAL_ID_PARAM = "elasticbeanstalk_external_id"; public static final String EXTERNAL_ID_LABEL = "External ID"; public static final Map DEFAULTS = Collections.unmodifiableMap(CollectionsUtil.asMap( - CREDENTIALS_TYPE_PARAM, ACCESS_KEYS_OPTION, - EXTERNAL_ID_PARAM, UUID.randomUUID().toString(), - USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN_PARAM, "false" + CREDENTIALS_TYPE_PARAM, ACCESS_KEYS_OPTION, + EXTERNAL_ID_PARAM, UUID.randomUUID().toString(), + USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN_PARAM, "false" )); public static final String TEMP_CREDENTIALS_SESSION_NAME_PARAM = "temp_credentials_session_name"; @@ -155,33 +152,33 @@ public static AWSClients createAWSClients(@NotNull Map params, b final boolean useDefaultCredProvChain = Boolean.parseBoolean(params.get(USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN_PARAM)); final AWSClients awsClients = - useDefaultCredProvChain ? - fromDefaultCredentialProviderChain(regionName) : - fromBasicCredentials(params.get(ACCESS_KEY_ID_PARAM), getSecretAccessKey(params), regionName); + useDefaultCredProvChain ? + fromDefaultCredentialProviderChain(regionName) : + fromBasicCredentials(params.get(ACCESS_KEY_ID_PARAM), getSecretAccessKey(params), regionName); return - TEMP_CREDENTIALS_OPTION.equals(params.get(CREDENTIALS_TYPE_PARAM)) ? createTempAWSClients(awsClients, params, lazy) : awsClients; + TEMP_CREDENTIALS_OPTION.equals(params.get(CREDENTIALS_TYPE_PARAM)) ? createTempAWSClients(awsClients, params, lazy) : awsClients; } @NotNull private static AWSClients createTempAWSClients(@NotNull final AWSClients clients, @NotNull final Map params, boolean lazy) { return fromExistingCredentials( - lazy ? new LazyCredentials() { - @NotNull - @Override - protected AWSSessionCredentials createCredentials() { - return createSessionCredentials(clients, params); - } - } : createSessionCredentials(clients, params), - clients.getRegion()); + lazy ? new LazyCredentials() { + @NotNull + @Override + protected AWSSessionCredentials createCredentials() { + return createSessionCredentials(clients, params); + } + } : createSessionCredentials(clients, params), + clients.getRegion()); } @NotNull private static AWSSessionCredentials createSessionCredentials(@NotNull final AWSClients clients, @NotNull Map params) { return clients.createSessionCredentials( - params.get(IAM_ROLE_ARN_PARAM), params.get(EXTERNAL_ID_PARAM), - patchSessionName(getStringOrDefault(params.get(TEMP_CREDENTIALS_SESSION_NAME_PARAM), TEMP_CREDENTIALS_SESSION_NAME_DEFAULT_PREFIX + new Date().getTime())), - getIntegerOrDefault(params.get(TEMP_CREDENTIALS_DURATION_SEC_PARAM), TEMP_CREDENTIALS_DURATION_SEC_DEFAULT)); + params.get(IAM_ROLE_ARN_PARAM), params.get(EXTERNAL_ID_PARAM), + patchSessionName(getStringOrDefault(params.get(TEMP_CREDENTIALS_SESSION_NAME_PARAM), TEMP_CREDENTIALS_SESSION_NAME_DEFAULT_PREFIX + new Date().getTime())), + getIntegerOrDefault(params.get(TEMP_CREDENTIALS_DURATION_SEC_PARAM), TEMP_CREDENTIALS_DURATION_SEC_DEFAULT)); } @NotNull diff --git a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSException.java b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSException.java index c483a44..7de9d09 100644 --- a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSException.java +++ b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSException.java @@ -24,24 +24,24 @@ import java.util.Map; -/** - * @author vbedrosova - */ public class AWSException extends RuntimeException { - // "CODEDEPLOY_" prefix is for backward compatibility - public static String SERVICE_PROBLEM_TYPE = "CODEDEPLOY_SERVICE"; - public static String CLIENT_PROBLEM_TYPE = "CODEDEPLOY_CLIENT"; - public static String EXCEPTION_BUILD_PROBLEM_TYPE = "CODEDEPLOY_EXCEPTION"; + // "ELASTICBEANSTALK_" prefix is for backward compatibility + public static String SERVICE_PROBLEM_TYPE = "ELASTICBEANSTALK_SERVICE"; + public static String CLIENT_PROBLEM_TYPE = "ELASTICBEANSTALK_CLIENT"; + public static String EXCEPTION_BUILD_PROBLEM_TYPE = "ELASTICBEANSTALK_EXCEPTION"; public static Map PROBLEM_TYPES = CollectionsUtil.asMap( - SERVICE_PROBLEM_TYPE, "Amazon service exception", - CLIENT_PROBLEM_TYPE, "Amazon client exception", - EXCEPTION_BUILD_PROBLEM_TYPE, "Amazon unexpected exception"); + SERVICE_PROBLEM_TYPE, "Amazon service exception", + CLIENT_PROBLEM_TYPE, "Amazon client exception", + EXCEPTION_BUILD_PROBLEM_TYPE, "Amazon unexpected exception"); - @Nullable private final String myIdentity; - @NotNull private final String myType; - @Nullable private final String myDetails; + @Nullable + private final String myIdentity; + @NotNull + private final String myType; + @Nullable + private final String myDetails; public AWSException(@NotNull String message, @Nullable String identity, @NotNull String type, @Nullable String details) { super(message); @@ -60,7 +60,8 @@ public AWSException(@NotNull Throwable t) { @NotNull public static String getMessage(@NotNull Throwable t) { if (t instanceof AWSException) return t.getMessage(); - if (t instanceof AmazonServiceException) return "AWS error: " + removeTrailingDot(((AmazonServiceException) t).getErrorMessage()); + if (t instanceof AmazonServiceException) + return "AWS error: " + removeTrailingDot(((AmazonServiceException) t).getErrorMessage()); if (t instanceof AmazonClientException) return "AWS client error: " + removeTrailingDot(t.getMessage()); return "Unexpected error: " + removeTrailingDot(t.getMessage()); } @@ -77,7 +78,7 @@ public static String getIdentity(@NotNull Throwable t) { @NotNull public static String getType(@NotNull Throwable t) { - if (t instanceof AWSException) return ((AWSException) t).getType(); + if (t instanceof AWSException) return ((AWSException) t).getType(); if (t instanceof AmazonServiceException) return SERVICE_PROBLEM_TYPE; if (t instanceof AmazonClientException) return CLIENT_PROBLEM_TYPE; return EXCEPTION_BUILD_PROBLEM_TYPE; @@ -89,11 +90,11 @@ public static String getDetails(@NotNull Throwable t) { if (t instanceof AmazonServiceException) { final AmazonServiceException ase = (AmazonServiceException) t; return "\n" + - "Service: " + ase.getServiceName() + "\n" + - "HTTP Status Code: " + ase.getStatusCode() + "\n" + - "AWS Error Code: " + ase.getErrorCode() + "\n" + - "Error Type: " + ase.getErrorType() + "\n" + - "Request ID: " + ase.getRequestId(); + "Service: " + ase.getServiceName() + "\n" + + "HTTP Status Code: " + ase.getStatusCode() + "\n" + + "AWS Error Code: " + ase.getErrorCode() + "\n" + + "Error Type: " + ase.getErrorType() + "\n" + + "Request ID: " + ase.getRequestId(); } return null; } @@ -105,7 +106,7 @@ private static String removeTrailingDot(@Nullable String msg) { @NotNull public String getIdentity() { - return myIdentity == null ? getMessage() : myIdentity; + return myIdentity == null ? getMessage() : myIdentity; } @NotNull diff --git a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSRegions.java b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSRegions.java index 55ce13c..28f64da 100644 --- a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSRegions.java +++ b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSRegions.java @@ -20,11 +20,10 @@ import com.amazonaws.regions.Regions; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; -/** - * @author vbedrosova - */ public final class AWSRegions { private static final Map REGION_NAMES_FOR_WEB; @@ -45,13 +44,13 @@ public final class AWSRegions { } @NotNull - public static String getRegionNameForWeb(@NotNull String regionCode){ + public static String getRegionNameForWeb(@NotNull String regionCode) { final String niceName = REGION_NAMES_FOR_WEB.get(regionCode); return niceName == null ? regionCode : niceName; } @NotNull - public static Map getAllRegions(){ + public static Map getAllRegions() { return Collections.unmodifiableMap(REGION_NAMES_FOR_WEB); } diff --git a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSUtil.java b/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSUtil.java deleted file mode 100644 index 8fe9ae4..0000000 --- a/amazon-util/src/main/java/jetbrains/buildServer/util/amazon/AWSUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.util.amazon; - -import com.amazonaws.services.codedeploy.model.BundleType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author vbedrosova - */ -public final class AWSUtil { - @Nullable - public static String getBundleType(@NotNull String revision) { - if (revision.endsWith(".zip")) return BundleType.Zip.name(); - if (revision.endsWith(".tar")) return BundleType.Tar.name(); - if (revision.endsWith(".tar.gz")) return BundleType.Tgz.name(); - return null; - } -} diff --git a/amazon-util/src/test/java/AWSCommonParamsTest.java b/amazon-util/src/test/java/AWSCommonParamsTest.java index 8163b27..9d92ad6 100644 --- a/amazon-util/src/test/java/AWSCommonParamsTest.java +++ b/amazon-util/src/test/java/AWSCommonParamsTest.java @@ -21,45 +21,43 @@ import org.testng.annotations.Test; import java.util.Map; + import static org.assertj.core.api.BDDAssertions.*; import static jetbrains.buildServer.util.amazon.AWSCommonParams.*; -/** - * @author vbedrosova - */ public class AWSCommonParamsTest extends BaseTestCase { @Test public void mandatory_params() { then(validate()).as("Must detect empty params").hasSize(4). - containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). - containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). - containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). - containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"); + containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). + containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). + containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). + containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"); } @Test public void unexpected_region() { then(validate(REGION_NAME_PARAM, "abrakadabra")).as("Must detect unexpected region name"). - containsEntry(REGION_NAME_PARAM, "Unsupported region name abrakadabra"); + containsEntry(REGION_NAME_PARAM, "Unsupported region name abrakadabra"); } @Test public void unexpected_credentials_type() { then(validate(CREDENTIALS_TYPE_PARAM, "abrakadabra")).as("Must detect unexpected credentials type"). - containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type has unexpected value abrakadabra"); + containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type has unexpected value abrakadabra"); } @Test public void default_credentials_provider_chain_true() { then(validate(USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN_PARAM, "true")).as("Mustn't require params"). - doesNotContainKey(ACCESS_KEY_ID_PARAM). - doesNotContainKey(SECURE_SECRET_ACCESS_KEY_PARAM); + doesNotContainKey(ACCESS_KEY_ID_PARAM). + doesNotContainKey(SECURE_SECRET_ACCESS_KEY_PARAM); } @Test public void temp_credentials_mandatory_params() { then(validate(CREDENTIALS_TYPE_PARAM, TEMP_CREDENTIALS_OPTION)).as("Must detect empty params"). - containsEntry(IAM_ROLE_ARN_PARAM, "IAM role ARN mustn't be empty"); + containsEntry(IAM_ROLE_ARN_PARAM, "IAM role ARN mustn't be empty"); } @NotNull diff --git a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/ApplicationRevision.java b/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/ApplicationRevision.java deleted file mode 100644 index 60a5da1..0000000 --- a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/ApplicationRevision.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import jetbrains.buildServer.agent.BuildProgressLogger; -import jetbrains.buildServer.util.CollectionsUtil; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.util.PathMappings; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.util.filters.Filter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.util.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -/** - * @author vbedrosova - */ -class ApplicationRevision { - @NotNull - private final String myName; - @NotNull - private final String myPaths; - @NotNull - private final File myBaseDir; - @NotNull - private final File myTempDir; - @Nullable - private final String myCustomAppSpec; - @NotNull - private final PathMappings myPathMappings; - @Nullable - private BuildProgressLogger myLogger; - - ApplicationRevision(@NotNull String name, @NotNull String paths, @NotNull File baseDir, @NotNull File tempDir, @Nullable String customAppSpecContent) { - myName = name; - myPaths = paths; - myBaseDir = baseDir; - myTempDir = tempDir; - myCustomAppSpec = customAppSpecContent; - - myPathMappings = new PathMappings(myBaseDir, CodeDeployUtil.getRevisionPathMappings(myPaths)); - } - - @NotNull - File getArchive() throws CodeDeployRunner.CodeDeployRunnerException { - final String readyRevisionPath = CodeDeployUtil.getReadyRevision(myPaths); - return readyRevisionPath == null ? packZip() : FileUtil.resolvePath(myBaseDir, readyRevisionPath); - } - - @NotNull - private File packZip() throws CodeDeployRunner.CodeDeployRunnerException { - final List files = new ArrayList(myPathMappings.collectFiles()); - - if (files.isEmpty()) { - throw new CodeDeployRunner.CodeDeployRunnerException("No " + CodeDeployConstants.REVISION_PATHS_LABEL.toLowerCase() + " files found", null); - } - return zipFiles(patchAppSpecYml(files), new File(myTempDir, myName.endsWith(".zip") ? myName : myName + ".zip")); - } - - @NotNull - private List patchAppSpecYml(@NotNull List files) throws CodeDeployRunner.CodeDeployRunnerException { - final File appSpecYml = CollectionsUtil.findFirst(files, new Filter() { - @Override - public boolean accept(@NotNull File data) { - return CodeDeployConstants.APPSPEC_YML.equals(data.getName()) && CodeDeployConstants.APPSPEC_YML.equals(myPathMappings.mapPath(data)); - } - }); - - final File customAppSpecYml = getCustomAppSpecYmlFile(); - if (customAppSpecYml != null) { - if (null == appSpecYml) { - log("Will use custom AppSpec file " + customAppSpecYml); - } else { - log("Will replace existing AppSpec file " + appSpecYml + " with custom " + customAppSpecYml); - files.remove(appSpecYml); - } - files.add(customAppSpecYml); - - } else if (null == appSpecYml) { - throw new CodeDeployRunner.CodeDeployRunnerException("No " + CodeDeployConstants.APPSPEC_YML + " file found among " + CodeDeployConstants.REVISION_PATHS_LABEL.toLowerCase() + " files and no custom AppSpec file provided", null); - } - return files; - } - - @Nullable - private File getCustomAppSpecYmlFile() throws CodeDeployRunner.CodeDeployRunnerException { - if (StringUtil.isEmptyOrSpaces(myCustomAppSpec)) return null; - - if (myCustomAppSpec.endsWith(CodeDeployConstants.APPSPEC_YML)) { - return FileUtil.resolvePath(myBaseDir, myCustomAppSpec); - } - - final File customAppSpecYml = new File(myTempDir, CodeDeployConstants.APPSPEC_YML); - if (!customAppSpecYml.isFile()) { - try { - FileUtil.writeFile(customAppSpecYml, "" + myCustomAppSpec, "UTF-8"); - } catch (IOException e) { - throw new CodeDeployRunner.CodeDeployRunnerException("Failed to write custom " + CodeDeployConstants.APPSPEC_YML, e); - } - } - return customAppSpecYml; - } - - @NotNull - private File zipFiles(@NotNull List files, @NotNull File destZip) throws CodeDeployRunner.CodeDeployRunnerException { - log("Packaging " + files.size() + " files to application revision " + destZip); - - ZipOutputStream zipOutput = null; - try { - zipOutput = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destZip))); - byte[] buffer = new byte[64 * 1024]; - - for (File f : files) { - - final ZipEntry zipEntry = new ZipEntry(getZipPath(f)); - zipEntry.setTime(f.lastModified()); - zipOutput.putNextEntry(zipEntry); - - final InputStream input = new BufferedInputStream(new FileInputStream(f)); - - try { - int read; - do { - read = input.read(buffer); - zipOutput.write(buffer, 0, Math.max(read, 0)); - } while (read == buffer.length); - } catch (IOException e) { - throw new CodeDeployRunner.CodeDeployRunnerException("Failed to package file " + f + " to application revision " + destZip, e); - } finally { - FileUtil.close(input); - zipOutput.closeEntry(); - } - } - } catch (Throwable t) { - if (t instanceof CodeDeployRunner.CodeDeployRunnerException) { - throw (CodeDeployRunner.CodeDeployRunnerException) t; - } - throw new CodeDeployRunner.CodeDeployRunnerException("Failed to package application revision " + destZip, t); - } finally { - FileUtil.close(zipOutput); - } - return destZip; - } - - @NotNull - private String getZipPath(@NotNull File f) throws CodeDeployRunner.CodeDeployRunnerException { - if (f.equals(getCustomAppSpecYmlFile())) return CodeDeployConstants.APPSPEC_YML; - - final String zipPath = myPathMappings.mapPath(f); - if(zipPath == null) throw new CodeDeployRunner.CodeDeployRunnerException("Unexpected application revision file " + f, null); - return zipPath; - } - - @NotNull - ApplicationRevision withLogger(@Nullable BuildProgressLogger logger) { - myLogger = logger; - return this; - } - - private void log(@NotNull String m) { - if (myLogger == null) return; - myLogger.message(m); - } -} diff --git a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/LoggingDeploymentListener.java b/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/LoggingDeploymentListener.java deleted file mode 100644 index a9a9d62..0000000 --- a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/LoggingDeploymentListener.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import com.intellij.openapi.diagnostic.Logger; -import jetbrains.buildServer.agent.BuildProgressLogger; -import jetbrains.buildServer.log.Loggers; -import jetbrains.buildServer.util.CollectionsUtil; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.util.amazon.AWSCommonParams; -import jetbrains.buildServer.util.amazon.AWSException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; - -/** - * @author vbedrosova - */ -class LoggingDeploymentListener extends AWSClient.Listener { - @NotNull - private static final Logger LOG = Logger.getInstance(Loggers.VCS_CATEGORY + CodeDeployRunner.class); - - static final String DEPLOY_APPLICATION = "deploy application"; - static final String REGISTER_REVISION = "register revision"; - static final String UPLOAD_REVISION = "upload revision"; - - @NotNull - private final Map myRunnerParameters; - @NotNull - private final BuildProgressLogger myBuildLogger; - @NotNull - private final String myCheckoutDir; - - LoggingDeploymentListener(@NotNull Map runnerParameters, @NotNull BuildProgressLogger buildLogger, @NotNull String checkoutDir) { - myRunnerParameters = runnerParameters; - myBuildLogger = buildLogger; - myCheckoutDir = checkoutDir; - } - - @Override - void uploadRevisionStarted(@NotNull File revision, @NotNull String s3BucketName, @NotNull String key) { - open(UPLOAD_REVISION); - log(String.format("Uploading application revision %s to S3 bucket %s using key %s", revision.getPath(), s3BucketName, key)); - } - - @Override - void uploadRevisionFinished(@NotNull File revision, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, @NotNull String url) { - final boolean hasVersion = StringUtil.isNotEmpty(s3ObjectVersion); - final boolean hasETag = StringUtil.isNotEmpty(s3ObjectETag); - - final String directUrl = - url + - (hasVersion || hasETag ? "?" : "") + - (hasVersion ? "versionId=" + s3ObjectVersion : "") + - (hasVersion && hasETag ? "&" : "") + - (hasETag ? "etag=" + s3ObjectETag : ""); - - log("Uploaded application revision " + directUrl); - if (!CodeDeployUtil.isRegisterStepEnabled(myRunnerParameters)) { - statusText("Uploaded " + directUrl); - } - if (hasVersion) parameter(CodeDeployConstants.S3_OBJECT_VERSION_CONFIG_PARAM, s3ObjectVersion); - if (hasETag) parameter(CodeDeployConstants.S3_OBJECT_ETAG_CONFIG_PARAM, s3ObjectETag); - close(UPLOAD_REVISION); - } - - @Override - void registerRevisionStarted(@NotNull String applicationName, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String s3BundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag) { - open(REGISTER_REVISION); - log(String.format("Registering application %s revision from S3 bucket %s with key %s, bundle type %s, %s version and %s ETag", applicationName, s3BucketName, s3ObjectKey, s3BundleType, StringUtil.isEmptyOrSpaces(s3ObjectVersion) ? "latest" : s3ObjectVersion, StringUtil.isEmptyOrSpaces(s3ObjectETag) ? "no" : s3ObjectETag)); - } - - @Override - void registerRevisionFinished(@NotNull String applicationName, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag) { - if (!CodeDeployUtil.isDeployStepEnabled(myRunnerParameters)) { - statusText("Registered revision"); - } - close(REGISTER_REVISION); - } - - @Override - void createDeploymentStarted(@NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName) { - open(DEPLOY_APPLICATION); - log(String.format("Creating application %s deployment to deployment group %s with %s deployment configuration", applicationName, deploymentGroupName, StringUtil.isEmptyOrSpaces(deploymentConfigName) ? "default" : deploymentConfigName)); - } - - @Override - void createDeploymentFinished(@NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName, @NotNull String deploymentId) { - parameter(CodeDeployConstants.DEPLOYMENT_ID_BUILD_CONFIG_PARAM, deploymentId); - log("Deployment " + deploymentId + " created"); - } - - @Override - void deploymentWaitStarted(@NotNull String deploymentId) { - log("Waiting for deployment finish"); - } - - - @Override - void deploymentInProgress(@NotNull String deploymentId, @Nullable InstancesStatus instancesStatus) { - progress(deploymentDescription(instancesStatus, null, false)); - } - - @Override - void deploymentFailed(@NotNull String deploymentId, @Nullable Integer timeoutSec, @Nullable ErrorInfo errorInfo, @Nullable InstancesStatus instancesStatus) { - String msg = (timeoutSec == null ? "" : "Timeout " + timeoutSec + " sec exceeded, "); - - err(msg + StringUtil.decapitalize(deploymentDescription(instancesStatus, deploymentId, true))); - msg += StringUtil.decapitalize(deploymentDescription(instancesStatus, deploymentId, false)); - - if (errorInfo != null) { - if (StringUtil.isNotEmpty(errorInfo.message)) { - err("Associated error: " + errorInfo.message); - msg += ": " + errorInfo.message; - } - if (StringUtil.isNotEmpty(errorInfo.code)) { - err("Error code: " + errorInfo.code); - } - } - - problem(getIdentity(timeoutSec, errorInfo, instancesStatus), timeoutSec == null ? CodeDeployConstants.FAILURE_BUILD_PROBLEM_TYPE : CodeDeployConstants.TIMEOUT_BUILD_PROBLEM_TYPE, msg); - - close(DEPLOY_APPLICATION); - } - - @Override - void deploymentSucceeded(@NotNull String deploymentId, @Nullable InstancesStatus instancesStatus) { - log(deploymentDescription(instancesStatus, deploymentId, true)); - statusText(deploymentDescription(instancesStatus, deploymentId, false)); - - close(DEPLOY_APPLICATION); - } - - @Override - void exception(@NotNull AWSException e) { - LOG.error(e); - - final String message = e.getMessage(); - final String details = e.getDetails(); - - err(message); - if (StringUtil.isNotEmpty(details)) err(details); - problem(getIdentity(e.getIdentity()), e.getType(), message); - close(DEPLOY_APPLICATION); - } - - private int getIdentity(@Nullable Integer timeoutSec, @Nullable ErrorInfo errorInfo, @Nullable InstancesStatus instancesStatus) { - return getIdentity( - timeoutSec == null ? null : timeoutSec.toString(), - errorInfo == null ? null : errorInfo.code, - instancesStatus == null ? null : instancesStatus.status - ); - } - - @NotNull - private String deploymentDescription(@Nullable InstancesStatus instancesStatus, @Nullable String deploymentId, boolean detailed) { - final StringBuilder sb = new StringBuilder("Deployment "); - - if (StringUtil.isNotEmpty(deploymentId)) { - sb.append(deploymentId).append(" "); - } - - if (instancesStatus == null) sb.append(CodeDeployConstants.STATUS_IS_UNKNOWN); - else { - sb.append(StringUtil.isEmptyOrSpaces(instancesStatus.status) ? CodeDeployConstants.STATUS_IS_UNKNOWN : instancesStatus.status); - sb.append(", ").append(instancesStatus.succeeded).append(" ").append(StringUtil.pluralize("instance", instancesStatus.succeeded)).append(" succeeded"); - if (instancesStatus.failed > 0 || detailed) sb.append(", ").append(instancesStatus.failed).append(" failed"); - if (instancesStatus.pending > 0 || detailed) sb.append(", ").append(instancesStatus.pending).append(" pending"); - if (instancesStatus.skipped > 0 || detailed) sb.append(", ").append(instancesStatus.skipped).append(" skipped"); - if (instancesStatus.inProgress > 0 || detailed) sb.append(", ").append(instancesStatus.inProgress).append(" in progress"); - } - - return sb.toString(); - } - - private int getIdentity(String... parts) { - return AWSCommonParams.calculateIdentity(myCheckoutDir, myRunnerParameters, CollectionsUtil.join(getIdentityFormingParameters(), Arrays.asList(parts))); - } - - @NotNull - private Collection getIdentityFormingParameters() { - return Arrays.asList( - myRunnerParameters.get(CodeDeployConstants.S3_BUCKET_NAME_PARAM), - myRunnerParameters.get(CodeDeployConstants.APP_NAME_PARAM), - myRunnerParameters.get(CodeDeployConstants.DEPLOYMENT_GROUP_NAME_PARAM)); - } - - protected void debug(@NotNull String message) { - myBuildLogger.message(String.format("##teamcity[message text='%s' tc:tags='tc:internal']", escape(message))); - } - - protected void log(@NotNull String message) { - myBuildLogger.message(message); - } - - protected void err(@NotNull String message) { - myBuildLogger.error(message); - } - - protected void open(@NotNull String block) { - myBuildLogger.targetStarted(block); - } - - protected void close(@NotNull String block) { - myBuildLogger.targetFinished(block); - } - - protected void progress(@NotNull String message) { - myBuildLogger.message(String.format("##teamcity[progressMessage '%s']", escape(message))); - } - - protected void problem(int identity, @NotNull String type, @NotNull String descr) { - myBuildLogger.message(String.format("##teamcity[buildProblem identity='%d' type='%s' description='%s' tc:tags='tc:internal']", identity, type, escape(descr))); - } - - protected void parameter(@NotNull String name, @NotNull String value) { - myBuildLogger.message(String.format("##teamcity[setParameter name='%s' value='%s' tc:tags='tc:internal']", name, value)); - } - - protected void statusText(@NotNull String text) { - myBuildLogger.message(String.format("##teamcity[buildStatus tc:tags='tc:internal' text='{build.status.text}; %s']", text)); - } - - @NotNull - protected String escape(@NotNull String s) { - return s. - replace("|", "||"). - replace("'", "|'"). - replace("\n", "|n"). - replace("\r", "|r"). - replace("\\uNNNN", "|0xNNNN"). - replace("[", "|[").replace("]", "|]"); - } -} diff --git a/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/ApplicationRevisionTest.java b/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/ApplicationRevisionTest.java deleted file mode 100644 index 1b5cd12..0000000 --- a/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/ApplicationRevisionTest.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import jetbrains.buildServer.agent.NullBuildProgressLogger; -import jetbrains.buildServer.util.ArchiveUtil; -import jetbrains.buildServer.util.FileUtil; -import org.assertj.core.api.Assertions; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static jetbrains.buildServer.runner.codedeploy.CodeDeployRunner.CodeDeployRunnerException; -import static org.assertj.core.api.BDDAssertions.*; - -import java.io.File; -import java.io.IOException; -import java.util.LinkedList; - -/** - * @author vbedrosova - */ -public class ApplicationRevisionTest extends LoggingTestCase { - - @BeforeMethod(alwaysRun = true) - public void mySetUp() throws Exception { - super.mySetUp(); - } - - @AfterMethod(alwaysRun = true) - public void myTearDown() throws Exception { - super.myTearDown(); - } - - @Test - public void ready_revision_zip() throws Exception { - ready_revision_arch(".zip"); - } - - @Test - public void ready_revision_tar() throws Exception { - ready_revision_arch(".tar"); - } - - @Test - public void ready_revision_tar_gz() throws Exception { - ready_revision_arch(".tar.gz"); - } - - private void ready_revision_arch(@NotNull String ext) throws Exception { - final String path = "some/path/readyRevision" + ext; - final File readyRevision = writeFile(path); - - then(create(path).getArchive()).as("Unexpected revision").isEqualTo(readyRevision); - } - - @Test - public void ready_revision_absolute_path() throws Exception { - final File readyRevision = writeTempFile("some/path/readyRevision.zip"); - then(create(readyRevision.getAbsolutePath()).getArchive()).as("Unexpected revision").isEqualTo(readyRevision); - } - - @Test - public void no_files_found() throws Exception { - try { - create(REVISION_PATHS).getArchive(); - failBecauseExceptionWasNotThrown(CodeDeployRunnerException.class); - } catch (CodeDeployRunnerException e) { - Assertions.assertThat(e).hasMessage("No " + CodeDeployConstants.REVISION_PATHS_LABEL.toLowerCase() + " files found"); - } - } - - @Test - public void no_appspec_yml_found() throws Exception { - try { - fillBaseDir(false); - - create(REVISION_PATHS).getArchive(); - failBecauseExceptionWasNotThrown(CodeDeployRunnerException.class); - } catch (CodeDeployRunnerException e) { - Assertions.assertThat(e).hasMessage("No appspec.yml file found among application revision files and no custom AppSpec file provided"); - } - } - - @Test - public void no_appspec_yml_found_custom_provided() throws Exception { - fillBaseDir(false); - - assertRevision(create(REVISION_PATHS, CAC).getArchive(), RESULT_PATHS, CAC); - - assertLog( - "Will use custom AppSpec file ##TEMP_DIR##/appspec.yml", - "Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void no_appspec_yml_found_custom_path_provided() throws Exception { - fillBaseDir(false); - writeFile("another/path/appspec.yml", CAC); - - assertRevision(create(REVISION_PATHS, "another/path/appspec.yml").getArchive(), RESULT_PATHS, CAC); - - assertLog( - "Will use custom AppSpec file ##BASE_DIR##/another/path/appspec.yml", - "Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void no_appspec_yml_found_custom_absolute_path_provided() throws Exception { - fillBaseDir(false); - - assertRevision(create(REVISION_PATHS, writeTempFile("some/path/appspec.yml", CAC).getAbsolutePath()).getArchive(), RESULT_PATHS, CAC); - - assertLog( - "Will use custom AppSpec file ##TEMP_DIR##/some/path/appspec.yml", - "Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void with_appspec_yml_custom_provided() throws Exception { - fillBaseDir(true); - - assertRevision(create(REVISION_PATHS, CAC).getArchive(), RESULT_PATHS, CAC); - - assertLog( - "Will replace existing AppSpec file ##BASE_DIR##/appspec.yml with custom ##TEMP_DIR##/appspec.yml", - "Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void with_appspec_yml_custom_path_provided() throws Exception { - fillBaseDir(true); - writeFile("another/path/appspec.yml", CAC); - - assertRevision(create(REVISION_PATHS, "another/path/appspec.yml").getArchive(), RESULT_PATHS, CAC); - - assertLog( - "Will replace existing AppSpec file ##BASE_DIR##/appspec.yml with custom ##BASE_DIR##/another/path/appspec.yml", - "Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void with_appspec_yml_custom_absolute_path_provided() throws Exception { - fillBaseDir(true); - - assertRevision(create(REVISION_PATHS, writeTempFile("some/path/appspec.yml", CAC).getAbsolutePath()).getArchive(), RESULT_PATHS, CAC); - - assertLog( - "Will replace existing AppSpec file ##BASE_DIR##/appspec.yml with custom ##TEMP_DIR##/some/path/appspec.yml", - "Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void with_appspec_yml() throws Exception { - fillBaseDir(true); - - final File revision = create(REVISION_PATHS).getArchive(); - then(revision).as("Unexpected revision").isEqualTo(getCustomRevision("test_revision.zip")); - assertRevision(revision, RESULT_PATHS, AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void wildcard_two_stars() throws Exception { - fillBaseDir(true); - - assertRevision(create("some/path/**,appspec.yml").getArchive(), RESULT_PATHS, AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void wildcard_include_all() throws Exception { - fillBaseDir(true); - - assertRevision(create("**").getArchive(), arr("some/path/index.html", "some/path/inner/path/error.html", "some/path/inner/path/test/test.html", "another/path/index.html", "another/path/inner/path/error.html", "another/path/inner/path/test/test.html", "appspec.yml"), AC); - - assertLog("Packaging 7 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void simple_paths() throws Exception { - fillBaseDir(true); - - assertRevision(create("some/path/index.html,some/path/inner/path/error.html,some/path/inner/path/test/test.html,appspec.yml").getArchive(), arr("index.html", "error.html", "test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void simple_dir() throws Exception { - fillBaseDir(true); - - assertRevision(create("some/path/inner/path/,appspec.yml").getArchive(), arr("error.html", "test/test.html", "appspec.yml"), AC); - - assertLog("Packaging 3 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void simple_paths_with_mapping() throws Exception { - fillBaseDir(false); - writeFile("another/path/appspec.yml", AC); - - assertRevision(create("some/path/index.html=>pages/dist,some/path/inner/path/error.html=>pages/dist/error,some/path/inner/path/test/test.html=>pages,another/path/appspec.yml => .").getArchive(), arr("pages/dist/index.html", "pages/dist/error/error.html", "pages/test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void simple_paths_with_mapping_with_slashes() throws Exception { - fillBaseDir(false); - writeFile("another/path/appspec.yml", AC); - - assertRevision(create("some/path/index.html=>/pages/dist,some/path/inner/path/error.html=>pages/dist/error/,some/path/inner/path/test/test.html=>/pages/,another/path/appspec.yml => .").getArchive(), arr("pages/dist/index.html", "pages/dist/error/error.html", "pages/test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void simple_dir_with_mapping() throws Exception { - fillBaseDir(true); - - assertRevision(create("some/path/=>pages/dist,some/path/inner/path/=>pages/dist/error,some/path/inner/path/test/=>pages,appspec.yml").getArchive(), arr("pages/dist/index.html", "pages/dist/error/error.html", "pages/test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void wildcard_two_stars_with_mapping() throws Exception { - fillBaseDir(true); - - assertRevision(create("some/path/**=>pages/dist,some/path/inner/path/test/**=>pages,appspec.yml => .").getArchive(), arr("pages/dist/index.html", "pages/dist/inner/path/error.html", "pages/test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - -// TW-45267 - @Test - public void wildcard_two_stars_with_mapping_with_plus_prefix() throws Exception { - fillBaseDir(true); - - assertRevision(create("+:some/path/**=>pages/dist,+:some/path/inner/path/test/**=>pages,+:appspec.yml").getArchive(), arr("pages/dist/index.html", "pages/dist/inner/path/error.html", "pages/test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void wildcard_three_stars_with_mapping() throws Exception { - fillBaseDir(true); - - assertRevision(create("some/path/**/*.html=>pages/dist,some/path/inner/path/test/**/*.html=>pages,appspec.yml => .").getArchive(), arr("pages/dist/index.html", "pages/dist/inner/path/error.html", "pages/test.html", "appspec.yml"), AC); - - assertLog("Packaging 4 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void wildcard_include_all_with_mapping() throws Exception { - fillBaseDir(true); - - assertRevision(create("** => pages/dist,some/path/**/test/** => pages,another/path/**/test/** => .,appspec.yml => .").getArchive(), arr("pages/dist/some/path/index.html", "pages/dist/some/path/inner/path/error.html", "pages/test.html", "pages/dist/another/path/index.html", "pages/dist/another/path/inner/path/error.html", "test.html", "appspec.yml"), AC); - - assertLog("Packaging 7 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - - @Test - public void only_dot() throws Exception { - fillBaseDir(true); - - assertRevision(create(".").getArchive(), arr("some/path/index.html", "some/path/inner/path/error.html", "some/path/inner/path/test/test.html", "another/path/index.html", "another/path/inner/path/error.html", "another/path/inner/path/test/test.html", "appspec.yml"), AC); - - assertLog("Packaging 7 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void empty_from() throws Exception { - fillBaseDir(true); - - assertRevision(create("=>dist,appspec.yml").getArchive(), arr("dist/some/path/index.html", "dist/some/path/inner/path/error.html", "dist/some/path/inner/path/test/test.html", "dist/another/path/index.html", "dist/another/path/inner/path/error.html", "dist/another/path/inner/path/test/test.html", "appspec.yml"), AC); - - assertLog("Packaging 7 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - @Test - public void no_false_matches() throws Exception { - fillBaseDir(true); - writeFile("another_file"); - - assertRevision(create("another/path/**=>dist , appspec.yml, another_file ").getArchive(), arr("dist/index.html", "dist/inner/path/error.html", "dist/inner/path/test/test.html", "appspec.yml", "another_file"), AC); - - assertLog("Packaging 5 files to application revision ##TEMP_DIR##/test_revision.zip"); - } - - private void fillBaseDir(boolean withAppSpecFile) throws IOException { - writeFile("some/path/index.html"); - writeFile("some/path/inner/path/error.html"); - writeFile("some/path/inner/path/test/test.html"); - writeFile("another/path/index.html"); - writeFile("another/path/inner/path/error.html"); - writeFile("another/path/inner/path/test/test.html"); - if (withAppSpecFile) writeFile("appspec.yml", AC); - } - - private void assertRevision(@NotNull File revision, @NotNull String[] paths, @NotNull String expectedAppSpecYmlContent) throws IOException { - final File res = unpackZip(revision); - final LinkedList resPaths = new LinkedList(); - collectPaths(res, res, resPaths); - then(resPaths).as("Unexpected number of files in zip").hasSize(paths.length); - for (String p : paths) { - then(resPaths).as("zip must contain " + p).contains(p); - } - then(FileUtil.readText(new File(res, new File("appspec.yml").getName()))).as("Unexpected appspec.yml content").isEqualTo(expectedAppSpecYmlContent); - } - - @NotNull - private File unpackZip(@NotNull File zip) throws IOException { - final File tempDir = createTempDir(); - ArchiveUtil.unpackZip(zip, tempDir); - return tempDir; - } - - private void collectPaths(@NotNull File f, @NotNull File baseDir, @NotNull LinkedList paths) throws IOException { - if (f.isFile()) paths.add(FileUtil.toSystemIndependentName(FileUtil.getRelativePath(baseDir, f))); - if (f.isDirectory()) { - final File[] files = f.listFiles(); - if (files == null || files.length == 0) return; - for (File child : files) { - collectPaths(child, baseDir, paths); - } - } - } - - @NotNull - private File getCustomRevision(@NotNull String name) { - return new File(getTempDir(), name); - } - - @NotNull - private ApplicationRevision create(@NotNull String paths) { - return create(paths, null); - } - - @NotNull - private ApplicationRevision create(@NotNull String paths, @Nullable String customAppSpec) { - return new ApplicationRevision("test_revision", paths, getBaseDir(), getTempDir(), customAppSpec).withLogger(new NullBuildProgressLogger() { - @Override - public void message(String message) { - ApplicationRevisionTest.this.logMessage(message); - } - }); - } - - @NotNull - private static String[] arr(String... strings) { - return strings; - } - - private static final String CAC = "CUSTOM_APPSPEC_CONTENT"; - private static final String AC = "APPSPEC_CONTENT"; - private static final String REVISION_PATHS = "some/path/**/*.html\nappspec.yml"; - private static final String[] RESULT_PATHS = arr("index.html", "inner/path/error.html", "inner/path/test/test.html", "appspec.yml"); -} diff --git a/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingDeploymentListenerTest.java b/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingDeploymentListenerTest.java deleted file mode 100644 index 535843c..0000000 --- a/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingDeploymentListenerTest.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import jetbrains.buildServer.agent.NullBuildProgressLogger; -import jetbrains.buildServer.util.amazon.AWSException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.io.File; -import java.util.Collections; - -/** - * @author vbedrosova - */ -public class LoggingDeploymentListenerTest extends LoggingTestCase { - - private static final String FAKE_ID = "ID-123XYZ"; - - @BeforeMethod(alwaysRun = true) - public void mySetUp() throws Exception { - super.mySetUp(); - } - - @AfterMethod(alwaysRun = true) - public void myTearDown() throws Exception { - super.myTearDown(); - } - - @Test - public void common_events() throws Exception { - final LoggingDeploymentListener listener = create(); - - final String revisionName = "revision.zip"; - final File revision = writeFile(revisionName); - final String bucketName = "bucketName"; - final String key = "path/key.zip"; - final String eTag = "12345"; - - listener.uploadRevisionStarted(revision, bucketName, key); - - final String url = "https://s3-eu-west-1.amazonaws.com/bucketName/path/key.zip"; - - listener.uploadRevisionFinished(revision, bucketName, key, null, eTag, url); - - final String applicationName = "App Name"; - final String bundleType = ".zip"; - - listener.registerRevisionStarted(applicationName, bucketName, key, bundleType, null, eTag); - listener.registerRevisionFinished(applicationName, bucketName, key, bundleType, null, eTag); - - final String groupName = "Deployment Fleet"; - - listener.createDeploymentStarted(applicationName, groupName, null); - - listener.createDeploymentFinished(applicationName, groupName, null, FAKE_ID); - - listener.deploymentWaitStarted(FAKE_ID); - - assertLog( - "OPEN " + LoggingDeploymentListener.UPLOAD_REVISION, - "LOG Uploading application revision ##BASE_DIR##/" + revisionName + " to S3 bucket " + bucketName + " using key " + key, - "LOG Uploaded application revision " + url + "?etag=" + eTag, - "STATUS_TEXT Uploaded " + url + "?etag=" + eTag, - "PARAM " + CodeDeployConstants.S3_OBJECT_ETAG_CONFIG_PARAM + " -> " + eTag, - "CLOSE " + LoggingDeploymentListener.UPLOAD_REVISION, - "OPEN " + LoggingDeploymentListener.REGISTER_REVISION, - "LOG Registering application " + applicationName + " revision from S3 bucket " + bucketName + " with key " + key + ", bundle type " + bundleType + ", latest version and " + eTag + " ETag", - "STATUS_TEXT Registered revision", - "CLOSE " + LoggingDeploymentListener.REGISTER_REVISION, - "OPEN " + LoggingDeploymentListener.DEPLOY_APPLICATION, - "LOG Creating application " + applicationName + " deployment to deployment group " + groupName + " with default deployment configuration", - "PARAM " + CodeDeployConstants.DEPLOYMENT_ID_BUILD_CONFIG_PARAM + " -> " + FAKE_ID, - "LOG Deployment " + FAKE_ID + " created", - "LOG Waiting for deployment finish"); - } - - @Test - public void deployment_progress_unknown() throws Exception { - create().deploymentInProgress(FAKE_ID, createStatus()); - assertLog("PROGRESS Deployment status is unknown, 0 instances succeeded"); - } - - @Test - public void deployment_progress() throws Exception { - create().deploymentInProgress(FAKE_ID, createStatus("in progress", 1, 1, 1, 1, 1)); - assertLog("PROGRESS Deployment in progress, 1 instance succeeded, 1 failed, 1 pending, 1 skipped, 1 in progress"); - } - - @Test - public void deployment_progress_finished_five_succeeded() throws Exception { - create().deploymentInProgress(FAKE_ID, createStatus("finished", 0, 0, 5, 0, 0)); - assertLog("PROGRESS Deployment finished, 5 instances succeeded"); - } - - @Test - public void deployment_succeeded_short() throws Exception { - create().deploymentSucceeded(FAKE_ID, createStatus("finished", 0, 0, 5, 0, 0)); - assertLog( - "LOG Deployment " + FAKE_ID + " finished, 5 instances succeeded, 0 failed, 0 pending, 0 skipped, 0 in progress", - "STATUS_TEXT Deployment " + FAKE_ID + " finished, 5 instances succeeded", - "CLOSE " + LoggingDeploymentListener.DEPLOY_APPLICATION); - } - - @Test - public void deployment_succeeded_long() throws Exception { - create().deploymentSucceeded(FAKE_ID, createStatus("finished", 2, 2, 1, 2, 2)); - assertLog( - "LOG Deployment " + FAKE_ID + " finished, 1 instance succeeded, 2 failed, 2 pending, 2 skipped, 2 in progress", - "STATUS_TEXT Deployment " + FAKE_ID + " finished, 1 instance succeeded, 2 failed, 2 pending, 2 skipped, 2 in progress", - "CLOSE " + LoggingDeploymentListener.DEPLOY_APPLICATION); - } - - @Test - public void deployment_failed_timeout() throws Exception { - create().deploymentFailed(FAKE_ID, 2400, null, createStatus("in progress", 1, 1, 0, 0, 0)); - assertLog( - "ERR Timeout 2400 sec exceeded, deployment " + FAKE_ID + " in progress, 0 instances succeeded, 0 failed, 1 pending, 0 skipped, 1 in progress", - "PROBLEM identity: -1745153132 type: CODEDEPLOY_TIMEOUT descr: Timeout 2400 sec exceeded, deployment " + FAKE_ID + " in progress, 0 instances succeeded, 1 pending, 1 in progress", - "CLOSE deploy application"); - } - - @Test - public void deployment_failed_timeout_with_error() throws Exception { - create().deploymentFailed(FAKE_ID, 2400, createError("abc", "Some error message"), createStatus("failed", 1, 1, 0, 1, 0)); - assertLog( - "ERR Timeout 2400 sec exceeded, deployment " + FAKE_ID + " failed, 0 instances succeeded, 1 failed, 1 pending, 0 skipped, 1 in progress", - "ERR Associated error: Some error message", - "ERR Error code: abc", - "PROBLEM identity: -116108899 type: CODEDEPLOY_TIMEOUT descr: Timeout 2400 sec exceeded, deployment " + FAKE_ID + " failed, 0 instances succeeded, 1 failed, 1 pending, 1 in progress: Some error message", - "CLOSE deploy application"); - } - - @Test - public void deployment_failed() throws Exception { - create().deploymentFailed(FAKE_ID, null, createError("abc", "Some error message"), createStatus("failed", 0, 0, 0, 2, 0)); - assertLog( - "ERR deployment " + FAKE_ID + " failed, 0 instances succeeded, 2 failed, 0 pending, 0 skipped, 0 in progress", - "ERR Associated error: Some error message", - "ERR Error code: abc", - "PROBLEM identity: 448838431 type: CODEDEPLOY_FAILURE descr: deployment " + FAKE_ID + " failed, 0 instances succeeded, 2 failed: Some error message", - "CLOSE deploy application"); - } - - @Test - public void deployment_exception_type() throws Exception { - create().exception(new AWSException("Some exception message", null, AWSException.EXCEPTION_BUILD_PROBLEM_TYPE, null)); - assertLog( - "ERR Some exception message", - "PROBLEM identity: 2086901196 type: CODEDEPLOY_EXCEPTION descr: Some exception message", - "CLOSE deploy application"); - } - - @Test - public void deployment_exception_description_type() throws Exception { - create().exception(new AWSException("Some exception message", null, AWSException.CLIENT_PROBLEM_TYPE, "Some exception details")); - assertLog( - "ERR Some exception message", - "ERR Some exception details", - "PROBLEM identity: 2086901196 type: CODEDEPLOY_CLIENT descr: Some exception message", - "CLOSE deploy application"); - } - - @Test - public void deployment_exception_description_type_identity() throws Exception { - create().exception(new AWSException("Some exception message", "ABC123", AWSException.CLIENT_PROBLEM_TYPE, "Some exception details")); - assertLog( - "ERR Some exception message", - "ERR Some exception details", - "PROBLEM identity: -1424436592 type: CODEDEPLOY_CLIENT descr: Some exception message", - "CLOSE deploy application"); - } - - @Override - protected void performAfterTestVerification() { - // override parent behaviour - } - - @NotNull - private AWSClient.Listener.InstancesStatus createStatus(@Nullable String status, int pending, int inProgress, int succeeded, int failed, int skipped) { - final AWSClient.Listener.InstancesStatus instancesStatus = new AWSClient.Listener.InstancesStatus(); - instancesStatus.pending = pending; - instancesStatus.inProgress = inProgress; - instancesStatus.succeeded = succeeded; - instancesStatus.failed = failed; - instancesStatus.skipped = skipped; - if (status != null) instancesStatus.status = status; - return instancesStatus; - } - - @NotNull - private AWSClient.Listener.InstancesStatus createStatus() { - return new AWSClient.Listener.InstancesStatus(); - } - - @NotNull - private AWSClient.Listener.ErrorInfo createError(@Nullable String code, @Nullable String message) { - final AWSClient.Listener.ErrorInfo errorInfo = new AWSClient.Listener.ErrorInfo(); - errorInfo.code = code; - errorInfo.message = message; - return errorInfo; - } - - @NotNull - private LoggingDeploymentListener create() { - return new LoggingDeploymentListener(Collections.emptyMap(), - new NullBuildProgressLogger(), - "fake_checkout_dir") { - @Override - protected void debug(@NotNull String message) { - logMessage("DEBUG " + message); - } - - @Override - protected void log(@NotNull String message) { - logMessage("LOG " + message); - } - - @Override - protected void err(@NotNull String message) { - logMessage("ERR " + message); - } - - @Override - protected void open(@NotNull String block) { - logMessage("OPEN " + block); - } - - @Override - protected void close(@NotNull String block) { - logMessage("CLOSE " + block); - } - - @Override - protected void parameter(@NotNull String name, @NotNull String value) { - logMessage("PARAM " + name + " -> " + value); - } - - @Override - protected void problem(int identity, @NotNull String type, @NotNull String descr) { - logMessage("PROBLEM identity: " + identity + " type: " + type + " descr: " + descr); - } - - @Override - protected void progress(@NotNull String message) { - logMessage("PROGRESS " + message); - } - - @Override - protected void statusText(@NotNull String text) { - logMessage("STATUS_TEXT " + text); - } - }; - } -} diff --git a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/AWSClient.java b/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/AWSClient.java deleted file mode 100644 index b11c995..0000000 --- a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/AWSClient.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import com.amazonaws.services.codedeploy.AmazonCodeDeployClient; -import com.amazonaws.services.codedeploy.model.*; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.PutObjectRequest; -import com.amazonaws.services.s3.model.PutObjectResult; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.util.amazon.AWSClients; -import jetbrains.buildServer.util.amazon.AWSException; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; - -/** - * @author vbedrosova - */ -public class AWSClient { - - @NotNull private AmazonS3Client myS3Client; - @NotNull private AmazonCodeDeployClient myCodeDeployClient; - @Nullable private String myDescription; - @NotNull private Listener myListener = new Listener(); - - public AWSClient(@NotNull AWSClients clients) { - myS3Client = clients.createS3Client(); - myCodeDeployClient = clients.createCodeDeployClient(); - } - - @NotNull - public AWSClient withDescription(@NotNull String description) { - myDescription = description; - return this; - } - - @NotNull - public AWSClient withListener(@NotNull Listener listener) { - myListener = listener; - return this; - } - - /** - * Uploads application revision archive to S3 bucket named s3BucketName with the provided key and bundle type. - *

- * For performing this operation target AWSClient must have corresponding S3 permissions. - * - * @param revision valid application revision containing appspec.yml - * @param s3BucketName valid S3 bucket name - * @param s3ObjectKey valid S3 object key - */ - public void uploadRevision(@NotNull File revision, - @NotNull String s3BucketName, @NotNull String s3ObjectKey) { - try { - doUploadRevision(revision, s3BucketName, s3ObjectKey); - } catch (Throwable t) { - processFailure(t); - } - } - - /** - * Registers application revision from the specified location for the specified CodeDeploy application. - *

- * For performing this operation target AWSClient must have corresponding CodeDeploy permissions. - * - * @param s3BucketName valid S3 bucket name - * @param s3ObjectKey valid S3 object key - * @param bundleType one of zip, tar or tar.gz - * @param s3ObjectVersion S3 object version (for versioned buckets) or null to use the latest version - * @param s3ObjectETag S3 object ETag (file checksum) for object validation or null if no validation should be performed - * @param applicationName CodeDeploy application name - */ - public void registerRevision(@NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, - @NotNull String applicationName) { - try { - doRegisterRevision(getRevisionLocation(s3BucketName, s3ObjectKey, bundleType, s3ObjectVersion, s3ObjectETag), applicationName); - } catch (Throwable t) { - processFailure(t); - } - } - - /** - * Creates deployment of the application revision from the specified location for specified application (must be pre-configured) to - * deploymentGroupName (must be pre-configured) EC2 instances group with - * deploymentConfigName or default configuration name and waits for deployment finish. - *

- * For performing this operation target AWSClient must have corresponding CodeDeploy permissions. - * - * @param s3BucketName valid S3 bucket name - * @param s3ObjectKey valid S3 object key - * @param bundleType one of zip, tar or tar.gz - * @param s3ObjectVersion S3 object version (for versioned buckets) or null to use the latest version - * @param s3ObjectETag S3 object ETag (file checksum) for object validation or null if no validation should be performed - * @param applicationName CodeDeploy application name - * @param deploymentGroupName deployment group name - * @param deploymentConfigName deployment configuration name or null for default deployment configuration - * @param waitTimeoutSec seconds to wait for the created deployment finish or fail - * @param waitIntervalSec seconds between polling CodeDeploy for the created deployment status - */ - public void deployRevisionAndWait(@NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, - @NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName, - int waitTimeoutSec, int waitIntervalSec) { - doDeployAndWait(s3BucketName, s3ObjectKey, bundleType, s3ObjectVersion, s3ObjectETag, applicationName, deploymentGroupName, deploymentConfigName, true, waitTimeoutSec, waitIntervalSec); - } - - /** - * The same as {@link #deployRevisionAndWait} but without waiting - */ - public void deployRevision(@NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, - @NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName) { - doDeployAndWait(s3BucketName, s3ObjectKey, bundleType, s3ObjectVersion, s3ObjectETag, applicationName, deploymentGroupName, deploymentConfigName, false, null, null); - } - - @SuppressWarnings("ConstantConditions") - private void doDeployAndWait(@NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, - @NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName, - boolean wait, @Nullable Integer waitTimeoutSec, @Nullable Integer waitIntervalSec) { - try { - final String deploymentId = createDeployment(getRevisionLocation(s3BucketName, s3ObjectKey, bundleType, s3ObjectVersion, s3ObjectETag), applicationName, deploymentGroupName, deploymentConfigName); - - if (wait) { - waitForDeployment(deploymentId, waitTimeoutSec, waitIntervalSec); - } - } catch (Throwable t) { - processFailure(t); - } - } - - private void waitForDeployment(@NotNull String deploymentId, int waitTimeoutSec, int waitIntervalSec) { - myListener.deploymentWaitStarted(deploymentId); - - final GetDeploymentRequest dRequest = new GetDeploymentRequest().withDeploymentId(deploymentId); - - DeploymentInfo dInfo = myCodeDeployClient.getDeployment(dRequest).getDeploymentInfo(); - - long startTime = (dInfo == null || dInfo.getStartTime() == null) ? System.currentTimeMillis() : dInfo.getStartTime().getTime(); - - while (dInfo == null || dInfo.getCompleteTime() == null) { - myListener.deploymentInProgress(deploymentId, getInstancesStatus(dInfo)); - - if (System.currentTimeMillis() - startTime > waitTimeoutSec * 1000) { - myListener.deploymentFailed(deploymentId, waitTimeoutSec, getErrorInfo(dInfo), getInstancesStatus(dInfo)); - return; - } - - try { - Thread.sleep(waitIntervalSec * 1000); - } catch (InterruptedException e) { - processFailure(e); - return; - } - - dInfo = myCodeDeployClient.getDeployment(dRequest).getDeploymentInfo(); - } - - if (isSuccess(dInfo)) { - myListener.deploymentSucceeded(deploymentId, getInstancesStatus(dInfo)); - } else { - myListener.deploymentFailed(deploymentId, null, getErrorInfo(dInfo), getInstancesStatus(dInfo)); - } - } - - private void doUploadRevision(@NotNull File revision, @NotNull String s3BucketName, @NotNull String s3ObjectKey) { - myListener.uploadRevisionStarted(revision, s3BucketName, s3ObjectKey); - - final PutObjectResult putObjectResult = myS3Client.putObject(new PutObjectRequest(s3BucketName, s3ObjectKey, revision)); - - myListener.uploadRevisionFinished(revision, s3BucketName, s3ObjectKey, putObjectResult.getVersionId(), putObjectResult.getETag(), myS3Client.getUrl(s3BucketName, s3ObjectKey).toString()); - } - - @NotNull - private RevisionLocation getRevisionLocation(@NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag) { - final S3Location loc = new S3Location().withBucket(s3BucketName).withKey(s3ObjectKey).withBundleType(bundleType); - if (StringUtil.isNotEmpty(s3ObjectVersion)) loc.withVersion(s3ObjectVersion); - if (StringUtil.isNotEmpty(s3ObjectETag)) loc.withETag(s3ObjectETag); - return new RevisionLocation().withRevisionType(RevisionLocationType.S3).withS3Location(loc); - } - - private void doRegisterRevision(@NotNull RevisionLocation revisionLocation, @NotNull String applicationName) { - final S3Location s3Location = revisionLocation.getS3Location(); - myListener.registerRevisionStarted(applicationName, s3Location.getBucket(), s3Location.getKey(), s3Location.getBundleType(), s3Location.getVersion(), s3Location.getETag()); - - myCodeDeployClient.registerApplicationRevision( - new RegisterApplicationRevisionRequest() - .withRevision(revisionLocation) - .withApplicationName(applicationName) - .withDescription(getDescription("Application revision registered by ", 100))); - - myListener.registerRevisionFinished(applicationName, s3Location.getBucket(), s3Location.getKey(), s3Location.getBundleType(), s3Location.getVersion(), s3Location.getETag()); - } - - @NotNull - private String createDeployment(@NotNull RevisionLocation revisionLocation, - @NotNull String applicationName, - @NotNull String deploymentGroupName, - @Nullable String deploymentConfigName) { - myListener.createDeploymentStarted(applicationName, deploymentGroupName, deploymentConfigName); - - final CreateDeploymentRequest request = - new CreateDeploymentRequest() - .withRevision(revisionLocation) - .withApplicationName(applicationName) - .withDeploymentGroupName(deploymentGroupName) - .withDescription(getDescription("Deployment created by ", 100)); - - if (StringUtil.isNotEmpty(deploymentConfigName)) request.setDeploymentConfigName(deploymentConfigName); - - final String deploymentId = myCodeDeployClient.createDeployment(request).getDeploymentId(); - myListener.createDeploymentFinished(applicationName, deploymentGroupName, deploymentConfigName, deploymentId); - return deploymentId; - } - - private void processFailure(@NotNull Throwable t) { - myListener.exception(new AWSException(t)); - } - - private boolean isSuccess(@NotNull DeploymentInfo dInfo) { - return DeploymentStatus.Succeeded.toString().equals(dInfo.getStatus()); - } - - @NotNull - private String getDescription(@NotNull String prefix, int threshold) { - return prefix + CodeDeployUtil.truncateStringValueWithDotsAtCenter(StringUtil.isEmptyOrSpaces(myDescription) ? getClass().getName() : myDescription, threshold - prefix.length()); - } - - @Contract("null -> null") - @Nullable - private Listener.InstancesStatus getInstancesStatus(@Nullable DeploymentInfo dInfo) { - if (dInfo == null) return null; - if (dInfo.getStatus() == null || dInfo.getDeploymentOverview() == null) return null; - - final Listener.InstancesStatus instancesStatus = new Listener.InstancesStatus(); - instancesStatus.status = getHumanReadableStatus(dInfo.getStatus()); - - final DeploymentOverview overview = dInfo.getDeploymentOverview(); - instancesStatus.succeeded = getInt(overview.getSucceeded()); - instancesStatus.failed = getInt(overview.getFailed()); - instancesStatus.inProgress = getInt(overview.getInProgress()); - instancesStatus.skipped = getInt(overview.getSkipped()); - instancesStatus.pending = getInt(overview.getPending()); - - return instancesStatus; - } - - private static int getInt(@Nullable Long l) { - return l == null ? 0 : l.intValue(); - } - - @NotNull - private String getHumanReadableStatus(@NotNull String status) { - if (DeploymentStatus.Created.toString().equals(status)) return "created"; - if (DeploymentStatus.Queued.toString().equals(status)) return "queued"; - if (DeploymentStatus.InProgress.toString().equals(status)) return "in progress"; - if (DeploymentStatus.Succeeded.toString().equals(status)) return "succeeded"; - if (DeploymentStatus.Failed.toString().equals(status)) return "failed"; - if (DeploymentStatus.Stopped.toString().equals(status)) return "stopped"; - return CodeDeployConstants.STATUS_IS_UNKNOWN; - } - - @Contract("null -> null") - @Nullable - private Listener.ErrorInfo getErrorInfo(@Nullable DeploymentInfo dInfo) { - if (dInfo == null) return null; - - final ErrorInformation errorInformation = dInfo.getErrorInformation(); - if (errorInformation == null) return null; - - final Listener.ErrorInfo errorInfo = new Listener.ErrorInfo(); - errorInfo.message = removeTrailingDot(errorInformation.getMessage()); - errorInfo.code = errorInformation.getCode(); - return errorInfo; - } - - @Contract("null -> null") - @Nullable - private String removeTrailingDot(@Nullable String msg) { - return (msg != null && msg.endsWith(".")) ? msg.substring(0, msg.length() - 1) : msg; - } - - public static class Listener { - void uploadRevisionStarted(@NotNull File revision, @NotNull String s3BucketName, @NotNull String s3ObjectKey) {} - void uploadRevisionFinished(@NotNull File revision, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, @NotNull String url) {} - void registerRevisionStarted(@NotNull String applicationName, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag) {} - void registerRevisionFinished(@NotNull String applicationName, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @NotNull String bundleType, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag) {} - void createDeploymentStarted(@NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName) {} - void createDeploymentFinished(@NotNull String applicationName, @NotNull String deploymentGroupName, @Nullable String deploymentConfigName, @NotNull String deploymentId) {} - void deploymentWaitStarted(@NotNull String deploymentId) {} - void deploymentInProgress(@NotNull String deploymentId, @Nullable InstancesStatus instancesStatus) {} - void deploymentFailed(@NotNull String deploymentId, @Nullable Integer timeoutSec, @Nullable ErrorInfo errorInfo, @Nullable InstancesStatus instancesStatus) {} - void deploymentSucceeded(@NotNull String deploymentId, @Nullable InstancesStatus instancesStatus) {} - void exception(@NotNull AWSException exception) {} - - public static class InstancesStatus { - int pending; - int inProgress; - int succeeded; - int failed; - int skipped; - @Nullable - String status; - } - - public static class ErrorInfo { - @Nullable - String code; - @Nullable - String message; - } - } -} diff --git a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployConstants.java b/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployConstants.java deleted file mode 100644 index 436a289..0000000 --- a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployConstants.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import jetbrains.buildServer.util.CollectionsUtil; - -import java.util.*; - -/** - * @author vbedrosova - */ -public interface CodeDeployConstants { - String RUNNER_TYPE = "aws.codeDeploy"; - String RUNNER_DISPLAY_NAME = "AWS CodeDeploy"; - String RUNNER_DESCR = "Prepare, upload, register and deploy application revision using AWS CodeDeploy"; - - String DEPLOYMENT_ID_BUILD_CONFIG_PARAM = "codedeploy.deployment.id"; - String S3_OBJECT_VERSION_CONFIG_PARAM = "codedeploy.revision.s3.version"; - String S3_OBJECT_ETAG_CONFIG_PARAM = "codedeploy.revision.s3.etag"; - String CUSTOM_APPSPEC_YML_CONFIG_PARAM = "codedeploy.custom.appspec.yml"; - - - String EDIT_PARAMS_HTML = "editCodeDeployParams.html"; - String VIEW_PARAMS_HTML = "viewCodeDeployParams.html"; - String EDIT_PARAMS_JSP = "editCodeDeployParams.jsp"; - String VIEW_PARAMS_JSP = "viewCodeDeployParams.jsp"; - - String DEPLOYMENT_SCENARIOS = "deploymentScenarios"; - - - String TIMEOUT_BUILD_PROBLEM_TYPE = "CODEDEPLOY_TIMEOUT"; - String FAILURE_BUILD_PROBLEM_TYPE = "CODEDEPLOY_FAILURE"; - - - String DEPLOYMENT_STEPS_PARAM = "codedeploy_deployment_steps"; - String DEPLOYMENT_STEPS_LABEL = "Deployment steps"; - - String REVISION_PATHS_PARAM = "codedeploy_revision_paths"; - String REVISION_PATHS_LABEL = "Application revision"; - - String S3_BUCKET_NAME_PARAM = "codedeploy_s3_bucket_name"; - String S3_BUCKET_NAME_LABEL = "S3 bucket"; - String REVISION_PATHS_NOTE = "Ant-style wildcards as well as target directories like out/**/*.zip => dist supported"; - - String S3_OBJECT_KEY_PARAM = "codedeploy_s3_object_key"; - String S3_OBJECT_KEY_LABEL = "S3 object key"; - - String APP_NAME_PARAM = "codedeploy_application_name"; - String APP_NAME_LABEL = "Application name"; - - String DEPLOYMENT_GROUP_NAME_PARAM = "codedeploy_deployment_group_name"; - String DEPLOYMENT_GROUP_NAME_LABEL = "Deployment group"; - String DEPLOYMENT_CONFIG_NAME_PARAM = "codedeploy_deployment_config_name"; - String DEPLOYMENT_CONFIG_NAME_LABEL = "Deployment configuration"; - - String WAIT_FLAG_PARAM = "codedeploy_wait"; - String WAIT_FLAG_LABEL = "Wait for deployment finish"; - String WAIT_TIMEOUT_SEC_PARAM = "codedeploy_wait_timeout_sec"; - String WAIT_TIMEOUT_SEC_LABEL = "Timeout (seconds)"; - String WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM = "codedeploy.wait.poll.interval.sec"; - int WAIT_POLL_INTERVAL_SEC_DEFAULT = 20; - - String UPLOAD_STEP = "s3uploadstep"; - String REGISTER_STEP = "registerstep"; - String DEPLOY_STEP = "deploystep"; - String STEP_SEPARATOR = "_"; - - String UPLOAD_REGISTER_DEPLOY_STEPS = UPLOAD_STEP + STEP_SEPARATOR + REGISTER_STEP + STEP_SEPARATOR + DEPLOY_STEP; - String REGISTER_DEPLOY_STEPS = REGISTER_STEP + STEP_SEPARATOR + DEPLOY_STEP; - String UPLOAD_REGISTER_STEPS = UPLOAD_STEP + STEP_SEPARATOR + REGISTER_STEP; - - Map STEP_LABELS = Collections.unmodifiableMap(CollectionsUtil.asMap( - UPLOAD_REGISTER_DEPLOY_STEPS, "Upload, register and deploy", - REGISTER_DEPLOY_STEPS, "Register and deploy", - UPLOAD_REGISTER_STEPS, "Upload and register", - UPLOAD_STEP, "Upload", - REGISTER_STEP, "Register", - DEPLOY_STEP, "Deploy" - )); - - Map DEFAULTS = Collections.unmodifiableMap(CollectionsUtil.asMap( - WAIT_FLAG_PARAM, "true", - DEPLOYMENT_STEPS_PARAM, UPLOAD_REGISTER_DEPLOY_STEPS - )); - - String STATUS_IS_UNKNOWN = "status is unknown"; - - String MULTILINE_SPLIT_REGEX = " *[,\n\r] *"; - String PATH_SPLIT_REGEX = " *=> *"; - String APPSPEC_YML = "appspec.yml"; -} diff --git a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/ParametersValidator.java b/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/ParametersValidator.java deleted file mode 100644 index 475dda3..0000000 --- a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/ParametersValidator.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import jetbrains.buildServer.parameters.ReferencesResolverUtil; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.util.amazon.AWSCommonParams; -import jetbrains.buildServer.util.amazon.AWSUtil; -import org.jetbrains.annotations.NotNull; - -import static jetbrains.buildServer.runner.codedeploy.CodeDeployConstants.*; -import static jetbrains.buildServer.runner.codedeploy.CodeDeployUtil.*; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * @author vbedrosova - */ -final class ParametersValidator { - /** - * Must be used for parameters validation during the build - * Returns map from parameter name to invalidity reason - */ - @NotNull - static Map validateRuntime(@NotNull Map runnerParams, @NotNull Map configParams, @NotNull File checkoutDir) { - final Map invalids = new HashMap(validate(runnerParams, true)); - - if (!invalids.containsKey(REVISION_PATHS_PARAM) && isUploadStepEnabled(runnerParams)) { - final String revisionPath = getReadyRevision(runnerParams.get(REVISION_PATHS_PARAM)); - if (revisionPath != null && !FileUtil.resolvePath(checkoutDir, revisionPath).exists()) { - invalids.put(REVISION_PATHS_PARAM, REVISION_PATHS_LABEL + " " + revisionPath + " doesn't exist"); - } - } - - if (isDeploymentWaitEnabled(runnerParams)) { - final String waitIntervalSec = configParams.get(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM); - if (StringUtil.isNotEmpty(waitIntervalSec)) { - validatePositiveInteger(invalids, waitIntervalSec, WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, true); - } - } - - return Collections.unmodifiableMap(invalids); - } - - /** - * Returns map from parameter name to invalidity reason - */ - @NotNull - static Map validateSettings(@NotNull Map params) { - return validate(params, false); - } - - private static Map validate(@NotNull Map runnerParams, boolean runtime) { - final Map invalids = new HashMap(); - - invalids.putAll(AWSCommonParams.validate(runnerParams, !runtime)); - - boolean uploadStepEnabled = false; - boolean registerStepEnabled = false; - boolean deployStepEnabled = false; - - final String deploymentSteps = runnerParams.get(DEPLOYMENT_STEPS_PARAM); - if (StringUtil.isEmptyOrSpaces(deploymentSteps)) { - invalids.put(DEPLOYMENT_STEPS_PARAM, DEPLOYMENT_STEPS_LABEL + " mustn't be empty"); - } else { - uploadStepEnabled = isUploadStepEnabled(runnerParams); - registerStepEnabled = isRegisterStepEnabled(runnerParams); - deployStepEnabled = isDeployStepEnabled(runnerParams); - - if (!uploadStepEnabled && !registerStepEnabled && !deployStepEnabled) { - invalids.put(DEPLOYMENT_STEPS_PARAM, DEPLOYMENT_STEPS_LABEL + " has unexpected value " + deploymentSteps); - } - } - - if (uploadStepEnabled) { - final String revisionPaths = runnerParams.get(REVISION_PATHS_PARAM); - if (StringUtil.isEmptyOrSpaces(revisionPaths)) { - invalids.put(REVISION_PATHS_PARAM, REVISION_PATHS_LABEL + " mustn't be empty"); - } else if (!isReference(revisionPaths, runtime)) { - final String readyRevision = getReadyRevision(revisionPaths); - if (readyRevision == null) { - if (getRevisionPathMappings(revisionPaths).isEmpty()) { - invalids.put(REVISION_PATHS_PARAM, REVISION_PATHS_LABEL + " has unexpected value, " + REVISION_PATHS_NOTE); - } - } - } - } - - if (uploadStepEnabled || registerStepEnabled || deployStepEnabled) { - final String s3BucketName = runnerParams.get(S3_BUCKET_NAME_PARAM); - if (StringUtil.isEmptyOrSpaces(s3BucketName)) { - invalids.put(S3_BUCKET_NAME_PARAM, S3_BUCKET_NAME_LABEL + " mustn't be empty"); - } else if (s3BucketName.contains("/")) { - invalids.put(S3_BUCKET_NAME_PARAM, S3_BUCKET_NAME_LABEL + " mustn't contain / characters. For addressing folders use " + S3_OBJECT_KEY_LABEL + " parameter"); - } - - final String s3ObjectKey = runnerParams.get(S3_OBJECT_KEY_PARAM); - if (StringUtil.isEmptyOrSpaces(s3ObjectKey)) { - if (!uploadStepEnabled) { - invalids.put(S3_OBJECT_KEY_PARAM, S3_OBJECT_KEY_LABEL + " mustn't be empty"); - } - } else { - validateS3Key(invalids, s3ObjectKey, S3_OBJECT_KEY_PARAM, S3_OBJECT_KEY_LABEL, runtime); - if (registerStepEnabled || deployStepEnabled) { - validateBundleType(invalids, s3ObjectKey, S3_OBJECT_KEY_PARAM, S3_OBJECT_KEY_LABEL, runtime); - } - } - } - - if (registerStepEnabled || deployStepEnabled) { - if (StringUtil.isEmptyOrSpaces(runnerParams.get(APP_NAME_PARAM))) { - invalids.put(APP_NAME_PARAM, APP_NAME_LABEL + " mustn't be empty"); - } - } - - if (deployStepEnabled) { - if (StringUtil.isEmptyOrSpaces(runnerParams.get(DEPLOYMENT_GROUP_NAME_PARAM))) { - invalids.put(DEPLOYMENT_GROUP_NAME_PARAM, DEPLOYMENT_GROUP_NAME_LABEL + " mustn't be empty"); - } - - if (isDeploymentWaitEnabled(runnerParams)) { - final String waitTimeoutSec = runnerParams.get(WAIT_TIMEOUT_SEC_PARAM); - if (StringUtil.isEmptyOrSpaces(waitTimeoutSec)) { - invalids.put(WAIT_TIMEOUT_SEC_PARAM, WAIT_TIMEOUT_SEC_LABEL + " mustn't be empty"); - } else { - validatePositiveInteger(invalids, waitTimeoutSec, WAIT_TIMEOUT_SEC_PARAM, WAIT_TIMEOUT_SEC_LABEL, runtime); - } - } - } - return invalids; - } - - private static void validatePositiveInteger(@NotNull Map invalids, @NotNull String param, @NotNull String key, @NotNull String name, boolean runtime) { - if (!isReference(param, runtime)) { - try { - final int i = Integer.parseInt(param); - if (i <= 0) { - invalids.put(key, name + " must be a positive integer value"); - } - } catch (NumberFormatException e) { - invalids.put(key, name + " must be a positive integer value"); - } - } - } - - private static void validateS3Key(@NotNull Map invalids, @NotNull String param, @NotNull String key, @NotNull String name, boolean runtime) { - if (!isReference(param, runtime)) { - if (!param.matches("[a-zA-Z_0-9!\\-\\.*'()/]*")) { - invalids.put(key, name + " must contain only safe characters"); - } - } - } - - private static void validateBundleType(@NotNull Map invalids, @NotNull String param, @NotNull String key, @NotNull String name, boolean runtime) { - if (!isReference(param, runtime)) { - if (null == AWSUtil.getBundleType(param)) { - invalids.put(key, name + " provides invalid bundle type, supported bundle types are .zip, .tar and .tar.gz"); - } - } - } - - private static boolean isReference(@NotNull String param, boolean runtime) { - return ReferencesResolverUtil.containsReference(param) && !runtime; - } -} diff --git a/aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtilTest.java b/aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtilTest.java deleted file mode 100644 index 5dabcb2..0000000 --- a/aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtilTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import org.testng.annotations.Test; - -import static jetbrains.buildServer.runner.codedeploy.CodeDeployUtil.getReadyRevision; -import static jetbrains.buildServer.runner.codedeploy.CodeDeployUtil.getRevisionPathMappings; -import static org.assertj.core.api.BDDAssertions.*; - -/** - * @author vbedrosova - */ -public class CodeDeployUtilTest { - @Test - public void ready_revision() { - then(getReadyRevision("ready_revision.zip")).isEqualTo("ready_revision.zip"); - then(getReadyRevision("ready_revision.tar")).isEqualTo("ready_revision.tar"); - then(getReadyRevision("ready_revision.tar.gz")).isEqualTo("ready_revision.tar.gz"); - - then(getReadyRevision("ready_revision.jar")).isNull(); - then(getReadyRevision("**/ready_revision.zip")).isNull(); - then(getReadyRevision("ready_revision.tar\nready_revision.zip")).isNull(); - then(getReadyRevision("ready_revision.tar,ready_revision.zip")).isNull(); - then(getReadyRevision("ready_revision/")).isNull(); - then(getReadyRevision("ready_revision\\")).isNull(); - } - - @Test - public void revision_path_mappings() { - then(getRevisionPathMappings("ready_revision/")).hasSize(1).containsEntry("ready_revision/", ""); - then(getRevisionPathMappings("ready_revision\\")).hasSize(1).containsEntry("ready_revision/", ""); - then(getRevisionPathMappings("ready_revision.zip")).isEmpty(); - then(getRevisionPathMappings("ready_revision.tar")).isEmpty(); - then(getRevisionPathMappings("ready_revision.tar.gz")).isEmpty(); - - then(getRevisionPathMappings("ready_revision.jar")).hasSize(1).containsEntry("ready_revision.jar", ""); - then(getRevisionPathMappings("**/ready_revision.zip")).hasSize(1).containsEntry("**/ready_revision.zip", ""); - then(getRevisionPathMappings("*.zip")).hasSize(1).containsEntry("*.zip", ""); - then(getRevisionPathMappings("abc?.zip")).hasSize(1).containsEntry("abc?.zip", ""); - then(getRevisionPathMappings("ready_revision.tar\nready_revision.zip")).hasSize(2).containsEntry("ready_revision.tar", "").containsEntry("ready_revision.zip", ""); - then(getRevisionPathMappings("ready_revision.tar,ready_revision.zip")).hasSize(2).containsEntry("ready_revision.tar", "").containsEntry("ready_revision.zip", ""); - then(getRevisionPathMappings("ready_revision.zip=>")).hasSize(1).containsEntry("ready_revision.zip", ""); - then(getRevisionPathMappings("ready_revision.zip=>.")).hasSize(1).containsEntry("ready_revision.zip", ""); - then(getRevisionPathMappings("foo\\bar\\baz\\*.html=>x\\y\\z")).hasSize(1).containsEntry("foo/bar/baz/*.html", "x/y/z"); - then(getRevisionPathMappings("foo/bar/baz/=>x/y/z/")).hasSize(1).containsEntry("foo/bar/baz/", "x/y/z"); - then(getRevisionPathMappings("foo\\bar\\baz\\=>x\\y\\z\\")).hasSize(1).containsEntry("foo/bar/baz/", "x/y/z"); - then(getRevisionPathMappings("./foo/bar/baz/../../bar/baz/=>./x/y/z/../../y/z/")).hasSize(1).containsEntry("foo/bar/baz/", "x/y/z"); - then(getRevisionPathMappings("")).hasSize(1).containsEntry("**", ""); - then(getRevisionPathMappings(".")).hasSize(1).containsEntry("**", ""); - then(getRevisionPathMappings("=>.")).hasSize(1).containsEntry("**", ""); - then(getRevisionPathMappings(".=>")).hasSize(1).containsEntry("**", ""); - then(getRevisionPathMappings(".=>.")).hasSize(1).containsEntry("**", ""); -// then(getRevisionPathMappings("=>")).hasSize(1).containsEntry("**", ""); - } -} diff --git a/aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/ParametersValidatorTest.java b/aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/ParametersValidatorTest.java deleted file mode 100644 index 85a4792..0000000 --- a/aws-codedeploy-common/src/test/java/jetbrains/buildServer/runner/codedeploy/ParametersValidatorTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jetbrains.buildServer.runner.codedeploy; - -import jetbrains.buildServer.BaseTestCase; -import jetbrains.buildServer.util.CollectionsUtil; -import org.jetbrains.annotations.NotNull; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.Map; -import static org.assertj.core.api.BDDAssertions.*; -import static jetbrains.buildServer.runner.codedeploy.CodeDeployConstants.*; -import static jetbrains.buildServer.util.amazon.AWSCommonParams.*; - -/** - * @author vbedrosova - */ -public class ParametersValidatorTest extends BaseTestCase { - @Test - public void mandatory_params() { - then(validate()).as("Must detect empty params").hasSize(5). - containsEntry(DEPLOYMENT_STEPS_PARAM, "Deployment steps mustn't be empty"). - containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). - containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). - containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). - containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"); - } - - @Test - public void unexpected_deployment_steps() { - then(validate(DEPLOYMENT_STEPS_PARAM, "abrakadabra")).as("Must detect unexpected deployment steps"). - containsEntry(DEPLOYMENT_STEPS_PARAM, "Deployment steps has unexpected value abrakadabra"); - } - - @Test - public void upload_mandatory_params() { - then(validate(DEPLOYMENT_STEPS_PARAM, UPLOAD_STEP)).as("Must detect empty params").hasSize(6). - containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). - containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). - containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). - containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"). - containsEntry(REVISION_PATHS_PARAM, "Application revision mustn't be empty"). - containsEntry(S3_BUCKET_NAME_PARAM, "S3 bucket mustn't be empty"); - } - - @Test - public void register_mandatory_params() { - then(validate(DEPLOYMENT_STEPS_PARAM, REGISTER_STEP)).as("Must detect empty params").hasSize(7). - containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). - containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). - containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). - containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"). - containsEntry(S3_BUCKET_NAME_PARAM, "S3 bucket mustn't be empty"). - containsEntry(S3_OBJECT_KEY_PARAM, "S3 object key mustn't be empty"). - containsEntry(APP_NAME_PARAM, "Application name mustn't be empty"); - } - - @Test - public void deploy_mandatory_params() { - then(validate(DEPLOYMENT_STEPS_PARAM, DEPLOY_STEP)).as("Must detect empty params").hasSize(8). - containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). - containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). - containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). - containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"). - containsEntry(S3_BUCKET_NAME_PARAM, "S3 bucket mustn't be empty"). - containsEntry(S3_OBJECT_KEY_PARAM, "S3 object key mustn't be empty"). - containsEntry(APP_NAME_PARAM, "Application name mustn't be empty"). - containsEntry(DEPLOYMENT_GROUP_NAME_PARAM, "Deployment group mustn't be empty"); - } - - @Test - public void unexpected_revision_paths() { - then(validate(DEPLOYMENT_STEPS_PARAM, UPLOAD_STEP, REVISION_PATHS_PARAM, "=>")).as("Must detect unexpected revision paths"). - containsEntry(REVISION_PATHS_PARAM, "Application revision has unexpected value, Ant-style wildcards as well as target directories like out/**/*.zip => dist supported"); - } - - @Test - public void s3_bucket_slashes() { - then(validate(DEPLOYMENT_STEPS_PARAM, UPLOAD_STEP, S3_BUCKET_NAME_PARAM, "abra/kadabra")).as("Must detect slashes in s3 bucket name"). - containsEntry(S3_BUCKET_NAME_PARAM, "S3 bucket mustn't contain / characters. For addressing folders use S3 object key parameter"); - } - - @Test - public void s3_object_key_unsafe_chars() { - then(validate(DEPLOYMENT_STEPS_PARAM, UPLOAD_STEP, S3_OBJECT_KEY_PARAM, "abra~kadabra")).as("Must detect unsafe characters in s3 object key"). - containsEntry(S3_OBJECT_KEY_PARAM, "S3 object key must contain only safe characters"); - } - - @Test - public void s3_object_key_unexpected_bundle_type() { - then(validate(DEPLOYMENT_STEPS_PARAM, DEPLOY_STEP, S3_OBJECT_KEY_PARAM, "abrakadabra.jar")).as("Must detect unexpected bundle type in s3 object key"). - containsEntry(S3_OBJECT_KEY_PARAM, "S3 object key provides invalid bundle type, supported bundle types are .zip, .tar and .tar.gz"); - } - - @Test - public void unexpected_wait_timeout() { - then(validate(DEPLOYMENT_STEPS_PARAM, DEPLOY_STEP, WAIT_FLAG_PARAM, "true", WAIT_TIMEOUT_SEC_PARAM, "10min")).as("Must detect unexpected wait timeout"). - containsEntry(WAIT_TIMEOUT_SEC_PARAM, "Timeout (seconds) must be a positive integer value"); - } - - @Test - public void revision_not_fount() throws Exception { - then(validateRuntime( - params(DEPLOYMENT_STEPS_PARAM, UPLOAD_STEP, REVISION_PATHS_PARAM, "ready_revision.zip"), - params())). - as("Must detect no revision"). - containsEntry(REVISION_PATHS_PARAM, "Application revision ready_revision.zip doesn't exist"); - } - - @Test - public void unexpected_wait_poll_interval() throws Exception { - then(validateRuntime( - params(DEPLOYMENT_STEPS_PARAM, DEPLOY_STEP, WAIT_FLAG_PARAM, "true"), - params(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, "50sec"))). - as("Must detect unexpected wait poll interval"). - containsEntry(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, "codedeploy.wait.poll.interval.sec must be a positive integer value"); - } - - @NotNull - private Map validate(String... pairs) { - return ParametersValidator.validateSettings(params(pairs)); - } - - @NotNull - private Map validateRuntime(@NotNull Map runnerParams, @NotNull Map configParams) throws IOException { - return ParametersValidator.validateRuntime(runnerParams, configParams, createTempDir()); - } - - @NotNull - private Map params(String... pairs) { - return CollectionsUtil.asMap(pairs); - } -} diff --git a/aws-codedeploy-server/src/main/resources/buildServerResources/editCodeDeployParams.jsp b/aws-codedeploy-server/src/main/resources/buildServerResources/editCodeDeployParams.jsp deleted file mode 100644 index 978c673..0000000 --- a/aws-codedeploy-server/src/main/resources/buildServerResources/editCodeDeployParams.jsp +++ /dev/null @@ -1,157 +0,0 @@ -<%-- - ~ Copyright 2000-2016 JetBrains s.r.o. - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --%> - -<%@ taglib prefix="l" tagdir="/WEB-INF/tags/layout" %> - - - - -<%@include file="paramsConstants.jspf"%> - - - - - ${deploymentScenarios[upload_register_deploy_steps]} - ${deploymentScenarios[upload_register_steps]} - ${deploymentScenarios[register_deploy_steps]} - ${deploymentScenarios[deploy_step]} - ${deploymentScenarios[upload_step]} - - Upload revision to S3, register it in CodeDeploy application and start deployment - Upload revision to S3 and register it in CodeDeploy application - Register previously uploaded revision in CodeDeploy application and starts deployment - Deploy previously uploaded and registered application revision - Upload application revision to S3 - - - - - - - - - - - Path to a ready-made revision archive or newline-separated list of files to package into revision - ${revision_path_note} - Must include appspec.yml - - - - - - Open S3 Console - Existing S3 bucket name - - - - - - Leave empty to use application revision archive name as a key - Unique path inside the bucket - - - - - - - CodeDeploy Application - - - - Open CodeDeploy Console - Pre-configured CodeDeploy application name - - - - - - Pre-configured EC2 instances, must be running for deployment to succeed - - - - - - e.g. "CodeDeployDefault.OneAtATime", "CodeDeployDefault.AllAtOnce" or a custom one, leave blank for default configuration - - - - - - - - - - Build will fail if the timeout is exceeded - - - - - diff --git a/aws-codedeploy-server/src/main/resources/buildServerResources/paramsConstants.jspf b/aws-codedeploy-server/src/main/resources/buildServerResources/paramsConstants.jspf deleted file mode 100644 index be14e67..0000000 --- a/aws-codedeploy-server/src/main/resources/buildServerResources/paramsConstants.jspf +++ /dev/null @@ -1,59 +0,0 @@ -<%-- - ~ Copyright 2000-2016 JetBrains s.r.o. - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --%> - -<%@ taglib prefix="props" tagdir="/WEB-INF/tags/props" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - -<%@ page import="jetbrains.buildServer.runner.codedeploy.CodeDeployConstants" %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aws-codedeploy-agent/build.gradle b/aws-elasticbeanstalk-agent/build.gradle similarity index 93% rename from aws-codedeploy-agent/build.gradle rename to aws-elasticbeanstalk-agent/build.gradle index 76f717f..847c342 100644 --- a/aws-codedeploy-agent/build.gradle +++ b/aws-elasticbeanstalk-agent/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'com.github.rodm.teamcity-agent' dependencies { - compile project(':aws-codedeploy-common') + compile project(':aws-elasticbeanstalk-common') testCompile "org.jetbrains.teamcity:tests-support:${teamcityVersion}" } diff --git a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunner.java b/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunner.java similarity index 51% rename from aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunner.java rename to aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunner.java index d5da15d..f51c3ef 100644 --- a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunner.java +++ b/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunner.java @@ -14,27 +14,22 @@ * limitations under the License. */ -package jetbrains.buildServer.runner.codedeploy; +package jetbrains.buildServer.runner.elasticbeanstalk; import jetbrains.buildServer.RunBuildException; import jetbrains.buildServer.agent.*; import jetbrains.buildServer.messages.ErrorData; -import jetbrains.buildServer.util.amazon.AWSUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.util.HashMap; import java.util.Map; -import static jetbrains.buildServer.runner.codedeploy.CodeDeployConstants.*; +import static jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkConstants.*; +import static jetbrains.buildServer.util.StringUtil.nullIfEmpty; import static jetbrains.buildServer.util.amazon.AWSCommonParams.*; -import static jetbrains.buildServer.util.StringUtil.*; -/** - * @author vbedrosova - */ -public class CodeDeployRunner implements AgentBuildRunner { +public class ElasticBeanstalkRunner implements AgentBuildRunner { @NotNull @Override public BuildProcess createBuildProcess(@NotNull final AgentRunningBuild runningBuild, @NotNull final BuildRunnerContext context) throws RunBuildException { @@ -49,62 +44,28 @@ protected BuildFinishedStatus runImpl() throws RunBuildException { final Mutable m = new Mutable(configParameters); m.problemOccurred = false; m.s3ObjectVersion = nullIfEmpty(configParameters.get(S3_OBJECT_VERSION_CONFIG_PARAM)); - m.s3ObjectETag = nullIfEmpty(configParameters.get(S3_OBJECT_ETAG_CONFIG_PARAM)); final AWSClient awsClient = createAWSClient(runnerParameters, runningBuild).withListener( - new LoggingDeploymentListener(runnerParameters, runningBuild.getBuildLogger(), runningBuild.getCheckoutDirectory().getAbsolutePath()) { - @Override - protected void problem(int identity, @NotNull String type, @NotNull String descr) { - super.problem(identity, type, descr); - m.problemOccurred = true; - } - - @Override - void uploadRevisionFinished(@NotNull File revision, @NotNull String s3BucketName, @NotNull String s3ObjectKey, @Nullable String s3ObjectVersion, @Nullable String s3ObjectETag, @NotNull String url) { - super.uploadRevisionFinished(revision, s3BucketName, s3ObjectKey, s3ObjectVersion, s3ObjectETag, url); - m.s3ObjectVersion = s3ObjectVersion; - m.s3ObjectETag = s3ObjectETag; - } - }); + new LoggingDeploymentListener(runnerParameters, runningBuild.getBuildLogger(), runningBuild.getCheckoutDirectory().getAbsolutePath())); final String s3BucketName = runnerParameters.get(S3_BUCKET_NAME_PARAM); String s3ObjectKey = runnerParameters.get(S3_OBJECT_KEY_PARAM); - if (CodeDeployUtil.isUploadStepEnabled(runnerParameters) && !m.problemOccurred && !isInterrupted()) { - final File readyRevision = new ApplicationRevision( - isEmptyOrSpaces(s3ObjectKey) ? runningBuild.getBuildTypeExternalId() : s3ObjectKey, - runnerParameters.get(REVISION_PATHS_PARAM), - runningBuild.getCheckoutDirectory(), runningBuild.getBuildTempDirectory(), - configParameters.get(CUSTOM_APPSPEC_YML_CONFIG_PARAM)).withLogger(runningBuild.getBuildLogger()).getArchive(); - - if (isEmptyOrSpaces(s3ObjectKey)) { - s3ObjectKey = readyRevision.getName(); - } - - awsClient.uploadRevision(readyRevision, s3BucketName, s3ObjectKey); - } - final String applicationName = runnerParameters.get(APP_NAME_PARAM); - final String bundleType = "" + AWSUtil.getBundleType(s3ObjectKey); + final String environmentName = runnerParameters.get(ENV_NAME_PARAM); + final String versionLabel = runnerParameters.get(APP_VERSION_PARAM); - if (CodeDeployUtil.isRegisterStepEnabled(runnerParameters) && !m.problemOccurred && !isInterrupted()) { - awsClient.registerRevision(s3BucketName, s3ObjectKey, bundleType, m.s3ObjectVersion, m.s3ObjectETag, applicationName); + if (!m.problemOccurred && !isInterrupted()) { + awsClient.createApplicationVersion(applicationName, versionLabel, s3BucketName, s3ObjectKey); } - if (CodeDeployUtil.isDeployStepEnabled(runnerParameters) && !m.problemOccurred && !isInterrupted()) { - final String deploymentGroupName = runnerParameters.get(DEPLOYMENT_GROUP_NAME_PARAM); - final String deploymentConfigName = nullIfEmpty(runnerParameters.get(DEPLOYMENT_CONFIG_NAME_PARAM)); - - if (CodeDeployUtil.isDeploymentWaitEnabled(runnerParameters)) { - awsClient.deployRevisionAndWait( - s3BucketName, s3ObjectKey, bundleType, m.s3ObjectVersion, m.s3ObjectETag, - applicationName, deploymentGroupName, deploymentConfigName, - Integer.parseInt(runnerParameters.get(WAIT_TIMEOUT_SEC_PARAM)), - getIntegerOrDefault(configParameters.get(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM), WAIT_POLL_INTERVAL_SEC_DEFAULT)); + if (!m.problemOccurred && !isInterrupted()) { + if (ElasticBeanstalkUtil.isDeploymentWaitEnabled(runnerParameters)) { + awsClient.updateEnvironmentAndWait(environmentName, versionLabel, + Integer.parseInt(runnerParameters.get(WAIT_TIMEOUT_SEC_PARAM)), + getIntegerOrDefault(configParameters.get(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM), WAIT_POLL_INTERVAL_SEC_DEFAULT)); } else { - awsClient.deployRevision( - s3BucketName, s3ObjectKey, bundleType, m.s3ObjectVersion, m.s3ObjectETag, - applicationName, deploymentGroupName, deploymentConfigName); + awsClient.updateEnvironment(environmentName, versionLabel); } } @@ -116,7 +77,7 @@ private Map validateParams() throws RunBuildException { final Map runnerParameters = context.getRunnerParameters(); final Map invalids = ParametersValidator.validateRuntime(runnerParameters, context.getConfigParameters(), runningBuild.getCheckoutDirectory()); if (invalids.isEmpty()) return runnerParameters; - throw new CodeDeployRunnerException(CodeDeployUtil.printStrings(invalids.values()), null); + throw new ElasticBeanstalkRunnerException(ElasticBeanstalkUtil.printStrings(invalids.values()), null); } }; } @@ -142,15 +103,15 @@ public boolean canRun(@NotNull BuildAgentConfiguration agentConfiguration) { private AWSClient createAWSClient(final Map runnerParameters, @NotNull final AgentRunningBuild runningBuild) { final Map params = new HashMap(runnerParameters); params.put(TEMP_CREDENTIALS_SESSION_NAME_PARAM, runningBuild.getBuildTypeExternalId() + runningBuild.getBuildId()); - if (CodeDeployUtil.isDeploymentWaitEnabled(runnerParameters)) { + if (ElasticBeanstalkUtil.isDeploymentWaitEnabled(runnerParameters)) { params.put(TEMP_CREDENTIALS_DURATION_SEC_PARAM, String.valueOf(2 * Integer.parseInt(runnerParameters.get(WAIT_TIMEOUT_SEC_PARAM)))); } return new AWSClient(createAWSClients(params, true)).withDescription("TeamCity build \"" + runningBuild.getBuildTypeName() + "\" #" + runningBuild.getBuildNumber()); } - static class CodeDeployRunnerException extends RunBuildException { - public CodeDeployRunnerException(@NotNull String message, @Nullable Throwable cause) { + static class ElasticBeanstalkRunnerException extends RunBuildException { + public ElasticBeanstalkRunnerException(@NotNull String message, @Nullable Throwable cause) { super(message, cause, ErrorData.BUILD_RUNNER_ERROR_TYPE); this.setLogStacktrace(false); } @@ -160,8 +121,8 @@ private class Mutable { public Mutable(@NotNull Map configParameters) { problemOccurred = false; s3ObjectVersion = nullIfEmpty(configParameters.get(S3_OBJECT_VERSION_CONFIG_PARAM)); - s3ObjectETag = nullIfEmpty(configParameters.get(S3_OBJECT_ETAG_CONFIG_PARAM)); } + boolean problemOccurred; String s3ObjectVersion; String s3ObjectETag; diff --git a/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/LoggingDeploymentListener.java b/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/LoggingDeploymentListener.java new file mode 100644 index 0000000..7f2f7ec --- /dev/null +++ b/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/LoggingDeploymentListener.java @@ -0,0 +1,183 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.runner.elasticbeanstalk; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.agent.BuildProgressLogger; +import jetbrains.buildServer.log.Loggers; +import jetbrains.buildServer.util.CollectionsUtil; +import jetbrains.buildServer.util.StringUtil; +import jetbrains.buildServer.util.amazon.AWSCommonParams; +import jetbrains.buildServer.util.amazon.AWSException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +class LoggingDeploymentListener extends AWSClient.Listener { + @NotNull + private static final Logger LOG = Logger.getInstance(Loggers.VCS_CATEGORY + ElasticBeanstalkRunner.class); + + static final String CREATE_VERSION = "create version"; + static final String UPDATE_ENVIRONMENT = "update environment"; + + @NotNull + private final Map myRunnerParameters; + @NotNull + private final BuildProgressLogger myBuildLogger; + @NotNull + private final String myCheckoutDir; + + LoggingDeploymentListener(@NotNull Map runnerParameters, @NotNull BuildProgressLogger buildLogger, @NotNull String checkoutDir) { + myRunnerParameters = runnerParameters; + myBuildLogger = buildLogger; + myCheckoutDir = checkoutDir; + } + + @Override + void createVersionStarted(@NotNull String applicationName, @NotNull String versionLabel, + @NotNull String s3BucketName, @NotNull String s3ObjectKey) { + open(CREATE_VERSION); + log(String.format("Creating application %s version %s with bucket %s and key %s", applicationName, versionLabel, s3BucketName, s3ObjectKey)); + } + + @Override + void createVersionFinished(@NotNull String applicationName, @NotNull String versionLabel, + @NotNull String s3BucketName, @NotNull String s3ObjectKey) { + log(String.format("Created application %s version %s with bucket %s and key %s", applicationName, versionLabel, s3BucketName, s3ObjectKey)); + close(CREATE_VERSION); + } + + @Override + void deploymentStarted(@NotNull String environmentId, @NotNull String applicationName, @NotNull String versionLabel) { + open(UPDATE_ENVIRONMENT); + log(String.format("Started deployment of application %s version %s to %s", applicationName, versionLabel, environmentId)); + } + + @Override + void deploymentWaitStarted(@NotNull String environmentId) { + log("Waiting for deployment finish"); + log(String.format("Waiting for deployment on environment %s", environmentId)); + } + + @Override + void deploymentInProgress(@NotNull String environmentId) { + progress(String.format("Waiting for deployment on environment %s", environmentId)); + } + + @Override + void deploymentFailed(@NotNull String environmentId, @NotNull String applicationName, @NotNull String versionLabel, + @NotNull Boolean hasTimeout, @Nullable ErrorInfo errorInfo) { + String msg = (!hasTimeout ? "Error, " : "Timeout exceeded, "); + + String errMessage = ""; + String errSeverity = ""; + if (errorInfo != null) { + if (StringUtil.isNotEmpty(errorInfo.message)) { + err("Associated error: " + errorInfo.message); + msg += ": " + errorInfo.message; + } + if (StringUtil.isNotEmpty(errorInfo.severity)) { + err("Error severity: " + errorInfo.severity); + } + errMessage = errorInfo.message; + errSeverity = errorInfo.severity; + } + + String failureType = !hasTimeout ? ElasticBeanstalkConstants.FAILURE_BUILD_PROBLEM_TYPE : ElasticBeanstalkConstants.TIMEOUT_BUILD_PROBLEM_TYPE; + + problem(getIdentity(hasTimeout.toString(), errMessage, errSeverity), failureType, msg); + + close(UPDATE_ENVIRONMENT); + } + + @Override + void deploymentSucceeded(@NotNull String environmentId, @NotNull String applicationName, @NotNull String versionLabel) { + String message = String.format("Application %s version %s was deployed successfully to %s", applicationName, versionLabel, environmentId); + log(message); + statusText(message); + close(UPDATE_ENVIRONMENT); + } + + @Override + void exception(@NotNull AWSException e) { + LOG.error(e); + + final String message = e.getMessage(); + final String details = e.getDetails(); + + err(message); + if (StringUtil.isNotEmpty(details)) err(details); + problem(getIdentity(e.getIdentity()), e.getType(), message); + close(UPDATE_ENVIRONMENT); + } + + private int getIdentity(String... parts) { + return AWSCommonParams.calculateIdentity(myCheckoutDir, myRunnerParameters, CollectionsUtil.join(getIdentityFormingParameters(), Arrays.asList(parts))); + } + + @NotNull + private Collection getIdentityFormingParameters() { + return Arrays.asList( + myRunnerParameters.get(ElasticBeanstalkConstants.S3_OBJECT_KEY_PARAM), + myRunnerParameters.get(ElasticBeanstalkConstants.S3_BUCKET_NAME_PARAM), + myRunnerParameters.get(ElasticBeanstalkConstants.ENV_NAME_PARAM), + myRunnerParameters.get(ElasticBeanstalkConstants.APP_NAME_PARAM), + myRunnerParameters.get(ElasticBeanstalkConstants.APP_VERSION_PARAM)); + } + + protected void log(@NotNull String message) { + myBuildLogger.message(message); + } + + protected void err(@NotNull String message) { + myBuildLogger.error(message); + } + + protected void open(@NotNull String block) { + myBuildLogger.targetStarted(block); + } + + protected void close(@NotNull String block) { + myBuildLogger.targetFinished(block); + } + + protected void progress(@NotNull String message) { + myBuildLogger.message(String.format("##teamcity[progressMessage '%s']", escape(message))); + } + + protected void problem(int identity, @NotNull String type, @NotNull String descr) { + myBuildLogger.message(String.format("##teamcity[buildProblem identity='%d' type='%s' description='%s' tc:tags='tc:internal']", identity, type, escape(descr))); + } + + protected void statusText(@NotNull String text) { + myBuildLogger.message(String.format("##teamcity[buildStatus tc:tags='tc:internal' text='{build.status.text}; %s']", text)); + } + + @NotNull + private String escape(@NotNull String s) { + return s. + replace("|", "||"). + replace("'", "|'"). + replace("\n", "|n"). + replace("\r", "|r"). + replace("\\uNNNN", "|0xNNNN"). + replace("[", "|[").replace("]", "|]"); + } +} diff --git a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/SyncBuildProcessAdapter.java b/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/SyncBuildProcessAdapter.java similarity index 91% rename from aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/SyncBuildProcessAdapter.java rename to aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/SyncBuildProcessAdapter.java index 58ecd76..87bef76 100644 --- a/aws-codedeploy-agent/src/main/java/jetbrains/buildServer/runner/codedeploy/SyncBuildProcessAdapter.java +++ b/aws-elasticbeanstalk-agent/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/SyncBuildProcessAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package jetbrains.buildServer.runner.codedeploy; +package jetbrains.buildServer.runner.elasticbeanstalk; import jetbrains.buildServer.RunBuildException; import jetbrains.buildServer.agent.BuildFinishedStatus; @@ -23,9 +23,6 @@ import java.util.concurrent.atomic.AtomicBoolean; -/** - * @author vbedrosova - */ public abstract class SyncBuildProcessAdapter implements BuildProcess { @NotNull private final AtomicBoolean myIsInterrupted = new AtomicBoolean(); @@ -45,7 +42,8 @@ public final void interrupt() { interruptImpl(); } - public void start() throws RunBuildException { } + public void start() throws RunBuildException { + } @NotNull public final BuildFinishedStatus waitFor() throws RunBuildException { @@ -61,5 +59,7 @@ public final BuildFinishedStatus waitFor() throws RunBuildException { @NotNull protected abstract BuildFinishedStatus runImpl() throws RunBuildException; - protected void interruptImpl() { } + + protected void interruptImpl() { + } } diff --git a/aws-codedeploy-agent/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml b/aws-elasticbeanstalk-agent/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml similarity index 80% rename from aws-codedeploy-agent/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml rename to aws-elasticbeanstalk-agent/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml index ba5f0f6..70e4f47 100644 --- a/aws-codedeploy-agent/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml +++ b/aws-elasticbeanstalk-agent/src/main/resources/META-INF/build-agent-plugin-aws-codedeploy-plugin.xml @@ -6,5 +6,5 @@ default-autowire="constructor" > - + diff --git a/aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingDeploymentListenerTest.java b/aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingDeploymentListenerTest.java new file mode 100644 index 0000000..ee659cc --- /dev/null +++ b/aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingDeploymentListenerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.runner.elasticbeanstalk; + +import jetbrains.buildServer.agent.NullBuildProgressLogger; +import jetbrains.buildServer.util.amazon.AWSException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Collections; + +public class LoggingDeploymentListenerTest extends LoggingTestCase { + + private static final String FAKE_ID = "ID-123XYZ"; + private static final String FAKE_APP_NAME = "NAME-FOO"; + private static final String FAKE_APP_VERSION = "1.0.0-alpha1"; + + @BeforeMethod(alwaysRun = true) + public void mySetUp() throws Exception { + super.mySetUp(); + } + + @AfterMethod(alwaysRun = true) + public void myTearDown() throws Exception { + super.myTearDown(); + } + + @Test + public void common_events() throws Exception { + final LoggingDeploymentListener listener = create(); + + final String bucketName = "bucketName"; + final String key = "path/key.zip"; + + listener.createVersionStarted(FAKE_APP_NAME, FAKE_APP_VERSION, bucketName, key); + + listener.createVersionFinished(FAKE_APP_NAME, FAKE_APP_VERSION, bucketName, key); + + listener.deploymentStarted(FAKE_ID, FAKE_APP_NAME, FAKE_APP_VERSION); + + listener.deploymentWaitStarted(FAKE_ID); + + listener.deploymentInProgress(FAKE_ID); + + listener.deploymentSucceeded(FAKE_ID, FAKE_APP_NAME, FAKE_APP_VERSION); + + assertLog( + "OPEN " + LoggingDeploymentListener.CREATE_VERSION, + "LOG Creating application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " with bucket " + bucketName + " and key " + key, + "LOG Created application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " with bucket " + bucketName + " and key " + key, + "CLOSE " + LoggingDeploymentListener.CREATE_VERSION, + "OPEN " + LoggingDeploymentListener.UPDATE_ENVIRONMENT, + "LOG Started deployment of application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " to " + FAKE_ID, + "LOG Waiting for deployment finish", + "LOG Waiting for deployment on environment " + FAKE_ID, + "PROGRESS Waiting for deployment on environment " + FAKE_ID, + "LOG Application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " was deployed successfully to " + FAKE_ID, + "STATUS_TEXT Application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " was deployed successfully to " + FAKE_ID, + "CLOSE " + LoggingDeploymentListener.UPDATE_ENVIRONMENT); + } + + @Test + public void deployment_progress() throws Exception { + create().deploymentInProgress(FAKE_ID); + assertLog("PROGRESS Waiting for deployment on environment " + FAKE_ID); + } + + @Test + public void deployment_succeeded() throws Exception { + create().deploymentSucceeded(FAKE_ID, FAKE_APP_NAME, FAKE_APP_VERSION); + assertLog( + "LOG Application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " was deployed successfully to " + FAKE_ID, + "STATUS_TEXT Application " + FAKE_APP_NAME + " version " + FAKE_APP_VERSION + " was deployed successfully to " + FAKE_ID, + "CLOSE " + LoggingDeploymentListener.UPDATE_ENVIRONMENT); + } + + @Test + public void deployment_failed_timeout() throws Exception { + create().deploymentFailed(FAKE_ID, FAKE_APP_NAME, FAKE_APP_VERSION, true, null); + assertLog( + "PROBLEM identity: 3569038 type: ELASTICBEANSTALK_TIMEOUT descr: Timeout exceeded, ", + "CLOSE update environment"); + } + + @Test + public void deployment_failed() throws Exception { + create().deploymentFailed(FAKE_ID, FAKE_APP_NAME, FAKE_APP_VERSION, false, createError("abc", "Some error message")); + assertLog( + "ERR Associated error: Some error message", + "ERR Error severity: abc", + "PROBLEM identity: 79914740 type: ELASTICBEANSTALK_FAILURE descr: Error, : Some error message", + "CLOSE update environment"); + } + + @Test + public void deployment_exception_type() throws Exception { + create().exception(new AWSException("Some exception message", null, AWSException.EXCEPTION_BUILD_PROBLEM_TYPE, null)); + assertLog( + "ERR Some exception message", + "PROBLEM identity: 2086901196 type: ELASTICBEANSTALK_EXCEPTION descr: Some exception message", + "CLOSE update environment"); + } + + @Test + public void deployment_exception_description_type() throws Exception { + create().exception(new AWSException("Some exception message", null, AWSException.CLIENT_PROBLEM_TYPE, "Some exception details")); + assertLog( + "ERR Some exception message", + "ERR Some exception details", + "PROBLEM identity: 2086901196 type: ELASTICBEANSTALK_CLIENT descr: Some exception message", + "CLOSE update environment"); + } + + @Override + protected void performAfterTestVerification() { + // override parent behaviour + } + + @NotNull + private AWSClient.Listener.ErrorInfo createError(@Nullable String severity, @Nullable String message) { + final AWSClient.Listener.ErrorInfo errorInfo = new AWSClient.Listener.ErrorInfo(); + errorInfo.severity = severity; + errorInfo.message = message; + return errorInfo; + } + + @NotNull + private LoggingDeploymentListener create() { + return new LoggingDeploymentListener(Collections.emptyMap(), + new NullBuildProgressLogger(), + "fake_checkout_dir") { + @Override + protected void log(@NotNull String message) { + logMessage("LOG " + message); + } + + @Override + protected void err(@NotNull String message) { + logMessage("ERR " + message); + } + + @Override + protected void open(@NotNull String block) { + logMessage("OPEN " + block); + } + + @Override + protected void close(@NotNull String block) { + logMessage("CLOSE " + block); + } + + @Override + protected void problem(int identity, @NotNull String type, @NotNull String descr) { + logMessage("PROBLEM identity: " + identity + " type: " + type + " descr: " + descr); + } + + @Override + protected void progress(@NotNull String message) { + logMessage("PROGRESS " + message); + } + + @Override + protected void statusText(@NotNull String text) { + logMessage("STATUS_TEXT " + text); + } + }; + } +} diff --git a/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingTestCase.java b/aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingTestCase.java similarity index 95% rename from aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingTestCase.java rename to aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingTestCase.java index 37d5f74..302b609 100644 --- a/aws-codedeploy-agent/src/test/java/jetbrains.buildServer.runner.codedeploy/LoggingTestCase.java +++ b/aws-elasticbeanstalk-agent/src/test/java/jetbrains.buildServer.runner.elasticbeanstalk/LoggingTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package jetbrains.buildServer.runner.codedeploy; +package jetbrains.buildServer.runner.elasticbeanstalk; import jetbrains.buildServer.BaseTestCase; import jetbrains.buildServer.util.FileUtil; @@ -27,11 +27,8 @@ import java.io.IOException; import java.util.LinkedList; -import static org.assertj.core.api.BDDAssertions.*; +import static org.assertj.core.api.BDDAssertions.then; -/** - * @author vbedrosova - */ abstract class LoggingTestCase extends BaseTestCase { @NotNull private final LinkedList myLog = new LinkedList(); diff --git a/aws-codedeploy-agent/teamcity-plugin.xml b/aws-elasticbeanstalk-agent/teamcity-plugin.xml similarity index 100% rename from aws-codedeploy-agent/teamcity-plugin.xml rename to aws-elasticbeanstalk-agent/teamcity-plugin.xml diff --git a/aws-codedeploy-common/build.gradle b/aws-elasticbeanstalk-common/build.gradle similarity index 100% rename from aws-codedeploy-common/build.gradle rename to aws-elasticbeanstalk-common/build.gradle diff --git a/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/AWSClient.java b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/AWSClient.java new file mode 100644 index 0000000..0c6a876 --- /dev/null +++ b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/AWSClient.java @@ -0,0 +1,241 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.runner.elasticbeanstalk; + +import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalkClient; +import com.amazonaws.services.elasticbeanstalk.model.*; +import jetbrains.buildServer.util.amazon.AWSClients; +import jetbrains.buildServer.util.amazon.AWSException; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AWSClient { + + @NotNull + private AWSElasticBeanstalkClient myElasticBeanstalkClient; + @Nullable + private String myDescription; + @NotNull + private Listener myListener = new Listener(); + + public AWSClient(@NotNull AWSClients clients) { + myElasticBeanstalkClient = clients.createElasticBeanstalkClient(); + } + + @NotNull + public AWSClient withDescription(@NotNull String description) { + myDescription = description; + return this; + } + + @NotNull + public AWSClient withListener(@NotNull Listener listener) { + myListener = listener; + return this; + } + + /** + * Uploads application revision archive to S3 bucket named s3BucketName with the provided key and bundle type. + *

+ * For performing this operation target AWSClient must have corresponding S3 permissions. + * + * @param s3BucketName valid S3 bucket name + * @param s3ObjectKey valid S3 object key + */ + public void createApplicationVersion(@NotNull String applicationName, @NotNull String versionLabel, + @NotNull String s3BucketName, @NotNull String s3ObjectKey) { + try { + myListener.createVersionStarted(applicationName, versionLabel, s3BucketName, s3ObjectKey); + S3Location location = new S3Location().withS3Bucket(s3BucketName).withS3Key(s3ObjectKey); + CreateApplicationVersionRequest request = new CreateApplicationVersionRequest(applicationName, versionLabel) + .withSourceBundle(location); + myElasticBeanstalkClient.createApplicationVersion(request); + myListener.createVersionFinished(applicationName, versionLabel, s3BucketName, s3ObjectKey); + } catch (Throwable t) { + processFailure(t); + } + } + + /** + * Creates deployment of the application revision from the specified location for specified application (must be pre-configured) to + * deploymentGroupName (must be pre-configured) EC2 instances group with + * deploymentConfigName or default configuration name and waits for deployment finish. + *

+ * For performing this operation target AWSClient must have corresponding ElasticBeanstalk permissions. + * + * @param environmentName ElasticBeanstalk environment name + * @param versionLabel ElasticBeanstalk version label + * @param waitTimeoutSec seconds to wait for the created deployment finish or fail + * @param waitIntervalSec seconds between polling ElasticBeanstalk for the created deployment status + */ + public void updateEnvironmentAndWait(@NotNull String environmentName, @NotNull String versionLabel, + int waitTimeoutSec, int waitIntervalSec) { + doUpdateAndWait(environmentName, versionLabel, true, waitTimeoutSec, waitIntervalSec); + } + + /** + * The same as {@link #updateEnvironmentAndWait} but without waiting + */ + public void updateEnvironment(@NotNull String environmentName, @NotNull String versionLabel) { + doUpdateAndWait(environmentName, versionLabel, false, null, null); + } + + @SuppressWarnings("ConstantConditions") + private void doUpdateAndWait(@NotNull String environmentName, @NotNull String versionLabel, + boolean wait, @Nullable Integer waitTimeoutSec, @Nullable Integer waitIntervalSec) { + try { + UpdateEnvironmentRequest request = new UpdateEnvironmentRequest() + .withEnvironmentName(environmentName) + .withVersionLabel(versionLabel); + UpdateEnvironmentResult result = myElasticBeanstalkClient.updateEnvironment(request); + + String environmentId = result.getEnvironmentId(); + + myListener.deploymentStarted(environmentId, environmentName, versionLabel); + + if (wait) { + waitForDeployment(environmentId, versionLabel, waitTimeoutSec, waitIntervalSec); + } + } catch (Throwable t) { + processFailure(t); + } + } + + private void waitForDeployment(@NotNull String environmentId, String versionLabel, int waitTimeoutSec, int waitIntervalSec) { + myListener.deploymentWaitStarted(environmentId); + + EnvironmentDescription environment = getEnvironment(environmentId); + String status = getHumanReadableStatus(environment.getStatus()); + List events = getErrorEvents(environmentId, versionLabel); + boolean hasError = events.size() > 0; + + long startTime = System.currentTimeMillis(); + + while (status.equals("updating") && !hasError) { + myListener.deploymentInProgress(environmentId); + + if (System.currentTimeMillis() - startTime > waitTimeoutSec * 1000) { + myListener.deploymentFailed(environmentId, environment.getApplicationName(), versionLabel, true, null); + return; + } + + try { + Thread.sleep(waitIntervalSec * 1000); + } catch (InterruptedException e) { + processFailure(e); + return; + } + + environment = getEnvironment(environmentId); + status = getHumanReadableStatus(environment.getStatus()); + events = getErrorEvents(environmentId, versionLabel); + hasError = events.size() > 0; + } + + if (isSuccess(environment, versionLabel)) { + myListener.deploymentSucceeded(environmentId, environment.getApplicationName(), versionLabel); + } else { + Listener.ErrorInfo errorEvent = events.size() > 0 ? getErrorInfo(events.get(0)) : null; + myListener.deploymentFailed(environmentId, environment.getApplicationName(), versionLabel, false, errorEvent); + } + } + + public EnvironmentDescription getEnvironment(@NotNull String environmentId) { + return myElasticBeanstalkClient.describeEnvironments(new DescribeEnvironmentsRequest().withEnvironmentIds(environmentId)) + .getEnvironments().get(0); + } + + private List getErrorEvents(@NotNull String environmentId, String versionLabel) { + return myElasticBeanstalkClient.describeEvents(new DescribeEventsRequest() + .withEnvironmentId(environmentId) + .withMaxRecords(10) + .withVersionLabel(versionLabel) + .withSeverity(EventSeverity.ERROR)) + .getEvents(); + } + + private boolean isSuccess(@NotNull EnvironmentDescription environment, @NotNull String versionLabel) { + return environment.getVersionLabel().equals(versionLabel); + } + + private void processFailure(@NotNull Throwable t) { + myListener.exception(new AWSException(t)); + } + + @NotNull + private String getHumanReadableStatus(@NotNull String status) { + if (EnvironmentStatus.Launching.toString().equals(status)) return "launching"; + if (EnvironmentStatus.Updating.toString().equals(status)) return "updating"; + if (EnvironmentStatus.Ready.toString().equals(status)) return "ready"; + if (EnvironmentStatus.Terminating.toString().equals(status)) return "terminating"; + if (EnvironmentStatus.Terminated.toString().equals(status)) return "terminated"; + return ElasticBeanstalkConstants.STATUS_IS_UNKNOWN; + } + + @NotNull + private Listener.ErrorInfo getErrorInfo(@NotNull EventDescription event) { + final Listener.ErrorInfo errorInfo = new Listener.ErrorInfo(); + errorInfo.message = removeTrailingDot(event.getMessage()); + errorInfo.severity = event.getSeverity(); + return errorInfo; + } + + @Contract("null -> null") + @Nullable + private String removeTrailingDot(@Nullable String msg) { + return (msg != null && msg.endsWith(".")) ? msg.substring(0, msg.length() - 1) : msg; + } + + public static class Listener { + void createVersionStarted(@NotNull String applicationName, @NotNull String versionLabel, + @NotNull String s3BucketName, @NotNull String s3ObjectKey) { + } + + void createVersionFinished(@NotNull String applicationName, @NotNull String versionLabel, + @NotNull String s3BucketName, @NotNull String s3ObjectKey) { + } + + void deploymentStarted(@NotNull String environmentId, @NotNull String applicationName, @NotNull String versionLabel) { + } + + void deploymentWaitStarted(@NotNull String environmentId) { + } + + void deploymentInProgress(@NotNull String environmentId) { + } + + void deploymentFailed(@NotNull String environmentId, @NotNull String applicationName, @NotNull String versionLabel, + @NotNull Boolean hasTimeout, @Nullable ErrorInfo errorInfo) { + } + + void deploymentSucceeded(@NotNull String environmentId, @NotNull String applicationName, @NotNull String versionLabel) { + } + + void exception(@NotNull AWSException exception) { + } + + public static class ErrorInfo { + @Nullable + String severity; + @Nullable + String message; + } + } +} diff --git a/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkConstants.java b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkConstants.java new file mode 100644 index 0000000..31976d8 --- /dev/null +++ b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkConstants.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.runner.elasticbeanstalk; + +public interface ElasticBeanstalkConstants { + String RUNNER_TYPE = "aws.elasticBeanstalk"; + String RUNNER_DISPLAY_NAME = "AWS Elastic Beanstalk"; + String RUNNER_DESCR = "Create, and update application version using AWS ElasticBeanstalk"; + + String DEPLOYMENT_ID_BUILD_CONFIG_PARAM = "elasticbeanstalk.deployment.id"; + String S3_OBJECT_VERSION_CONFIG_PARAM = "elasticbeanstalk.revision.s3.version"; + + String EDIT_PARAMS_HTML = "editElasticBeanstalkParams.html"; + String VIEW_PARAMS_HTML = "viewElasticBeanstalkParams.html"; + String EDIT_PARAMS_JSP = "editElasticBeanstalkParams.jsp"; + String VIEW_PARAMS_JSP = "viewElasticBeanstalkParams.jsp"; + + String TIMEOUT_BUILD_PROBLEM_TYPE = "ELASTICBEANSTALK_TIMEOUT"; + String FAILURE_BUILD_PROBLEM_TYPE = "ELASTICBEANSTALK_FAILURE"; + + String S3_BUCKET_NAME_PARAM = "elasticbeanstalk_s3_bucket_name"; + String S3_BUCKET_NAME_LABEL = "S3 bucket"; + + String S3_OBJECT_KEY_PARAM = "elasticbeanstalk_s3_object_key"; + String S3_OBJECT_KEY_LABEL = "S3 object key"; + + String ENV_NAME_PARAM = "elasticbeanstalk_environment_name"; + String ENV_NAME_LABEL = "Environment Name"; + + String APP_NAME_PARAM = "elasticbeanstalk_appname_label"; + String APP_NAME_LABEL = "Application Name"; + + String APP_VERSION_PARAM = "elasticbeanstalk_version_label"; + String APP_VERSION_LABEL = "Application Version"; + + String WAIT_FLAG_PARAM = "elasticbeanstalk_wait"; + String WAIT_FLAG_LABEL = "Wait for deployment finish"; + String WAIT_TIMEOUT_SEC_PARAM = "elasticbeanstalk_wait_timeout_sec"; + String WAIT_TIMEOUT_SEC_LABEL = "Timeout (seconds)"; + String WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM = "elasticbeanstalk.wait.poll.interval.sec"; + int WAIT_POLL_INTERVAL_SEC_DEFAULT = 20; + + String STATUS_IS_UNKNOWN = "status is unknown"; +} diff --git a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtil.java b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkUtil.java similarity index 50% rename from aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtil.java rename to aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkUtil.java index 57314cf..58b4fe9 100644 --- a/aws-codedeploy-common/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployUtil.java +++ b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkUtil.java @@ -14,73 +14,23 @@ * limitations under the License. */ -package jetbrains.buildServer.runner.codedeploy; +package jetbrains.buildServer.runner.elasticbeanstalk; import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.util.PathMappings; import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.util.amazon.AWSUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collection; +import java.util.Map; -import static jetbrains.buildServer.runner.codedeploy.CodeDeployConstants.*; +import static jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkConstants.WAIT_FLAG_PARAM; -/** - * @author vbedrosova - */ -final class CodeDeployUtil { - static boolean isUploadStepEnabled(@NotNull Map params) { - return isStepEnabled(UPLOAD_STEP, params); - } - - static boolean isRegisterStepEnabled(@NotNull Map params) { - return isStepEnabled(REGISTER_STEP, params); - } - - static boolean isDeployStepEnabled(@NotNull Map params) { - return isStepEnabled(DEPLOY_STEP, params); - } +final class ElasticBeanstalkUtil { static boolean isDeploymentWaitEnabled(@NotNull Map params) { - return isDeployStepEnabled(params) && Boolean.parseBoolean(params.get(WAIT_FLAG_PARAM)); - } - - private static boolean isStepEnabled(@NotNull String step, @NotNull Map params) { - final String steps = params.get(DEPLOYMENT_STEPS_PARAM); - return steps != null && steps.contains(step); - } - - @Nullable - static String getReadyRevision(@NotNull String revisionPathsParam) { - final String[] split = revisionPathsParam.trim().split(MULTILINE_SPLIT_REGEX); - if (split.length == 1) { - final String revisionPath = split[0]; - if (PathMappings.isWildcard(revisionPath)) return null; - if (null == AWSUtil.getBundleType(revisionPath)) return null; - return revisionPath; - } - return null; - } - - @NotNull - static Map getRevisionPathMappings(@NotNull String revisionPathsParam) { - final String readyRevision = getReadyRevision(revisionPathsParam); - if (readyRevision == null) { - final Map dest = new LinkedHashMap(); - for (String path : revisionPathsParam.trim().split(MULTILINE_SPLIT_REGEX)) { - final String[] parts = path.split(PATH_SPLIT_REGEX); - if (parts.length > 0) { - dest.put( - normalize(parts[0], true), - parts.length == 1 ? StringUtil.EMPTY : normalize(parts[1], false)); - } - } - return Collections.unmodifiableMap(dest); - } - return Collections.emptyMap(); + return Boolean.parseBoolean(params.get(WAIT_FLAG_PARAM)); } @NotNull diff --git a/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidator.java b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidator.java new file mode 100644 index 0000000..b73becc --- /dev/null +++ b/aws-elasticbeanstalk-common/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidator.java @@ -0,0 +1,126 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.runner.elasticbeanstalk; + +import jetbrains.buildServer.parameters.ReferencesResolverUtil; +import jetbrains.buildServer.util.StringUtil; +import jetbrains.buildServer.util.amazon.AWSCommonParams; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkConstants.*; +import static jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkUtil.isDeploymentWaitEnabled; + +final class ParametersValidator { + /** + * Must be used for parameters validation during the build + * Returns map from parameter name to invalidity reason + */ + @NotNull + static Map validateRuntime(@NotNull Map runnerParams, @NotNull Map configParams, @NotNull File checkoutDir) { + final Map invalids = new HashMap(validate(runnerParams, true)); + + if (isDeploymentWaitEnabled(runnerParams)) { + final String waitIntervalSec = configParams.get(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM); + if (StringUtil.isNotEmpty(waitIntervalSec)) { + validatePositiveInteger(invalids, waitIntervalSec, WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, true); + } + } + + return Collections.unmodifiableMap(invalids); + } + + /** + * Returns map from parameter name to invalidity reason + */ + @NotNull + static Map validateSettings(@NotNull Map params) { + return validate(params, false); + } + + private static Map validate(@NotNull Map runnerParams, boolean runtime) { + final Map invalids = new HashMap(); + + invalids.putAll(AWSCommonParams.validate(runnerParams, !runtime)); + + final String s3BucketName = runnerParams.get(S3_BUCKET_NAME_PARAM); + if (StringUtil.isEmptyOrSpaces(s3BucketName)) { + invalids.put(S3_BUCKET_NAME_PARAM, S3_BUCKET_NAME_LABEL + " mustn't be empty"); + } else if (s3BucketName.contains("/")) { + invalids.put(S3_BUCKET_NAME_PARAM, S3_BUCKET_NAME_LABEL + " mustn't contain / characters. For addressing folders use " + S3_OBJECT_KEY_LABEL + " parameter"); + } + + final String s3ObjectKey = runnerParams.get(S3_OBJECT_KEY_PARAM); + if (StringUtil.isEmptyOrSpaces(s3ObjectKey)) { + invalids.put(S3_OBJECT_KEY_PARAM, S3_OBJECT_KEY_LABEL + " mustn't be empty"); + } else { + validateS3Key(invalids, s3ObjectKey, S3_OBJECT_KEY_PARAM, S3_OBJECT_KEY_LABEL, runtime); + } + + if (StringUtil.isEmptyOrSpaces(runnerParams.get(ENV_NAME_PARAM))) { + invalids.put(ENV_NAME_PARAM, ENV_NAME_LABEL + " mustn't be empty"); + } + + if (StringUtil.isEmptyOrSpaces(runnerParams.get(APP_NAME_PARAM))) { + invalids.put(APP_NAME_PARAM, APP_NAME_LABEL + " mustn't be empty"); + } + + if (StringUtil.isEmptyOrSpaces(runnerParams.get(APP_VERSION_PARAM))) { + invalids.put(APP_VERSION_PARAM, APP_VERSION_LABEL + " mustn't be empty"); + } + + if (isDeploymentWaitEnabled(runnerParams)) { + final String waitTimeoutSec = runnerParams.get(WAIT_TIMEOUT_SEC_PARAM); + if (StringUtil.isEmptyOrSpaces(waitTimeoutSec)) { + invalids.put(WAIT_TIMEOUT_SEC_PARAM, WAIT_TIMEOUT_SEC_LABEL + " mustn't be empty"); + } else { + validatePositiveInteger(invalids, waitTimeoutSec, WAIT_TIMEOUT_SEC_PARAM, WAIT_TIMEOUT_SEC_LABEL, runtime); + } + } + + return invalids; + } + + private static void validatePositiveInteger(@NotNull Map invalids, @NotNull String param, @NotNull String key, @NotNull String name, boolean runtime) { + if (!isReference(param, runtime)) { + try { + final int i = Integer.parseInt(param); + if (i <= 0) { + invalids.put(key, name + " must be a positive integer value"); + } + } catch (NumberFormatException e) { + invalids.put(key, name + " must be a positive integer value"); + } + } + } + + private static void validateS3Key(@NotNull Map invalids, @NotNull String param, @NotNull String key, @NotNull String name, boolean runtime) { + if (!isReference(param, runtime)) { + if (!param.matches("[a-zA-Z_0-9!\\-\\.*'()/]*")) { + invalids.put(key, name + " must contain only safe characters"); + } + } + } + + private static boolean isReference(@NotNull String param, boolean runtime) { + return ReferencesResolverUtil.containsReference(param) && !runtime; + } +} diff --git a/aws-elasticbeanstalk-common/src/test/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidatorTest.java b/aws-elasticbeanstalk-common/src/test/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidatorTest.java new file mode 100644 index 0000000..5a33299 --- /dev/null +++ b/aws-elasticbeanstalk-common/src/test/java/jetbrains/buildServer/runner/elasticbeanstalk/ParametersValidatorTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.buildServer.runner.elasticbeanstalk; + +import jetbrains.buildServer.BaseTestCase; +import jetbrains.buildServer.util.CollectionsUtil; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.Map; + +import static jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkConstants.*; +import static jetbrains.buildServer.util.amazon.AWSCommonParams.*; +import static org.assertj.core.api.BDDAssertions.then; + +public class ParametersValidatorTest extends BaseTestCase { + @Test + public void mandatory_params() { + then(validate()).as("Must detect empty params").hasSize(9). + containsEntry(APP_NAME_PARAM, "Application Name mustn't be empty"). + containsEntry(ENV_NAME_PARAM, "Environment Name mustn't be empty"). + containsEntry(S3_BUCKET_NAME_PARAM, "S3 bucket mustn't be empty"). + containsEntry(S3_OBJECT_KEY_PARAM, "S3 object key mustn't be empty"). + containsEntry(APP_VERSION_PARAM, "Application Version mustn't be empty"). + containsEntry(REGION_NAME_PARAM, "AWS region mustn't be empty"). + containsEntry(CREDENTIALS_TYPE_PARAM, "Credentials type mustn't be empty"). + containsEntry(ACCESS_KEY_ID_PARAM, "Access key ID mustn't be empty"). + containsEntry(SECURE_SECRET_ACCESS_KEY_PARAM, "Secret access key mustn't be empty"); + } + + @Test + public void s3_bucket_slashes() { + then(validate(S3_BUCKET_NAME_PARAM, "abra/kadabra")).as("Must detect slashes in s3 bucket name"). + containsEntry(S3_BUCKET_NAME_PARAM, "S3 bucket mustn't contain / characters. For addressing folders use S3 object key parameter"); + } + + @Test + public void s3_object_key_unsafe_chars() { + then(validate(S3_OBJECT_KEY_PARAM, "abra~kadabra")).as("Must detect unsafe characters in s3 object key"). + containsEntry(S3_OBJECT_KEY_PARAM, "S3 object key must contain only safe characters"); + } + + @Test + public void unexpected_wait_timeout() { + then(validate(WAIT_FLAG_PARAM, "true", WAIT_TIMEOUT_SEC_PARAM, "10min")).as("Must detect unexpected wait timeout"). + containsEntry(WAIT_TIMEOUT_SEC_PARAM, "Timeout (seconds) must be a positive integer value"); + } + + @Test + public void unexpected_wait_poll_interval() throws Exception { + then(validateRuntime( + params(WAIT_FLAG_PARAM, "true"), + params(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, "50sec"))). + as("Must detect unexpected wait poll interval"). + containsEntry(WAIT_POLL_INTERVAL_SEC_CONFIG_PARAM, "elasticbeanstalk.wait.poll.interval.sec must be a positive integer value"); + } + + @NotNull + private Map validate(String... pairs) { + return ParametersValidator.validateSettings(params(pairs)); + } + + @NotNull + private Map validateRuntime(@NotNull Map runnerParams, @NotNull Map configParams) throws IOException { + return ParametersValidator.validateRuntime(runnerParams, configParams, createTempDir()); + } + + @NotNull + private Map params(String... pairs) { + return CollectionsUtil.asMap(pairs); + } +} diff --git a/aws-codedeploy-server/build.gradle b/aws-elasticbeanstalk-server/build.gradle similarity index 93% rename from aws-codedeploy-server/build.gradle rename to aws-elasticbeanstalk-server/build.gradle index 888ef12..9b31a85 100644 --- a/aws-codedeploy-server/build.gradle +++ b/aws-elasticbeanstalk-server/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'com.github.rodm.teamcity-server' dependencies { - compile project(':aws-codedeploy-common') + compile project(':aws-elasticbeanstalk-common') } teamcity { diff --git a/aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployBuildProblemTypes.java b/aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkBuildProblemTypes.java similarity index 64% rename from aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployBuildProblemTypes.java rename to aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkBuildProblemTypes.java index 7c28ba6..85f2808 100644 --- a/aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployBuildProblemTypes.java +++ b/aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkBuildProblemTypes.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package jetbrains.buildServer.runner.codedeploy; +package jetbrains.buildServer.runner.elasticbeanstalk; import jetbrains.buildServer.ExtensionHolder; import jetbrains.buildServer.serverSide.problems.BaseBuildProblemTypeDetailsProvider; @@ -24,14 +24,11 @@ import java.util.Map; -/** - * @author vbedrosova - */ -public class CodeDeployBuildProblemTypes { +public class ElasticBeanstalkBuildProblemTypes { - public CodeDeployBuildProblemTypes(@NotNull ExtensionHolder extensionHolder) { - register(CodeDeployConstants.TIMEOUT_BUILD_PROBLEM_TYPE, "CodeDeploy timeout", extensionHolder); - register(CodeDeployConstants.FAILURE_BUILD_PROBLEM_TYPE, "CodeDeploy failure", extensionHolder); + public ElasticBeanstalkBuildProblemTypes(@NotNull ExtensionHolder extensionHolder) { + register(ElasticBeanstalkConstants.TIMEOUT_BUILD_PROBLEM_TYPE, "ElasticBeanstalk timeout", extensionHolder); + register(ElasticBeanstalkConstants.FAILURE_BUILD_PROBLEM_TYPE, "ElasticBeanstalk failure", extensionHolder); for (Map.Entry e : AWSException.PROBLEM_TYPES.entrySet()) register(e.getKey(), e.getValue(), extensionHolder); @@ -39,19 +36,19 @@ public CodeDeployBuildProblemTypes(@NotNull ExtensionHolder extensionHolder) { private static void register(@NotNull final String type, @NotNull final String descr, @NotNull final ExtensionHolder extensionHolder) { extensionHolder.registerExtension(BuildProblemTypeDetailsProvider.class, - type, - new BaseBuildProblemTypeDetailsProvider() { - @NotNull - public String getType() { - return type; - } - - @NotNull - @Override - public String getTypeDescription() { - return descr; - } - }); + type, + new BaseBuildProblemTypeDetailsProvider() { + @NotNull + public String getType() { + return type; + } + + @NotNull + @Override + public String getTypeDescription() { + return descr; + } + }); } } diff --git a/aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunType.java b/aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunType.java similarity index 82% rename from aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunType.java rename to aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunType.java index b02954c..28b35ea 100644 --- a/aws-codedeploy-server/src/main/java/jetbrains/buildServer/runner/codedeploy/CodeDeployRunType.java +++ b/aws-elasticbeanstalk-server/src/main/java/jetbrains/buildServer/runner/elasticbeanstalk/ElasticBeanstalkRunType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package jetbrains.buildServer.runner.codedeploy; +package jetbrains.buildServer.runner.elasticbeanstalk; import jetbrains.buildServer.controllers.BaseController; import jetbrains.buildServer.serverSide.*; @@ -33,12 +33,9 @@ import java.util.HashMap; import java.util.Map; -import static jetbrains.buildServer.runner.codedeploy.CodeDeployConstants.*; +import static jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkConstants.*; -/** - * @author vbedrosova - */ -public class CodeDeployRunType extends RunType { +public class ElasticBeanstalkRunType extends RunType { @NotNull private final String myEditParamsPath; @NotNull @@ -46,10 +43,10 @@ public class CodeDeployRunType extends RunType { @NotNull private final AWSCommonParams myAWSCommonParams; - public CodeDeployRunType(@NotNull RunTypeRegistry registry, - @NotNull PluginDescriptor descriptor, - @NotNull WebControllerManager controllerManager, - @NotNull AWSCommonParams awsCommonParams) { + public ElasticBeanstalkRunType(@NotNull RunTypeRegistry registry, + @NotNull PluginDescriptor descriptor, + @NotNull WebControllerManager controllerManager, + @NotNull AWSCommonParams awsCommonParams) { registry.registerRunType(this); myAWSCommonParams = awsCommonParams; @@ -68,9 +65,7 @@ private static String registerController(@NotNull final PluginDescriptor descrip @Nullable @Override protected ModelAndView doHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws Exception { - final ModelAndView mv = new ModelAndView(descriptor.getPluginResourcesPath(jspPath)); - mv.getModel().put(DEPLOYMENT_SCENARIOS, STEP_LABELS); - return mv; + return new ModelAndView(descriptor.getPluginResourcesPath(jspPath)); } }); return resolvedHtmlPath; @@ -95,7 +90,7 @@ public InvalidProperty createFrom(@NotNull Map.Entry source) { @NotNull @Override public Map getDefaultRunnerProperties() { - final Map defaults = new HashMap(DEFAULTS); + final Map defaults = new HashMap(); defaults.putAll(myAWSCommonParams.getDefaults()); return defaults; } @@ -134,9 +129,6 @@ public String getViewRunnerParamsJspFilePath() { @Override public String describeParameters(@NotNull Map parameters) { final Map invalids = ParametersValidator.validateSettings(parameters); - return - invalids.isEmpty() ? - STEP_LABELS.get(parameters.get(DEPLOYMENT_STEPS_PARAM)) + " application revision" : - CodeDeployUtil.printStrings(invalids.values()); + return invalids.isEmpty() ? "Application revision" : ElasticBeanstalkUtil.printStrings(invalids.values()); } } diff --git a/aws-codedeploy-server/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml b/aws-elasticbeanstalk-server/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml similarity index 78% rename from aws-codedeploy-server/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml rename to aws-elasticbeanstalk-server/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml index 70c3644..ab44876 100644 --- a/aws-codedeploy-server/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml +++ b/aws-elasticbeanstalk-server/src/main/resources/META-INF/build-server-plugin-aws-codedeploy-plugin.xml @@ -21,6 +21,8 @@ default-autowire="constructor"> - - + + diff --git a/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/editElasticBeanstalkParams.jsp b/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/editElasticBeanstalkParams.jsp new file mode 100644 index 0000000..1fce62c --- /dev/null +++ b/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/editElasticBeanstalkParams.jsp @@ -0,0 +1,91 @@ +<%-- + ~ Copyright 2000-2016 JetBrains s.r.o. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --%> + +<%@ taglib prefix="l" tagdir="/WEB-INF/tags/layout" %> + + + + +<%@include file="paramsConstants.jspf"%> + + + + + + + Open S3 Console + Existing S3 bucket name + + + + + + Unique path inside the bucket + + + + + + + ElasticBeanstalk Application + + + + Open ElasticBeanstalk Console + Pre-configured ElasticBeanstalk environment name + + + + + Open ElasticBeanstalk Console + Pre-configured ElasticBeanstalk application name + + + + + + Version Label to Publish + + + + + + + + + + Build will fail if the timeout is exceeded + + + + diff --git a/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/paramsConstants.jspf b/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/paramsConstants.jspf new file mode 100644 index 0000000..15ff33a --- /dev/null +++ b/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/paramsConstants.jspf @@ -0,0 +1,43 @@ +<%-- + ~ Copyright 2000-2016 JetBrains s.r.o. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --%> + +<%@ taglib prefix="props" tagdir="/WEB-INF/tags/props" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +<%@ page import="jetbrains.buildServer.runner.elasticbeanstalk.ElasticBeanstalkConstants" %> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aws-codedeploy-server/src/main/resources/buildServerResources/viewCodeDeployParams.jsp b/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/viewElasticBeanstalkParams.jsp similarity index 74% rename from aws-codedeploy-server/src/main/resources/buildServerResources/viewCodeDeployParams.jsp rename to aws-elasticbeanstalk-server/src/main/resources/buildServerResources/viewElasticBeanstalkParams.jsp index bdc622e..04c7b9a 100644 --- a/aws-codedeploy-server/src/main/resources/buildServerResources/viewCodeDeployParams.jsp +++ b/aws-elasticbeanstalk-server/src/main/resources/buildServerResources/viewElasticBeanstalkParams.jsp @@ -16,17 +16,8 @@ <%@include file="paramsConstants.jspf"%> - -

- ${deployment_steps_label}: ${empty deploymentSteps ? 'empty' : deploymentSteps} -
- -
- ${revision_path_label}: -
-
${bucket_name_label}:
@@ -39,15 +30,15 @@
- ${app_name_label}: + ${env_name_label}:
- ${dep_group_name_label}: + ${app_name_label}:
- ${dep_config_name_label}: + ${app_version_label}:
@@ -60,5 +51,3 @@ ${wait_timeout_label}:
- - diff --git a/build.gradle b/build.gradle index 7d201ba..aed6717 100644 --- a/build.gradle +++ b/build.gradle @@ -28,8 +28,8 @@ buildscript { ext.teamcityVersion = hasProperty('teamcity.version') ? property('teamcity.version') : '10.0-SNAPSHOT' -ext.teamcityDir = hasProperty('teamcity.dir') ? property('teamcity.dir') : "$rootDir/teamcity/servers/TeamCity-${teamcityVersion}" -ext.teamcityDataDir = "$rootDir/teamcity/data/" + teamcityVersion +ext.teamcityDir = hasProperty('teamcity.dir') ? property('teamcity.dir') : "$rootDir/teamcity/app" +ext.teamcityDataDir = "$rootDir/teamcity/data" ext.teamcityJavaHome = System.properties['java.home'] ext.awsSDKVersion = hasProperty('aws.sdk.version') ? property('aws.sdk.version') : '1.11.4' @@ -45,7 +45,7 @@ idea { } subprojects { - group = 'jetbrains.buildServer.codedeploy' + group = 'jetbrains.buildServer.elasticbeanstalk' } configure(subprojects - project(':build')) { diff --git a/build/build.gradle b/build/build.gradle index d88495a..2484161 100644 --- a/build/build.gradle +++ b/build/build.gradle @@ -19,19 +19,19 @@ apply plugin: 'com.github.rodm.teamcity-server' dependencies { agent project(':amazon-util') - agent project(':aws-codedeploy-common') - agent project(':aws-codedeploy-agent') + agent project(':aws-elasticbeanstalk-common') + agent project(':aws-elasticbeanstalk-agent') server project(':amazon-util') - server project(':aws-codedeploy-common') - server project(':aws-codedeploy-server') + server project(':aws-elasticbeanstalk-common') + server project(':aws-elasticbeanstalk-server') } def awsSDKFiles = {project(':amazon-util').configurations.compile.files({it.group == 'com.amazonaws' && it.name == 'aws-java-sdk'})} teamcity { agent { - descriptor = project(':aws-codedeploy-agent').file('teamcity-plugin.xml') + descriptor = project(':aws-elasticbeanstalk-agent').file('teamcity-plugin.xml') files { into('lib') { from awsSDKFiles() @@ -41,7 +41,7 @@ teamcity { server { descriptor = file("$rootDir/teamcity-plugin.xml") - tokens = [Plugin_Version: 'SNAPSHOT-' + new Date().format('yyyyMMddHHmm')] + tokens = [Plugin_Version: '1.0.0-alpha1'] files { into('server') { from awsSDKFiles() diff --git a/settings.gradle b/settings.gradle index 68f972a..a532b13 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,11 +14,10 @@ * limitations under the License. */ -rootProject.name = 'aws-codedeploy-plugin' +rootProject.name = 'aws-elasticbeanstalk-plugin' -include 'aws-codedeploy-common' -include 'aws-codedeploy-agent' -include 'aws-codedeploy-server' +include 'aws-elasticbeanstalk-common' +include 'aws-elasticbeanstalk-agent' +include 'aws-elasticbeanstalk-server' include 'build' include 'amazon-util' - diff --git a/teamcity-plugin.xml b/teamcity-plugin.xml index 7d09ce0..25aefd2 100644 --- a/teamcity-plugin.xml +++ b/teamcity-plugin.xml @@ -18,14 +18,14 @@ - aws-codedeploy-plugin - AWS CodeDeploy + aws-elasticbeanstalk-plugin + AWS Elastic Beanstalk @Plugin_Version@ - Build runner for deploying application to AWS EC2 and on-premise instances using AWS CodeDeploy - https://github.com/JetBrains/teamcity-aws-codedeploy-plugin + Build runner for deploying application to AWS Elastic Beanstalk + https://github.com/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin - JetBrains, s.r.o. - http://www.jetbrains.com + rtfpessoa + https://github.com/rtfpessoa/teamcity-aws-elasticbeanstalk-plugin