From 8cc27edd1f897c28224dcabe8c1793564117c545 Mon Sep 17 00:00:00 2001 From: Dongyu 'Gary' Zheng Date: Mon, 25 Jul 2016 15:00:03 +0000 Subject: [PATCH] Version 0.1.0 - 03/23/2016 1. Implemented actions :upload, :download, :delete, :delete_url 2. Created rspec tests --- CHANGELOG.md | 5 + Gemfile | 4 + LICENSE | 402 +++++++++--------- README.md | 215 +++++++++- Rakefile | 6 + chef-nexus.gemspec | 45 ++ lib/chef/nexus.rb | 366 ++++++++++++++++ lib/chef/nexus/version.rb | 23 + lib/chef/pom.erb | 9 + spec/chef/nexus_spec.rb | 166 ++++++++ spec/chef/recipes/delete/coordinates.rb | 12 + spec/chef/recipes/delete/remote_url.rb | 12 + spec/chef/recipes/download/coordinates.rb | 13 + .../download/coordinates_doesnt_exist.rb | 13 + .../download/coordinates_no_packaging.rb | 13 + spec/chef/recipes/download/remote_url.rb | 13 + .../recipes/download/remote_url_update.rb | 15 + spec/chef/recipes/upload/different_file.rb | 13 + .../recipes/upload/different_file_update.rb | 15 + ...artifactId:packaging:classifier:version.rb | 13 + ...tn_groupId:artifactId:packaging:version.rb | 15 + .../upload/extn_groupId:artifactId:version.rb | 13 + .../upload/no_extn_coordinate_attributes.rb | 18 + .../no_extn_groupId:artifactId:version.rb | 14 + ...extn_package_groupId:artifactId:version.rb | 16 + .../upload/remote_url_different_file.rb | 13 + .../remote_url_different_file_update.rb | 15 + .../recipes/upload/remote_url_no_parse.rb | 13 + .../upload/remote_url_no_parse_no_pom.rb | 15 + spec/chef/recipes/upload/remote_url_parse.rb | 13 + spec/config_sample.rb | 9 + spec/spec_helper.rb | 158 +++++++ 32 files changed, 1482 insertions(+), 203 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 Gemfile create mode 100644 Rakefile create mode 100644 chef-nexus.gemspec create mode 100644 lib/chef/nexus.rb create mode 100644 lib/chef/nexus/version.rb create mode 100644 lib/chef/pom.erb create mode 100644 spec/chef/nexus_spec.rb create mode 100644 spec/chef/recipes/delete/coordinates.rb create mode 100644 spec/chef/recipes/delete/remote_url.rb create mode 100644 spec/chef/recipes/download/coordinates.rb create mode 100644 spec/chef/recipes/download/coordinates_doesnt_exist.rb create mode 100644 spec/chef/recipes/download/coordinates_no_packaging.rb create mode 100644 spec/chef/recipes/download/remote_url.rb create mode 100644 spec/chef/recipes/download/remote_url_update.rb create mode 100644 spec/chef/recipes/upload/different_file.rb create mode 100644 spec/chef/recipes/upload/different_file_update.rb create mode 100644 spec/chef/recipes/upload/extn_groupId:artifactId:packaging:classifier:version.rb create mode 100644 spec/chef/recipes/upload/extn_groupId:artifactId:packaging:version.rb create mode 100644 spec/chef/recipes/upload/extn_groupId:artifactId:version.rb create mode 100644 spec/chef/recipes/upload/no_extn_coordinate_attributes.rb create mode 100644 spec/chef/recipes/upload/no_extn_groupId:artifactId:version.rb create mode 100644 spec/chef/recipes/upload/no_extn_package_groupId:artifactId:version.rb create mode 100644 spec/chef/recipes/upload/remote_url_different_file.rb create mode 100644 spec/chef/recipes/upload/remote_url_different_file_update.rb create mode 100644 spec/chef/recipes/upload/remote_url_no_parse.rb create mode 100644 spec/chef/recipes/upload/remote_url_no_parse_no_pom.rb create mode 100644 spec/chef/recipes/upload/remote_url_parse.rb create mode 100644 spec/config_sample.rb create mode 100644 spec/spec_helper.rb diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..27649cb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.0 (03/23/2016) +- Implemented actions :upload, :download, :delete, :delete_url +- Created rspec tests \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..0f83db5 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in chef-nexus.gemspec +gemspec diff --git a/LICENSE b/LICENSE index 8dada3e..1b22bef 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,201 @@ - 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: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) 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 - - (d) 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 - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. + 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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) 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 + + (d) 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 + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. \ No newline at end of file diff --git a/README.md b/README.md index 64a3bbd..5e3e167 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,213 @@ -# chef-nexus -chef-nexus is a Ruby gem that provides the `nexus` Chef resource for managing artifacts on Nexus by Sonatype. +# Chef::Nexus + +chef-nexus is a Chef resource for managing artifacts on Nexus by Sonatype. + +## Usage + +Simply install the gem and `require 'chef/nexus'` in your recipes, then you can use the `nexus` resource. + +There is an optional Nexus config file that you can create and it will be read in the following order: +1. `File.read(ENV['NEXUS_CONFIG'])` +2. `File.read("#{ENV['HOME']}/.nexus/config")` +3. `File.read('/etc/.nexus/config')` + +**Note:** only the first one found will be loaded + +Example config file: +```json +{ + "default": { + "url": "http://mynexus.net/nexus/content/", + "repo": "name_of_repo", + "auth": "gary:secr3t" + }, + "gary": { + "url": "http://mynexus.net/nexus/content/", + "repo": "disk_images", + "auth": "gary:p@ssw0rd" + } +} +``` + +**NOTE:** + +* You can pick different profiles with attribute `nexus_profile` +* You can set you default profile with environment variable `export NEXUS_PROFILE=gary` +* If you don't set the env variable or use the attributes below - `"default"` profile must be present, or you will need to specify the `nexus_profile` attribute every time. +* Attribute `nexus_profile` has precedence over environment variable + +You can also specify these as attributes: +```ruby +nexus_url 'http://mynexus.net/nexus/content/' +nexus_repo 'name_of_repo' +nexus_auth 'gary:secr3t' +``` + +And as environment variables: +```shell +export NEXUS_URL=http://mynexus.net/nexus/content/ +export NEXUS_REPO=name_of_repo +export NEXUS_AUTH=gary:secr3t +``` + +Order of precedence: +1. `attribute` +2. `environment` +3. `config` + +### Attributes + +```ruby + :nexus_profile => String of the profile you want to use + :nexus_url => String url of Nexus + :nexus_repo => String name of your repository + :nexus_auth => String of your Nexus credentials + :use_auth => Boolean specifing whether to authenticate against the Nexus server, fix for 403 Forbidden + + :upload_pom => Boolean indicating whether to generate and upload a pom file, default true + :update_if_exists => Boolean specifying whether to overwrite existing artifacts during upload action (deletes artifact folder first) + + :local_file => String absolute path to the file to upload from, or download to + :remote_url => String of the URL to be upload to / download from, if used, all attributes below are ignored. SEE NOTES + + :coordinates => String Maven coordinates, see: https://maven.apache.org/pom.html#Maven_Coordinates + :groupId => String name of group + :artifactId => String name of artifact + :packaging => String of packaging type + :classifier => String name of the files classifier + :version => [String, Fixnum, Float] of the version +``` +**NOTES**: + +* `:remote_url` will be parsed for pom information if it is syntactically correct according to Maven & Nexus standards, as in: +`/repositories/////--.` +* `:remote_url` takes precedence over coordinates as the upload / download endpoint. +* Usage of `:remote_url` is **NOT RECOMMENDED** +* Order of precedence during generation of pom file: +(groupId & artifactId & packaging & classifier & version) > coordinates > remote_url + +### Actions + +```ruby + actions :upload, :download, :delete, :delete_url + default_action :upload +``` + +### Examples + +#### 1. Upload a file to Nexus without authentication and without the pom file + +```ruby +nexus 'some description' do + use_auth false + upload_pom false + + coordinates 'com.gary.image:cloud-img:jar:1.2.0' + local_file '/home/gary/cloud-img-1.2.0.jar' + + action :upload +end +``` + +#### 2. Upload a file to Nexus without using coordinates and overriding Nexus endpoint config + +```ruby +nexus 'some description' do + local_file '/home/gary/cloud-img-1.2.0.jar' + + nexus_url 'http://mynexus.net/nexus/content/repositories/' + nexus_auth 'gary:secr3t' + nexus_repo 'name_of_repo' + + groupId 'com.gary.image' + artifactId 'cloud-img' + packaging 'jar' + classifier 'some_classifier' + version '1.2.0' + + action :upload +end +``` + +#### 3. Upload a file to an exact location on Nexus using remote_url (not recommended) + +```ruby +nexus 'some description' do + remote_url 'http://mynexus.net/nexus/content/repositories/com/gary/image/cloud-img/some_folder/bad_practice.jar' + local_file '/home/gary/cloud-img-1.2.0.jar' + + action :upload +end +``` + +#### 4. Download a file from Nexus using coordinates, with another profile + +```ruby +nexus 'some description' do + nexus_profile 'gary' + coordinates 'com.gary.image:cloud-img:jar:1.2.0' + local_file '/home/gary/cloud-img-1.2.0.jar' + + action :download +end +``` + +#### 5. Download a file from Nexus using remote_url + +```ruby +nexus 'some description' do + remote_url 'http://mynexus.net/nexus/content/repositories/com/gary/image/cloud-img/1.2.0/cloud-img-1.2.0.jar' + local_file '/home/gary/cloud-img-1.2.0.jar' + + action :download +end +``` + +#### 6. Delete an artifact from Nexus + +```ruby +nexus 'some description' do + coordinates 'com.gary.image:cloud-img:jar:1.2.0' + + action :delete +end +``` +**NOTE:** This action does not accept attribute `:remote_url` as it is dangerous to do so. **Ex.** you might delete *ALL* artifacts by accident +**WARNING:** This action will delete the version folder (folder that holds the file), so everything inside it will be deleted as well + +#### 7. Delete a file or folder from Nexus (delete folder 1.2.0 in this case) + +```ruby +nexus 'some description' do + remote_url 'http://mynexus.net/nexus/content/repositories/com/gary/image/cloud-img/1.2.0/' + + action :delete_url +end +``` +**NOTE:** This action requires attribute `:remote_url` + +## Development + +* Source hosted at [GitHub](https://github.com/blackberry/chef-nexus) +* Report issues/questions/feature requests on [GitHub Issues](https://github.com/blackberry/chef-nexus/issues) + +Pull requests are very welcome! Make sure your patches are well tested. Ideally create a topic branch for every separate change you make. For example: + +1. Fork the repo +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +### Testing + +Please test your changes! Here's how: + +1. Create and configure `spec/config.rb` from `spec/config_sample.rb` +2. Run `rspec spec/chef/nexus_spec.rb` from your `chef-nexus` folder + +If you add new functionality, please create new tests accordingly. + +## Authors + +Created by [Dongyu 'Gary' Zheng](https://github.com/dongyuzheng) () diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..93cb943 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/chef-nexus.gemspec b/chef-nexus.gemspec new file mode 100644 index 0000000..d726a26 --- /dev/null +++ b/chef-nexus.gemspec @@ -0,0 +1,45 @@ +# Copyright 2016, BlackBerry, Inc. +# +# 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. + +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'chef/nexus/version' + +Gem::Specification.new do |spec| + spec.name = 'chef-nexus' + spec.version = Chef::Nexus::VERSION + spec.license = 'Apache 2.0' + spec.platform = Gem::Platform::RUBY + spec.extra_rdoc_files = ['README.md', 'LICENSE'] + + spec.authors = ['Dongyu \'Gary\' Zheng'] + spec.email = ['garydzheng@gmail.com'] + + spec.summary = 'Chef resource for managing artifacts on Nexus by Sonatype' + spec.description = spec.summary + spec.homepage = 'https://github.com/blackberry/chef-nexus' + + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_dependency 'chef' + spec.add_dependency 'compat_resource' + + spec.add_development_dependency 'bundler', '~> 1.11' + spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'rspec', '~> 3.0' +end diff --git a/lib/chef/nexus.rb b/lib/chef/nexus.rb new file mode 100644 index 0000000..e7e7493 --- /dev/null +++ b/lib/chef/nexus.rb @@ -0,0 +1,366 @@ +# Copyright 2016, BlackBerry, Inc. +# +# 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. + +require 'chef_compat/resource' +require 'json' +require 'erb' +require 'digest' + +class Chef + class Resource + # + # Chef resource for managing artifacts on Nexus by Sonatype + # + class Nexus < ChefCompat::Resource + resource_name :nexus + + property :nexus_profile, String, desired_state: false + property :nexus_url, String, desired_state: false + property :nexus_repo, String, desired_state: false + property :nexus_auth, String, desired_state: false + property :use_auth, [TrueClass, FalseClass], default: true, desired_state: false + + property :upload_pom, [TrueClass, FalseClass], default: true, desired_state: false + property :update_if_exists, [TrueClass, FalseClass], default: false + + property :local_file, String, desired_state: false + property :remote_url, String, desired_state: false + + property :coordinates, String, desired_state: false + property :groupId, String, desired_state: false + property :artifactId, String, desired_state: false + property :packaging, String, desired_state: false + property :classifier, String, desired_state: false + property :version, [String, Fixnum, Float], desired_state: false + + property :file_sha1, [String, NilClass] + property :file_md5, [String, NilClass] + property :pom_sha1, [String, NilClass] + property :pom_md5, [String, NilClass] + + load_current_value do + file_sha1 download_and_read(curl_url + '.sha1') + file_md5 download_and_read(curl_url + '.md5') + if upload_pom && can_generate_pom + pom_sha1 download_and_read(curl_base_url + '.pom.sha1') + pom_md5 download_and_read(curl_base_url + '.pom.md5') + end + end + + action :upload do + fail ':local_file is missing' unless local_file + fail "#{local_file} does not exist" unless ::File.exist?(local_file) + + if upload_pom && !can_generate_pom + msg = "chef-nexus was unable retrieve enough information to generate a pom\n" + msg << "is :remote_url correct in terms of Maven & Nexus syntax? See README: Attributes - Notes\n" + msg << 'Set attribute :upload_pom false to bypass this error.' + fail msg + end + + file_sha1 local_file_sha1 + file_md5 local_file_md5 + + unless file_equal(current_resource) + if file_exists + fail 'Different file currently exists on Nexus (or checksums are missing), if you want to overwrite it, set attribute :update_if_exists to true' unless update_if_exists + delete(curl_url) + end + converge_by "uploaded file '#{local_file}' to Nexus at '#{curl_url}'" do + upload(local_file, curl_url) + end + end + + converge_if_changed :file_sha1 do + delete(curl_url + '.sha1') if url_exists(curl_url + '.sha1') + write_file(curl_url + '.sha1', file_sha1) + end + + converge_if_changed :file_md5 do + delete(curl_url + '.md5') if url_exists(curl_url + '.md5') + write_file(curl_url + '.md5', file_md5) + end + + if upload_pom + pom_content = ERB.new(::File.read("#{::File.dirname(__FILE__)}/pom.erb"), nil, '-').result(binding) + + pom_sha1 Digest::SHA1.hexdigest(pom_content) + pom_md5 Digest::MD5.hexdigest(pom_content) + + unless pom_equal(current_resource) + if pom_exists + fail 'Different pom currently exists on Nexus (or checksums are missing), if you want to overwrite it, set attribute :update_if_exists to true' unless update_if_exists + delete(curl_base_url + '.pom') + end + converge_by "uploaded pom to Nexus at '#{curl_base_url + '.pom'}'" do + write_file(curl_base_url + '.pom', pom_content) + end + end + + converge_if_changed :pom_sha1 do + delete(curl_base_url + '.pom.sha1') if url_exists(curl_base_url + '.pom.sha1') + write_file(curl_base_url + '.pom.sha1', pom_sha1) + end + + converge_if_changed :pom_md5 do + delete(curl_base_url + '.pom.md5') if url_exists(curl_base_url + '.pom.md5') + write_file(curl_base_url + '.pom.md5', pom_md5) + end + end + end + + action :download do + fail ':local_file is missing' unless local_file + fail "No file exists at '#{curl_url}' or you do not permissions" unless file_exists + unless file_equal(current_resource) + fail 'Different version currently exists locally, if you want to overwrite it, set attribute :update_if_exists to true' if ::File.exist?(local_file) && !update_if_exists + converge_by "downloaded file '#{local_file}' from Nexus at '#{curl_url}'" do + execute_or_fail("mkdir -p #{::File.dirname(local_file)}") + download(curl_url, local_file) + updated_by_last_action(true) + end + end + end + + action :delete do + fail 'action :delete does not accept attribute :remote_url ... use Maven coordinates instead, or use action :delete_url' if remote_url + converge_by "deleted artifact '#{curl_url}' from Nexus" do + delete(curl_url.split('/')[0...-1].join('/')) + updated_by_last_action(true) + end if url_exists(curl_url.split('/')[0...-1].join('/')) + end + + action :delete_url do + fail 'action :delete_url requires attribute :remote_url' unless remote_url + converge_by "deleted '#{curl_url}' from Nexus" do + delete(curl_url) + updated_by_last_action(true) + end if url_exists(curl_url) + end + + protected + + def upload(local, remote) + execute_or_fail("curl -v #{use_auth ? "-u #{n_auth} " : nil}-T #{local} #{remote}") + fail "Server responded with successful creation, but '#{remote}' does not exist." unless url_exists(remote) + end + + def download(remote, local) + execute_or_fail("curl -v #{use_auth ? "-u #{n_auth} " : nil}#{remote} > #{local}") + fail "File appears to have been downloaded, but '#{local}' does not exist." unless ::File.exist?(local) + end + + def delete(remote) + execute_or_fail("curl -v #{use_auth ? "-u #{n_auth} " : nil}-X DELETE #{remote}") + fail "Server responded with successful deletion, but '#{remote}' still exists." if url_exists(remote) + end + + def execute_or_fail(cmd, check_http = true) + output = `#{cmd}` + fail_me = false + output.scan(%r{^ +(\d{3}) - .*?$}).each { |code| fail_me = true unless code[0] == '1' || code[0] == '2' } if check_http + if $?.exitstatus != 0 || fail_me + fail "Command failed: #{cmd}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n#{output.strip}\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + end + output + end + + def write_file(remote, content) + tmp = '/tmp/' + Digest::SHA1.hexdigest(rand(100000000000000).to_s) + ::File.open(tmp, 'w') { |file| file.write(content) } + upload(tmp, remote) + ::File.delete(tmp) + end + + def cords + @cords ||= begin + hsh = {} + if remote_url + scn = remote_url.scan(%r{^.*?/repositories/.*?/(.*?)/((?:\d+\.)*\d+)/(.*?)-((?:\d+\.)*\d+)(?:-(.*?))?\.(.*)$}) + if scn.length == 1 && scn[0][1] == scn[0][3] + group_id, _, artifact_id = scn[0][0].rpartition('/') + if !group_id.empty? && artifact_id == scn[0][2] + hsh[:groupId] = group_id.tr('/', '.') + hsh[:artifactId] = artifact_id + hsh[:version] = scn[0][1] + hsh[:packaging] = scn[0][5] + hsh[:classifier] = scn[0][4] + end + end + hsh.delete_if { |_, v| v.nil? } + end + if coordinates + splt = coordinates.split(':') + unless splt.length >= 3 && splt.length <= 5 + fail %q(:coordinates must follow the one of the following formats +groupId:artifactId:version +groupId:artifactId:packaging:version +groupId:artifactId:packaging:classifier:version) + end + hsh[:groupId] = splt.first + hsh[:artifactId] = splt[1] + hsh[:version] = splt.last + hsh[:packaging] = splt[2] if splt.length >= 4 + hsh[:classifier] = splt[3] if splt.length == 5 + end + %w(groupId artifactId version packaging classifier).each do |x| + p = eval(x) + hsh[x.to_sym] = p if p + end + fail 'Your must specify :coordinates OR at least all of [:groupId, :artifactId, :version]' if hsh.size < 3 && !remote_url + if local_file && !hsh[:packaging] + extn = ::File.basename(local_file).split('.', 2)[1] + fail 'Files require an extension, or specify it with :packaging' if extn.nil? && !remote_url + hsh[:packaging] = extn + end + hsh + end + end + + def curl_url + @curl_url ||= begin + remote_url || begin + url = curl_base_url.dup + url += "-#{cords[:classifier]}" if cords[:classifier] + url += ".#{cords[:packaging]}" + url + end + end + end + + def curl_base_url + @curl_base_url ||= begin + url = [n_url, 'repositories', n_repo] + url.push(cords[:groupId].split('.')).flatten! + url.push(cords[:artifactId]) + url.push(cords[:version]) + url.push("#{cords[:artifactId]}-#{cords[:version]}") + url.join('/') + end + end + + def n_url + @n_url ||= begin + nurl = nexus_url || ENV['NEXUS_URL'] || n_config['url'] + fail "Please provide Nexus url as either an attribute :nexus_url or in ~/.nexus/config profile '#{n_profile}'" unless nurl + nurl.chomp('/') + end + end + + def n_auth + @n_auth ||= begin + nauth = nexus_auth || ENV['NEXUS_AUTH'] || n_config['auth'] + fail "Please provide Nexus auth as either an attribute :nexus_auth or in ~/.nexus/config profile '#{n_profile}'" if use_auth && !nauth + nauth + end + end + + def n_repo + @n_repo ||= begin + nrepo = nexus_repo || ENV['NEXUS_REPO'] || n_config['repo'] + fail "Please provide Nexus repository as either an attribute :repository or in ~/.nexus/config profile '#{n_profile}'" unless nrepo + nrepo + end + end + + def n_config + @n_config ||= begin + if ENV['NEXUS_CONFIG'] && ::File.exist?(ENV['NEXUS_CONFIG']) + JSON.parse(::File.read(ENV['NEXUS_CONFIG']))[n_profile] || {} + elsif ::File.exist?("#{ENV['HOME']}/.nexus/config") + JSON.parse(::File.read("#{ENV['HOME']}/.nexus/config"))[n_profile] || {} + elsif ::File.exist?('/etc/.nexus/config') + JSON.parse(::File.read('/etc/.nexus/config'))[n_profile] || {} + else + {} + end + end + end + + def n_profile + @n_profile ||= nexus_profile || ENV['NEXUS_PROFILE'] || 'default' + end + + def local_file_md5 + @local_file_md5 ||= begin + ::File.open(local_file, 'rb') do |f| + digest = Digest::MD5.new + buffer = '' + digest.update(buffer) while f.read(4096, buffer) + digest.hexdigest + end if ::File.exist?(local_file) + end + end + + def local_file_sha1 + @local_file_sha1 ||= begin + ::File.open(local_file, 'rb') do |f| + digest = Digest::SHA1.new + buffer = '' + digest.update(buffer) while f.read(4096, buffer) + digest.hexdigest + end if ::File.exist?(local_file) + end + end + + def file_exists + @file_exists ||= url_exists(curl_url) + end + + # If neither SHA1 or MD5 checksums are on Nexus, we will assume not equal. + def file_equal(current_resource) + @file_equal ||= begin + if file_exists + return true if current_resource.file_sha1 && local_file_sha1 == current_resource.file_sha1 + return true if current_resource.file_md5 && local_file_md5 == current_resource.file_md5 + end + false + end + end + + def pom_exists + @pom_exists ||= url_exists(curl_base_url + '.pom') + end + + def pom_equal(current_resource) + @pom_equal ||= begin + if pom_exists + return true if current_resource.pom_sha1 && pom_sha1 == current_resource.pom_sha1 + return true if current_resource.pom_md5 && pom_md5 == current_resource.pom_md5 + end + false + end + end + + def can_generate_pom + @can_generate_pom ||= [:groupId, :artifactId, :version, :packaging].all? { |x| cords.key?(x) } + end + + def url_exists(url) + `curl --output /dev/null --silent --head --fail #{use_auth ? "-u #{n_auth} " : nil}#{url}` + $?.exitstatus == 0 + end + + def download_and_read(url) + return nil unless url_exists(url) + tmp = '/tmp/' + Digest::SHA1.hexdigest(rand(100000000000000).to_s) + cmd = "curl -v #{use_auth ? "-u #{n_auth} " : nil}#{url} > #{tmp}" + execute_or_fail(cmd) + content = ::File.read(tmp) + ::File.delete(tmp) + content + end + end + end +end diff --git a/lib/chef/nexus/version.rb b/lib/chef/nexus/version.rb new file mode 100644 index 0000000..a05aab9 --- /dev/null +++ b/lib/chef/nexus/version.rb @@ -0,0 +1,23 @@ +# Copyright 2016, BlackBerry, Inc. +# +# 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. + +module Chef + # + # The version of chef-nexus, follows Semantic Versioning 2.0.0 + # See: http://semver.org/spec/v2.0.0.html + # + module Nexus + VERSION = '0.1.0' + end +end diff --git a/lib/chef/pom.erb b/lib/chef/pom.erb new file mode 100644 index 0000000..06155d2 --- /dev/null +++ b/lib/chef/pom.erb @@ -0,0 +1,9 @@ + + + 4.0.0 + <%= cords[:groupId] %> + <%= cords[:artifactId] %> + <%= cords[:version] %> + <%= cords[:packaging] %> + diff --git a/spec/chef/nexus_spec.rb b/spec/chef/nexus_spec.rb new file mode 100644 index 0000000..edb49ae --- /dev/null +++ b/spec/chef/nexus_spec.rb @@ -0,0 +1,166 @@ +# Copyright 2016, BlackBerry, Inc. +# +# 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. + +require 'spec_helper' + +describe 'Chef::Nexus' do + ############ + ## UPLOAD ## + ############ + + idempotency_helper( + 'Upload file with extension using coordinates groupId:artifactId:version', + 'upload/extn_groupId:artifactId:version.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/has_extension.test' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/artifact/1.0.0/artifact-1.0.0.test'" + ) + idempotency_helper( + 'Upload file with extension using coordinates groupId:artifactId:packaging:version', + 'upload/extn_groupId:artifactId:packaging:version.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/has_extension.test' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/sub/artifact/1.0.0/artifact-1.0.0.jar'" + ) + idempotency_helper( + 'Upload file with extension using coordinates groupId:artifactId:packaging:classifier:version', + 'upload/extn_groupId:artifactId:packaging:classifier:version.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/has_extension.test' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/sub/subsub/artifact/1.0.0/artifact-1.0.0-classifier.jar'" + ) + describe 'Try to upload a file without package, should fail.' do + it do + is_expected.to converge_test_recipe( + :recipe => 'upload/no_extn_groupId:artifactId:version.rb', + :expected => 'Files require an extension, or specify it with :packaging', + :fail_if => 'uploaded file' + ) + end + end + idempotency_helper( + 'Upload file without an extension using package attribute', + 'upload/no_extn_package_groupId:artifactId:version.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/no_extension' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/artifact/1.0.1/artifact-1.0.1.pkg'" + ) + idempotency_helper( + 'Upload file with all coordinate attributes, overriding Maven coordinates', + 'upload/no_extn_coordinate_attributes.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/no_extension' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/artifact/1.2/artifact-1.2-classifier.pkg'" + ) + describe 'Try to upload a different file to one that already exists on Nexus, should fail.' do + it do + is_expected.to converge_test_recipe( + :recipe => 'upload/different_file.rb', + :expected => 'Different file currently exists on Nexus (or checksums are missing), if you want to overwrite it, set attribute :update_if_exists to true', + :fail_if => 'uploaded file' + ) + end + end + describe 'Overwrite existing artifact' do + it do + is_expected.to converge_test_recipe( + :recipe => 'upload/different_file_update.rb', + :expected => "uploaded file '/tmp/chef_nexus_rspec_temp/no_extension' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/sub/artifact/1.0.0/artifact-1.0.0.jar'" + ) + end + end + describe 'Try to upload a file using remote_url which does not pass parsing, should fail' do + it do + is_expected.to converge_test_recipe( + :recipe => 'upload/remote_url_no_parse.rb', + :expected => 'chef-nexus was unable retrieve enough information to generate a pom', + :fail_if => 'uploaded file' + ) + end + end + idempotency_helper( + 'Upload a file using remote_url which does not pass parsing, without pom', + 'upload/remote_url_no_parse_no_pom.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/no_extension' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/no_parse'" + ) + idempotency_helper( + 'Upload a file using remote_url which passes parsing', + 'upload/remote_url_parse.rb', + "uploaded file '/tmp/chef_nexus_rspec_temp/no_extension' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/artifact/1.7/artifact-1.7-classifier.pkg'" + ) + describe 'Try to upload a different file to one that already exists on Nexus using remote_url, should fail.' do + it do + is_expected.to converge_test_recipe( + :recipe => 'upload/remote_url_different_file.rb', + :expected => 'Different file currently exists on Nexus (or checksums are missing), if you want to overwrite it, set attribute :update_if_exists to true', + :fail_if => 'uploaded file' + ) + end + end + describe 'Overwrite existing artifact using remote_url' do + it do + is_expected.to converge_test_recipe( + :recipe => 'upload/remote_url_different_file_update.rb', + :expected => "uploaded file '/tmp/chef_nexus_rspec_temp/has_extension.test' to Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/sub/artifact/1.0.0/artifact-1.0.0.jar'" + ) + end + end + + ############## + ## DOWNLOAD ## + ############## + + describe 'Try to download a file using coordinates without packaging, should fail' do + it do + is_expected.to converge_test_recipe( + :recipe => 'download/coordinates_no_packaging.rb', + :expected => 'Files require an extension, or specify it with :packaging', + :fail_if => 'downloaded file' + ) + end + end + describe 'Try to download non-existent file, should fail.' do + it do + is_expected.to converge_test_recipe( + :recipe => 'download/coordinates_doesnt_exist.rb', + :expected => "No file exists at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/artifact/1.0.0/artifact-1.0.0.gary' or you do not permissions", + :fail_if => 'downloaded file' + ) + end + end + idempotency_helper( + 'Download file using coordinates', + 'download/coordinates.rb', + "downloaded file '/tmp/chef_nexus_rspec_temp/downloaded.test' from Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/artifact/1.0.0/artifact-1.0.0.test'" + ) + describe 'Try to download different file, should fail' do + it do + is_expected.to converge_test_recipe( + :recipe => 'download/remote_url.rb', + :expected => 'Different version currently exists locally, if you want to overwrite it, set attribute :update_if_exists to true', + :fail_if => 'downloaded file' + ) + end + end + idempotency_helper( + 'Download and overwrite file using remote_url', + 'download/remote_url_update.rb', + "downloaded file '/tmp/chef_nexus_rspec_temp/downloaded.test' from Nexus at 'http://ebj-pilot-nexus.devlab2k.testnet.rim.net/nexus/content/repositories/ebu-opennebula-images/chef-nexus-rspec-test/no_parse'" + ) + + ############ + ## DELETE ## + ############ + + idempotency_helper( + 'Delete an artifact using coordinates', + 'delete/coordinates.rb', + '' + ) + idempotency_helper( + 'Delete the test folder on Nexus using remote_url', + 'delete/remote_url.rb', + '' + ) +end diff --git a/spec/chef/recipes/delete/coordinates.rb b/spec/chef/recipes/delete/coordinates.rb new file mode 100644 index 0000000..efe5937 --- /dev/null +++ b/spec/chef/recipes/delete/coordinates.rb @@ -0,0 +1,12 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test.sub.subsub:artifact:jar:classifier:1.0.0' + action :delete +end diff --git a/spec/chef/recipes/delete/remote_url.rb b/spec/chef/recipes/delete/remote_url.rb new file mode 100644 index 0000000..337273f --- /dev/null +++ b/spec/chef/recipes/delete/remote_url.rb @@ -0,0 +1,12 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/" + action :delete_url +end diff --git a/spec/chef/recipes/download/coordinates.rb b/spec/chef/recipes/download/coordinates.rb new file mode 100644 index 0000000..2f45c07 --- /dev/null +++ b/spec/chef/recipes/download/coordinates.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test:artifact:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/downloaded.test' + action :download +end diff --git a/spec/chef/recipes/download/coordinates_doesnt_exist.rb b/spec/chef/recipes/download/coordinates_doesnt_exist.rb new file mode 100644 index 0000000..fab7ee4 --- /dev/null +++ b/spec/chef/recipes/download/coordinates_doesnt_exist.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test:artifact:gary:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/downloaded' + action :download +end diff --git a/spec/chef/recipes/download/coordinates_no_packaging.rb b/spec/chef/recipes/download/coordinates_no_packaging.rb new file mode 100644 index 0000000..ec64a48 --- /dev/null +++ b/spec/chef/recipes/download/coordinates_no_packaging.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test:artifact:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/downloaded' + action :download +end diff --git a/spec/chef/recipes/download/remote_url.rb b/spec/chef/recipes/download/remote_url.rb new file mode 100644 index 0000000..4c9db7f --- /dev/null +++ b/spec/chef/recipes/download/remote_url.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/no_parse" + local_file '/tmp/chef_nexus_rspec_temp/downloaded.test' + action :download +end diff --git a/spec/chef/recipes/download/remote_url_update.rb b/spec/chef/recipes/download/remote_url_update.rb new file mode 100644 index 0000000..c07ed22 --- /dev/null +++ b/spec/chef/recipes/download/remote_url_update.rb @@ -0,0 +1,15 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + update_if_exists true + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/no_parse" + local_file '/tmp/chef_nexus_rspec_temp/downloaded.test' + action :download +end diff --git a/spec/chef/recipes/upload/different_file.rb b/spec/chef/recipes/upload/different_file.rb new file mode 100644 index 0000000..5f46f6b --- /dev/null +++ b/spec/chef/recipes/upload/different_file.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test.sub:artifact:jar:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/chef/recipes/upload/different_file_update.rb b/spec/chef/recipes/upload/different_file_update.rb new file mode 100644 index 0000000..bfe6314 --- /dev/null +++ b/spec/chef/recipes/upload/different_file_update.rb @@ -0,0 +1,15 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + update_if_exists true + + coordinates 'chef-nexus-rspec-test.sub:artifact:jar:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/chef/recipes/upload/extn_groupId:artifactId:packaging:classifier:version.rb b/spec/chef/recipes/upload/extn_groupId:artifactId:packaging:classifier:version.rb new file mode 100644 index 0000000..1099d58 --- /dev/null +++ b/spec/chef/recipes/upload/extn_groupId:artifactId:packaging:classifier:version.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test.sub.subsub:artifact:jar:classifier:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/has_extension.test' + action :upload +end diff --git a/spec/chef/recipes/upload/extn_groupId:artifactId:packaging:version.rb b/spec/chef/recipes/upload/extn_groupId:artifactId:packaging:version.rb new file mode 100644 index 0000000..41fbfcd --- /dev/null +++ b/spec/chef/recipes/upload/extn_groupId:artifactId:packaging:version.rb @@ -0,0 +1,15 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + upload_pom true + update_if_exists true + + coordinates 'chef-nexus-rspec-test.sub:artifact:jar:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/has_extension.test' +end diff --git a/spec/chef/recipes/upload/extn_groupId:artifactId:version.rb b/spec/chef/recipes/upload/extn_groupId:artifactId:version.rb new file mode 100644 index 0000000..95e31e8 --- /dev/null +++ b/spec/chef/recipes/upload/extn_groupId:artifactId:version.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test:artifact:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/has_extension.test' + action :upload +end diff --git a/spec/chef/recipes/upload/no_extn_coordinate_attributes.rb b/spec/chef/recipes/upload/no_extn_coordinate_attributes.rb new file mode 100644 index 0000000..238051e --- /dev/null +++ b/spec/chef/recipes/upload/no_extn_coordinate_attributes.rb @@ -0,0 +1,18 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + coordinates 'chef-nexus-rspec-test:artifact:1.0.2' + groupId 'chef-nexus-rspec-test' + artifactId 'artifact' + packaging 'pkg' + classifier 'classifier' + version 1.2 + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/chef/recipes/upload/no_extn_groupId:artifactId:version.rb b/spec/chef/recipes/upload/no_extn_groupId:artifactId:version.rb new file mode 100644 index 0000000..0cffe74 --- /dev/null +++ b/spec/chef/recipes/upload/no_extn_groupId:artifactId:version.rb @@ -0,0 +1,14 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + upload_pom false + + coordinates 'chef-nexus-rspec-test:artifact:1.0.0' + local_file '/tmp/chef_nexus_rspec_temp/no_extension' +end diff --git a/spec/chef/recipes/upload/no_extn_package_groupId:artifactId:version.rb b/spec/chef/recipes/upload/no_extn_package_groupId:artifactId:version.rb new file mode 100644 index 0000000..0af0b83 --- /dev/null +++ b/spec/chef/recipes/upload/no_extn_package_groupId:artifactId:version.rb @@ -0,0 +1,16 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + upload_pom false + + coordinates 'chef-nexus-rspec-test:artifact:1.0.1' + packaging 'pkg' + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/chef/recipes/upload/remote_url_different_file.rb b/spec/chef/recipes/upload/remote_url_different_file.rb new file mode 100644 index 0000000..c6c3566 --- /dev/null +++ b/spec/chef/recipes/upload/remote_url_different_file.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/sub/artifact/1.0.0/artifact-1.0.0.jar" + local_file '/tmp/chef_nexus_rspec_temp/has_extension.test' + action :upload +end diff --git a/spec/chef/recipes/upload/remote_url_different_file_update.rb b/spec/chef/recipes/upload/remote_url_different_file_update.rb new file mode 100644 index 0000000..57b8d1e --- /dev/null +++ b/spec/chef/recipes/upload/remote_url_different_file_update.rb @@ -0,0 +1,15 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + update_if_exists true + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/sub/artifact/1.0.0/artifact-1.0.0.jar" + local_file '/tmp/chef_nexus_rspec_temp/has_extension.test' + action :upload +end diff --git a/spec/chef/recipes/upload/remote_url_no_parse.rb b/spec/chef/recipes/upload/remote_url_no_parse.rb new file mode 100644 index 0000000..bcae9e8 --- /dev/null +++ b/spec/chef/recipes/upload/remote_url_no_parse.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/no_parse" + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/chef/recipes/upload/remote_url_no_parse_no_pom.rb b/spec/chef/recipes/upload/remote_url_no_parse_no_pom.rb new file mode 100644 index 0000000..00d4730 --- /dev/null +++ b/spec/chef/recipes/upload/remote_url_no_parse_no_pom.rb @@ -0,0 +1,15 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + upload_pom false + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/no_parse" + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/chef/recipes/upload/remote_url_parse.rb b/spec/chef/recipes/upload/remote_url_parse.rb new file mode 100644 index 0000000..0ca574c --- /dev/null +++ b/spec/chef/recipes/upload/remote_url_parse.rb @@ -0,0 +1,13 @@ +require 'chef/nexus' +require "#{File.dirname(__FILE__)}/../../../config.rb" + +nexus 'chef-nexus rspec test' do + nexus_url NEXUS_URL + nexus_repo NEXUS_REPO + nexus_auth NEXUS_AUTH + use_auth USE_AUTH + + remote_url "#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/artifact/1.7/artifact-1.7-classifier.pkg" + local_file '/tmp/chef_nexus_rspec_temp/no_extension' + action :upload +end diff --git a/spec/config_sample.rb b/spec/config_sample.rb new file mode 100644 index 0000000..2aa41cd --- /dev/null +++ b/spec/config_sample.rb @@ -0,0 +1,9 @@ +####################################################### +## This is the config file used for the RSpec tests. ## +## Feel free to modify any variables to your needs. ## +####################################################### + +NEXUS_URL = 'http://mynexus.net/nexus/content/' +NEXUS_REPO = 'chef-nexus-tests' +NEXUS_AUTH = 'gary:secr3t' +USE_AUTH = true diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..a419f31 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,158 @@ +# Copyright 2016, BlackBerry, Inc. +# +# 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. + +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +require 'chef/nexus' +require_relative 'config.rb' + +def chef_run(recipe, append_path = true) + `chef-client -z #{append_path ? './spec/chef/recipes/' + recipe : recipe} --force-formatter` +end + +def format_error(msg) + "================================================================================\n#{msg}\n================================================================================\n " +end + +def get_unique_file(path, basename, rest) + p = path.chomp('/') + fn = "#{p}/#{basename}#{rest}" + return fn unless File.exist?(fn) + n = 1 + n += 1 while File.exist?("#{p}/#{basename}__#{n}#{rest}") + "#{p}/#{basename}__#{n}#{rest}" +end + +def get_error(stdout, expected, fail_if) + stacktrace = stdout.match(/FATAL: Stacktrace dumped to (.*?chef-stacktrace\.out)/) + stacktrace = stacktrace ? stacktrace[1] : nil + err = stacktrace ? "Chef run did not report 'Chef Client finished'." : nil + + case fail_if + when Regexp + return "stdout matched the following when it should not have:\n#{fail_if}", stacktrace if stdout =~ fail_if + when String + return "stdout included the following when it should not have:\n#{fail_if}", stacktrace if stdout.include?(fail_if) + end unless fail_if.nil? + + # Each chef run can only fail due to one reason, so if it was an expected error, we can simply return nil + [' RuntimeError: ', ' NoMethodError: ', ' TypeError: ', ' ERROR: ', ' FATAL: '].each do |e| + the_error = (stdout.split(e).last).split("\n")[0...-1].join("\n") + case expected + when Regexp + if e + the_error =~ expected + return nil, nil + else + return "#{e.strip}\n#{the_error}", stacktrace + end + when String + if (e + the_error).include?(expected) + return nil, nil + else + return "#{e.strip}\n#{the_error}", stacktrace + end + else + return "#{e.strip}\n#{the_error}", stacktrace + end if stdout.include?(e) + end if err + + return err, stacktrace if expected.nil? + + case expected + when Regexp + return "stdout did not match the following when it should have:\n#{expected}", stacktrace unless stdout =~ expected + when String + return "stdout did not include the following when it should have:\n#{expected}", stacktrace unless stdout.include?(expected) + end + + [err, stacktrace] +end + +# data = { +# :recipe => 'recipe to test, must be given', +# :expected => 'fail if not match, can be errors', +# :fail_if => 'fail if match' +# } +RSpec::Matchers.define :converge_test_recipe do |data = {}| + fail 'All tests require a :recipe.' unless data[:recipe] + match do + stdout = chef_run(data[:recipe]) + puts stdout + + dir = RSpec.configuration.log_dir + '/' + File.dirname(data[:recipe]) + FileUtils.mkdir_p(dir) + + log_basename = File.basename(data[:recipe], '.*') + File.open(get_unique_file("./#{dir}", log_basename, '.stdout.log'), 'w+') { |file| file.write(stdout) } + + @error_message, stacktrace = get_error(stdout, data[:expected], data[:fail_if]) + @error_message = format_error(@error_message) unless @error_message.nil? + + FileUtils.cp(stacktrace, get_unique_file("./#{dir}", log_basename, '.stacktrace.out')) if stacktrace + + @error_message.nil? + end + failure_message do + @error_message + end +end + +def idempotency_helper(context, recipe, expected = nil) + describe context do + it { is_expected.to converge_test_recipe(:recipe => recipe, :expected => expected, :fail_if => '(up to date)') } + end + describe "[SKIP] #{context}" do + it { is_expected.to converge_test_recipe(:recipe => recipe, :expected => '(up to date)', :fail_if => nil) } + end +end + +def delete(remote) + if url_exists(remote) + execute_or_fail("curl -v #{USE_AUTH ? "-u #{NEXUS_AUTH} " : nil}-X DELETE #{remote}") + fail "Server responded with successful deletion, but '#{remote}' still exists." if url_exists(remote) + end +end + +def url_exists(url) + `curl --output /dev/null --silent --head --fail #{USE_AUTH ? "-u #{NEXUS_AUTH} " : nil}#{url}` + $?.exitstatus == 0 +end + +def execute_or_fail(cmd, check_http = true) + output = `#{cmd}` + fail_me = false + output.scan(%r{^ +(\d{3}) - .*?$}).each { |code| fail_me = true unless code[0] == '1' || code[0] == '2' } if check_http + if $?.exitstatus != 0 || fail_me + fail "Command failed: #{cmd}\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n#{output.strip}\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + end + output +end + +RSpec.configure do |config| + config.run_all_when_everything_filtered = true + config.filter_run :focus + config.add_setting :log_dir, :default => "test-results/#{Time.now.strftime('%Y%m%d_%H%M%S')}" + config.before(:suite) do + FileUtils.rm_rf(config.log_dir) + FileUtils.mkdir_p(config.log_dir) + FileUtils.rm_rf('/tmp/chef_nexus_rspec_temp/') + FileUtils.mkdir_p('/tmp/chef_nexus_rspec_temp/') + `echo 'all your base are belong to us' > '/tmp/chef_nexus_rspec_temp/has_extension.test'` + `echo 'its a trap!' > '/tmp/chef_nexus_rspec_temp/no_extension'` + delete("#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/") + end + config.after(:suite) do + FileUtils.rm_rf('/tmp/chef_nexus_rspec_temp/') + delete("#{NEXUS_URL.chomp('/')}/repositories/#{NEXUS_REPO}/chef-nexus-rspec-test/") + end +end